(第32回)KOZOSを改造しよう

2009/01/08

あなたは 人目のお客様です.

いやー,いろいろ別の作業があって,更新が1年以上も開いてしまった.

で,KOZOSなんだけど,1年間ちょっといろいろ考えたのだが,割り込みまわりを もっとOSっぽくするのと,あといくつか改善というか新機能のアイディアがあるので, まあ徐々に試していきたい.

まず「割り込み」と「リアルタイム性」について考えてみよう.

最初に考えなければならないのは,以下のことだ.

  1. ある(優先度の高い)スレッドの動作中に,別の割り込みを可能にするか?
  2. ある割り込みのハンドラ実行中に,別の割り込みを可能にするか?
  3. あるシステムコールの実行中に,別の割り込みを可能にするか?
以下に,それぞれについて考えてみよう.

「ある(優先度の高い)スレッドの動作中に,別の割り込みを可能にするか?」

まず1についてだが,リアルタイム性を確保するならばスレッドに優先度をつける だけではダメで,割り込みにも優先度をつけて,優先度の高いスレッドの動作中には 優先度の低い割り込みは入らないようにしたい.つまり,割り込みをマスクしたい.

なぜかというと,割り込み発生時にはまず割り込みハンドラが起動するが, 割り込みハンドラの内部では時間のかかる処理は行わないというのが鉄則なので, ハンドラは簡単な処理(受信バッファからのデータの吸い上げと割り込みの刈り取り等) のみ行って,あとは複雑な処理は処理用のスレッドにまかせたい. つまり割り込みハンドラは処理スレッドに(メッセージなどで)処理データを通知して, 終了してしまいたい.で,あとは処理スレッドが処理を行えばいいのだけど, ここで別の(もっと優先度の低い)割り込みが入ると,処理スレッドが割り込まれて, 処理の完了までのリアルタイム性を確保できない.

これを避ける方法のひとつとして,リアルタイム性の必要な処理はすべて割り込み ハンドラで行う,という方法もあるかもしれない. (高優先度の割り込みハンドラ実行中に低優先度の割り込みハンドラが起動されない ように,割り込み処理には優先度が必要だろう)

しかしそれではあんまりなので,対処としてとりあえず思いつくのは,割り込みに レベルをつけて,スレッドの優先度に対応させるという方法だ. 割り込みを受け付けて,あるスレッドがディスパッチされて動作を開始するときに, そのスレッドに対応する割り込みレベルよりも低レベルの割り込みをマスクして しまう,というものだ.

マスク処理は割り込みを管理している extintr スレッドが行う. このため extintr スレッドは最優先にする必要がある. というのは,割り込み発生時にはハンドラから処理スレッドに対して (メッセージによって)データが渡され,それによって処理スレッドが動作開始するが, このスレッドの動作開始(ディスパッチ)より前にマスク処理が行われないと まずいからだ. さらに extintr のマスク処理中にも割り込みを受け付けないように,extintr 実行中は SIGHUP を受け付けないようにシグナルの設定をする必要があるかもしれない.

こう考えると,extintr は割り込み管理スレッドともいうべきもので, 実際に(汎用OS上でなく)実CPU上でKOZOSを動かすとしても,必須のスレッド だということになる.

ちなみに割り込みのマスクには,extintr の fd_set を利用すればよい. マスクしたいソケットのビットを fd_set から落として select() することで, そのソケットからの入力はマスクされる. つまり select() 用の fd_set が,割り込みマスクレジスタ&割り込みステータス レジスタのような役割になる.

「ある割り込みのハンドラ実行中に,別の割り込みを可能にするか?」

次に2についてだが,割り込み応答性能を向上させるには,割り込み処理中に, もっと優先度の高い別の割り込みを受けることを可能にしたい. つまり割り込みのネストを可能にしたい.

しかしこれにはちょっと問題がある.というのは,たとえば ある割り込みを受けて,そのデータ処理はスレッドで行うとすると, スレッドのディスパッチが行われるまでデータ処理が行えないので, 広い意味での割り込み処理が完了しないことになる. (割り込みハンドラはデータ送信して完了しているが,データ処理は完了できない)

ここでたとえば低優先度の割り込みの処理中に,高優先度の割り込みがネストして 発生したとしよう.この場合,低優先度の割り込みハンドラは処理を中断して, 高優先度の割り込みハンドラが実行される.で,データをメッセージによって 処理用スレッドに送信するのだが,この後は中断された低優先度の割り込みハンドラの 処理が(まだOSのスタックに残っているので)継続して実行される.で,この低優先度の 割り込みハンドラの処理が終らないと,スレッドはディスパッチされない. ということはリアルタイム性を確保できない.

