(注意)このブログは本家 のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.

いやー,いろいろ別の作業があって,更新が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() を利用して割り込みハンドラからメッセージを投げるように改造してみた. 実は改造したのが去年の正月でもう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() を追加.

ってとこかしら.