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

前回までで割り込みとタイマ周りをすっきりさせることができたので,いよいよ割り込みに優先度をつけて,リアルタイム性をさらに高めたい.

まず割り込みの優先度についてなのだけど,やりたいことは,ある割り込みを受けてあるスレッドが動作可能になったときに,特定の割り込みをマスクすることで,優先度の低い割り込みに割り込まれないようにすることだ.

優先度の低い割り込みが発生したときに,(たとえスレッドが切り替わらないにしても)割り込みハンドラが呼ばれてしまっては,結局はもともと動いていた(もっと優先度の高い)スレッドの処理が待たされてしまう.なので割り込みをマスクすることで,優先度の低い割り込みを完全に停止させなければならない.

結局のところ,スレッドのディスパッチ時に割り込みマスクされるような機能を追加することで,割り込みに優先度をもたせてリアルタイム性を確保することができる.

割り込みのマスクは,OSのカーネルが行う必要がある.というのはスレッドのディスパッチ直前(もしくはディスパッチと同時)に,そのスレッドの優先度に応じて割り込みマスクをかける必要があるからだ.

で,KOZOSでの割り込みのマスクのしかたなのだけど,現在,割り込みまわりはextintr スレッドが管理している.なので手っ取り早く考えつくのは,スレッドのディスパッチ前に extintr に割り込みマスクを行ってもらうという方法だ.具体的には,KOZOS がスケジューリングの後,スレッドをディスパッチする前にextintr スレッドに(カーネル内部から)依頼メッセージを投げて,さらに extintr をいったんディスパッチして,extintr スレッドに割り込みマスク処理をしてもらうようにする.(カーネル内部からメッセージを投げるには,メッセージ送信関数であるthread_send()を直接呼べばよい)

でもこの方法はダメ.というのは,extintr スレッドに依頼して割り込みマスクをしてもらった後にスケジューリングして本来のスレッドをディスパッチしようとするが,ここでまた extintr への依頼が発生してしまい,無限ループになってしまうからだ.これを防ぐためには,2度目は extintr にメッセージを投げないようになんらかの対処をする必要があるが,これはちょっとめんどいし,多重に割り込みがかかった場合になんかよくわからんことになりそうな気がする.

もうひとつの方法は,extintr が行っている割り込みまわりの処理をKOZOSのカーネルに持っていき,カーネルにやらせるというものだ.現状では各スレッドは extintr にメッセージを投げて割り込み処理を依頼しているが,これらのサービスはカーネルがシステムコールとして実装することになる.割り込みまわりはすべてカーネルの管理になるので,ディスパッチ前の割り込みマスク処理も容易だ.

で,ふつうの組み込みOSはそーいう作りになっている思うのだけど,KOZOSはなるべくサービスをスレッド化して,カーネル内部はすっきりさせていきたいという考えがある.実際に割り込みまわりのサービスも extintr スレッドが行っているし,前回はタイマサービスもスレッド化している(まあこれは,リアルタイム性の無い部分を外に切り出す,という意味も強いのだが).

ということで,割り込みまわりの管理とサービスは extintr に任せたまま,スレッドのディスパッチ前には割り込みマスクをかけるような実装にしたい.いろいろ考えたのだが,こんな実装はどうだろうか.
  • スレッドのディスパッチ前に特定の関数を呼ばせる機能をカーネルに追加する.具体的には,新規システムコール(kz_precall)を追加する(スレッドIDと関数ポインタを指定して kz_precall を呼ぶと,指定したスレッドがディスパッチされる直前に指定した関数が呼ばれるようになる).
  • extintr は各スレッドから割り込み処理(割り込み発生時には,そのスレッドにメッセージを投げる,という処理)の依頼を受けたら,kz_precall()でそのスレッドがディスパッチされる前に呼ばれる関数を設定する.
  • 上記関数には,特定の割り込みをマスクするような処理を記述しておく.これにより,割り込みが入って処理スレッドが動作する前に,特定の割り込みがマスクされるようになる.(マスクパターンを階段状にすることで,割り込みに優先度をつけることができる)
ということで,まずは準備として kz_precall() システムコールを追加してみた.ソースは以下のような感じ.修正内容は簡単で,スレッド構造体に関数ポインタ保存用の precall というメンバを追加し,kz_precall システムコールで設定,さらにディスパッチ前に関数を呼ぶような処理を入れているだけ.

ちなみに thread.c:thread_intrvec() でのスケジューリング処理の直後にコメントがあるように,KOZOSではシグナルのとりこぼしを防ぐために,ディスパッチ前に一瞬だけシグナルマスクを開けている.これは setcontext() でコンテキストが切り替わるためにシグナルが失われてしまうことが原因(このため実ハードウエア上で動作させるならば,この処理は不要だろう)なのだけど,precall の実行はこの蓋開けの前に行う必要がある.蓋開け後だと,precall によるマスク処理中に割り込みが入ってきたときに,なんかわけのわからんことになる気がするし,蓋開け前でマスク処理の設定をして,蓋開けによって割り込みが入ったとしても,再度スケジューリングが行われて必要に応じて precall が再度呼ばれてマスクが設定されなおすので,とくに問題は無い.

あー眠い.次回はいよいよ extintr にマスク指定処理を追加して,割り込みを優先度づけしてみよう.