この根本対策としては,OSのカーネル内処理自体をスレッド化して,割り込みハンドラ はOSのコンテキストで実行するのではなく,スレッド化してやる,という方法が 考えられる(キュー操作などで排他したい箇所は,一時的に割り込み禁止にする. またシステムコールを呼び出したいときには,OSのシステムコール処理用関数を 直で呼び出して構わない).考えられるのだけれど,それじゃあハンドラから処理用 スレッドにメッセージ投げてあとはスレッドにまかせるという当初の作りと あまり変わらない気もする(割り込み禁止区間を短くすることで,応答性能を向上する 効果はあるだろうが).

ということで,割り込みハンドラ実行中の割り込みは受け付けない,という作りが いいだろう.このため割り込み応答性能はちょっと落ちるが,まあいいか.

ちなみにリアルタイム性についてだが,割り込みハンドラ実行中には割り込み禁止に なる.これは優先度の低い割り込みハンドラの実行中でも,割り込み禁止になる (優先度の高い割り込みであっても,待たされる). よって優先度にかかわらず,割り込みが発生してハンドリングされるまでの間に, 割り込みハンドラの処理の最悪時間がかかる(待たされる)可能性がある. これは割り込みハンドラの処理を見ることで見積もることができるので, まあリアルタイム性はあるといえるのではなかろうか. ただし割り込みハンドラの内部で,キューの検索とかのような 処理時間を見積もれない処理をしてはならないことになる.

また,割り込みハンドラの処理が終って割り込み可能にする際に,別の割り込みが すでに発生して待たされている可能性がある.これは複数の割り込みが発生している かもしれないので,優先度に応じてハンドリングする必要があるだろう.

「あるシステムコールの実行中に,別の割り込みを可能にするか?」

で,最後に3の話なのだけど,システムコールも割り込みの一種であると考えると, OSがシステムコールの処理をしている最中には割り込み禁止にしていいように思える. この場合,上で説明した「割り込みハンドラの処理の最悪時間」の見積もりに, システムコール処理も含めなければならないことになる.

うーん,なんかここまでくると,やっぱ割り込みハンドラをスレッド化して, 割り込み処理中の割り込みを可能にするといいのかもしんない. でもまあとりあえずいいや.

ちなみに割り込み中の割り込みが禁止だとすると,割り込みハンドラからシステム コールを呼ぶことも禁止になってしまうが,これは許可したい.というのは, これができないとハンドラからスレッドへのメッセージ送信ができないからだ. ちなみにシステムコールの処理中は割り込み禁止なので, 「システムコールの処理中に割り込みが発生し,ハンドラが起動して,その中から さらにシステムコールが呼ばれる」というような「システムコールのネスト」は 発生しない.

で,手始めの改造. 従来は extintr は kz_setsig() システムコールを使って割り込み(SIGHUP)を 受け付けていたのだけど,kz_sethandler() を利用して割り込みハンドラから メッセージを投げるように改造してみた.

(2009/04/10 ライセンスに関する文書として,KL-01とLICENSEを追加. 詳しくは第43回を参照)

実は改造したのが去年の正月でもう1年近く前で,なんでこんなふうに改造したのか あんまりおぼえていないので,ちょっと読んでわかる範囲で説明すると, もともと extintr の SIGHUP 受信時に行っていた処理を extintr_handler() という 関数にして,kz_sethandler() でそっちが呼ばれるようにしただけだ. さらにハンドラ内部からシステムコールが呼べるように,extintr_proc() で ハンドラ実行前に専用のコンテキストを用意するような処理を追加してある. (さらにそのシステムコールの延長で,用意したコンテキストがレディーキューに 対して処理されないように,getcurrent() とかに対処を入れている...のだと思う)

システムコールは割り込みハンドラから呼ばれる場合もあるので, システムコールのパラメータをカレントスレッドからでなく引数から取得するように syscall_proc() や thread_intr() を修正.thread_intrvec() も同様の理由で修正.

あと,割り込みハンドラからシステムコールが呼び出せるように, extintr_proc() 内部で block_sys によってSIGSYSを有効化する処理を追加.

あとついでに,割り込み禁止/有効化のサービス関数として kz_block()/kz_unblock() を追加.

ってとこかしら.


メールは kozos(アットマーク)kozos.jp まで