(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.
前回に引続き,最悪応答時間についてもう少し考えてみよう.
前回は,割り込み検知の際に割り込みレベルに応じて割り込みチェックするように修正した.で,現在のソースで実際に割り込みが入った場合のことを考えてみる.
たとえばある割り込みAが発生して,それに応じてスレッドAが動作するような場合を考えてみよう.
割り込みAの発生により,KOZOSの割り込みハンドラ(thread_intr())が呼ばれる.外部割り込み(SIGHUP)に対しては kz_setsig() で extintr が設定されているので,extintr スレッドにメッセージが投げられることになる.で,extintr は割り込みの発生状態を調べて(extintr_handler()),各割り込みの処理スレッド(この場合はスレッドA)に対してメッセージを投げることになる.スレッドAはメッセージを受けて,当該の処理を行うわけだ.
図にするとこんなかんじか.
問題は(*1)の割り込み蓋開け処理(thread_intrvec()の末尾で setcontext() によるディスパッチの直前に SIG_UNBLOCK でシグナルマスクを開けている処理)なのだけど,これはディスパッチ前に一瞬だけ割り込みを開けて,待たされている割り込みがあるならば割り込み処理を行うようになっている.これは実は KOZOS をPC-UNIX上で動作させるときの対処で,setcontext()でコンテキスト切替えを行うとシグナルが引き継がれないために割り込みが消えてしまうことの対処なのだけど,ここで割り込みを受け付けてしまうので,たとえば(*2)での割り込み全マスク前に別の割り込みが入っていたとすると,割り込み蓋開けによって(*1)の位置で割り込みを受け付けてしまう.これは優先度の低い割り込みでも受け付けてしまうので,問題だ.
まあ実はこれはPC-UNIX上で動作させる場合の問題であり,実ハードウエアで動作させるときには問題にはならないような気もするのだが,リアルタイム性ということを考えるときにはちょっとなんだかなあなつくりだ.
このへんのつくりの問題は,そもそも割り込み処理を extintr というスレッドに任せているという点にあるとおもう.このつくり自体はけっこう気に入っているのだが,割り込みの受け付けをスレッドが行っているため,割り込み処理の最中に割り込みマスクの切替えなどを気をつけなければならないようなつくりになってしまっている.これは複数の割り込みがもっと複雑にからみあった状態で最悪応答時間を見積もる際に,非常に複雑になってしまい都合が悪い.
ちなみに以下は現時点のソースでの割り込み処理の最悪応答見積もりのメモ.システムコールの処理中に割り込みが入って,システムコールの処理が終るまで割り込み処理が待たされて,さらに割り込み処理中に別の優先度の低い割り込みが入って...というイメージなのだけど,うーんわからん.ほんとはこれを図にして載せようかと思っていたのだけど面倒なのであっさり断念.
画像はこちら
ということで,割り込み検知を extintr でなく割り込みハンドラの延長で行うことで割り込みマスクの遷移を無くして,もっと単純化しようと思う.
...と思ったのだけど,そーいうふうに修正してみたのだけどなんかうまく動かない...以下が修正したソース.
とりあえず extintr.c の USE_MESSAGE という #define で動作を切替えられるようにしておいたので,USE_MESSAGE を有効にして,従来動作(割り込み検知を extintr が行う)で最悪応答を検証してみよう.
ここでは簡単のために,上の図の(*1)での割り込み蓋開け時の割り込みは考えないこととする.というのは,実ハードウエアではおそらくこのような蓋開け処理は不要だからだ.
で,割り込みAが発生してからスレッドAが動作するまでの時間が最悪応答になるのは,あるスレッドのシステムコール処理中で割り込み禁止状態になっていて,その最中に割り込みを受け付けた場合だ.この場合,システムコールの処理終了まで待ち合わせてから割り込み処理を行うことになる.で,実際の割り込みハンドラ呼び出しからスレッドAが動き出すまでは,上の図の動作をすることになる.ここで(*2)の前に優先度の低い割り込みが発生したとしても,(*1)の位置での蓋開けを考えないならば,その割り込みの検知はずっとあとに遅れさせられるので,スレッドAの動作に影響を及ぼすことはない.(*2)のマスク処理以降は,優先度の低い割り込みはマスクされるので,SIGHUPが発生することはないので問題は無い.
問題は,割り込み優先度が高→低に戻されるときに,優先度の低い割り込みが残っていたら割り込み処理を行う,という動作だ.これは preproc() の末尾で以下のようにして行っている.
しかしこれは preproc() の内部で行われているので,kz_precall()に設定したスレッドのディスパッチ前ハンドラ呼び出しによって行われる.なので,割り込み禁止状態で行われる.つまりここの処理は割り込み応答にそのまま影響する.で,最悪の場合には全割り込みをサーチすることになるので,最悪応答時間に計上することになり,最悪応答時間がそれなりに悪化することになる.
う~~~ん,いろいろ考えたんだけど,結局のところ全割り込みサーチはどこかで行われることになるので,最悪応答に必ず計上しなければならないことになるのかなあ...ちなみに以下が,修正後の場合の最悪応答の見積もりのメモ.
画像はこちら
ちなみに,割り込み処理を extintr でなく割り込みハンドラで行う場合,extintr との排他を考える必要がある.どういうことかというと,extintr は通常スレッドからの割り込み監視要求を受け付けて,割り込み処理用のテーブルの設定を行う(extintr_mainloop()の延長で,interrupts[]に設定する).このテーブル設定と,割り込みハンドラでの割り込み処理時のテーブル参照がぶつかると,なんかまずそうな気がするからだ.
まあもっとも現状で動的に設定しているのが interrupts[].id だけで,interrupts[] の他のパラメータは初期化時に設定してそのまま変化しないので,とくに問題は無いような気もする.あと extintr が動くときは allmask() によって割り込み優先度が最高になっているために外部割り込みは入らないので,実際には排他のことは考えなくてもいい気もする.ただ,本来ならばこのような割り込み設定処理は extintr のようなスレッドに任せるのでなく,カーネルがシステムコールを持って,カーネル内部でやるべきなのかもしれない.(extintr が行う場合,extintr の実行中は割り込みマスクしているので,これはこれで最悪応答性に影響する)
うーん,なんか難しいなあ...とりあえず言えることは,KOZOSは今までOSとしての機能を極力スレッドによって実現するというポリシーで作ってきた(割り込み処理を extintr スレッドに任せるなどが代表的).これはこれですっきりしたつくりになるのだが,しかし割り込み処理に関していうと,スレッドに任せるというのはリアルタイム性を考える上で非常によろしくない.KOZOSの現状のソースコードでは,スレッド化というポリシーと,リアルタイム性の追求という方向性がぶつかってしまい,矛盾に陥っているように感じる.
リアルタイム性を考えるならば,割り込み設定処理は専用のシステムコールを作成し,割り込み検知処理は割り込みハンドラ内部で行うことで,すべてカーネル内部で行うべきのように感じる.これらをスレッドに任せると,ディスパッチ時の優先度切替えとか割り込み受け付けとかを非常に気にしなければならなくなり,破綻するようだ.
まあちょっとよくわからなくなってきたので,残念だがこれはこれでとりあえず置いておこう.ちょっと他にやりたいことがたまっているので,今後はそっちを進めることにしよう.
ちょっと extintr.c が複雑化しているので,いっそのことリアルタイム性をまったく考えずにシンプルさとわかりやすさを追求したバージョンを別に作ってもいいかもしれないなあ.実は今後は実ハードウエアへの移植を考えたいのだけど,一回,もうちょっとシンプルに書き直してもいいかもしれない.
前回に引続き,最悪応答時間についてもう少し考えてみよう.
前回は,割り込み検知の際に割り込みレベルに応じて割り込みチェックするように修正した.で,現在のソースで実際に割り込みが入った場合のことを考えてみる.
たとえばある割り込みAが発生して,それに応じてスレッドAが動作するような場合を考えてみよう.
割り込みAの発生により,KOZOSの割り込みハンドラ(thread_intr())が呼ばれる.外部割り込み(SIGHUP)に対しては kz_setsig() で extintr が設定されているので,extintr スレッドにメッセージが投げられることになる.で,extintr は割り込みの発生状態を調べて(extintr_handler()),各割り込みの処理スレッド(この場合はスレッドA)に対してメッセージを投げることになる.スレッドAはメッセージを受けて,当該の処理を行うわけだ.
図にするとこんなかんじか.
カーネル 子プロセス extintr スレッドA
│ │ │ │
│ │ kz_recv()待ち kz_recv()待ち
│ SIGHUP │←割り込み発生 │ │
│←───────│ │ │
割り込み処理 │ メッセージ │ │
│────────────────→│ │
schedule() │ │ │
(extintrがcurrentになる) │ │ │
│ │ │ │
precall() │ │ │
(割り込みが全マスクされる)(*2)│ │ │
│ │ │ │
割り込み蓋開け(*1) │ │ │
│ │ │ │
dispatch() │ │ │
│────────────────→│ │
│ │ 割り込み検知 │
│ │ │ メッセージ │
│ │ │───────→│
│ │ kz_recv()待ち │
│←────────────────│ │
schedule() │ │ │
(スレッドAがcurrentになる) │ │ │
│ │ │ │
precall() │ │ │
(割り込みA以上のレベルの │ │ │
割り込みがマスクされる) │ │ │
│ │ │ │
割り込み優先度が低くなるので, │ │ │
残っている割り込みが無いか検索 │ │ │
するために,extintrに割り込み │ │ │
処理メッセージを投げる │ │ │
│ メッセージ │ │ │
│────────────────→│ │
│ │ 割り込み検知 │
│ │ │ │
│ │ kz_recv()待ち │
│←────────────────│ │
schedule() │ │ │
(スレッドAがcurrentになる) │ │ │
│ │ │ │
currentスレッドがスレッドAの │ │ │
ままで変化していないので, │ │ │
precall()処理は行われない │ │ │
│ │ │ │
割り込み蓋開け │ │ │
│ │ │ │
dispatch() │ │ │
│─────────────────────────→│
│ │ │ 割り込み処理
│ │ │ │
│ │ │ │
問題は(*1)の割り込み蓋開け処理(thread_intrvec()の末尾で setcontext() によるディスパッチの直前に SIG_UNBLOCK でシグナルマスクを開けている処理)なのだけど,これはディスパッチ前に一瞬だけ割り込みを開けて,待たされている割り込みがあるならば割り込み処理を行うようになっている.これは実は KOZOS をPC-UNIX上で動作させるときの対処で,setcontext()でコンテキスト切替えを行うとシグナルが引き継がれないために割り込みが消えてしまうことの対処なのだけど,ここで割り込みを受け付けてしまうので,たとえば(*2)での割り込み全マスク前に別の割り込みが入っていたとすると,割り込み蓋開けによって(*1)の位置で割り込みを受け付けてしまう.これは優先度の低い割り込みでも受け付けてしまうので,問題だ.
まあ実はこれはPC-UNIX上で動作させる場合の問題であり,実ハードウエアで動作させるときには問題にはならないような気もするのだが,リアルタイム性ということを考えるときにはちょっとなんだかなあなつくりだ.
このへんのつくりの問題は,そもそも割り込み処理を extintr というスレッドに任せているという点にあるとおもう.このつくり自体はけっこう気に入っているのだが,割り込みの受け付けをスレッドが行っているため,割り込み処理の最中に割り込みマスクの切替えなどを気をつけなければならないようなつくりになってしまっている.これは複数の割り込みがもっと複雑にからみあった状態で最悪応答時間を見積もる際に,非常に複雑になってしまい都合が悪い.
ちなみに以下は現時点のソースでの割り込み処理の最悪応答見積もりのメモ.システムコールの処理中に割り込みが入って,システムコールの処理が終るまで割り込み処理が待たされて,さらに割り込み処理中に別の優先度の低い割り込みが入って...というイメージなのだけど,うーんわからん.ほんとはこれを図にして載せようかと思っていたのだけど面倒なのであっさり断念.
画像はこちら
ということで,割り込み検知を extintr でなく割り込みハンドラの延長で行うことで割り込みマスクの遷移を無くして,もっと単純化しようと思う.
...と思ったのだけど,そーいうふうに修正してみたのだけどなんかうまく動かない...以下が修正したソース.
とりあえず extintr.c の USE_MESSAGE という #define で動作を切替えられるようにしておいたので,USE_MESSAGE を有効にして,従来動作(割り込み検知を extintr が行う)で最悪応答を検証してみよう.
ここでは簡単のために,上の図の(*1)での割り込み蓋開け時の割り込みは考えないこととする.というのは,実ハードウエアではおそらくこのような蓋開け処理は不要だからだ.
で,割り込みAが発生してからスレッドAが動作するまでの時間が最悪応答になるのは,あるスレッドのシステムコール処理中で割り込み禁止状態になっていて,その最中に割り込みを受け付けた場合だ.この場合,システムコールの処理終了まで待ち合わせてから割り込み処理を行うことになる.で,実際の割り込みハンドラ呼び出しからスレッドAが動き出すまでは,上の図の動作をすることになる.ここで(*2)の前に優先度の低い割り込みが発生したとしても,(*1)の位置での蓋開けを考えないならば,その割り込みの検知はずっとあとに遅れさせられるので,スレッドAの動作に影響を及ぼすことはない.(*2)のマスク処理以降は,優先度の低い割り込みはマスクされるので,SIGHUPが発生することはないので問題は無い.
問題は,割り込み優先度が高→低に戻されるときに,優先度の低い割り込みが残っていたら割り込み処理を行う,という動作だ.これは preproc() の末尾で以下のようにして行っている.
#ifdef USE_MESSAGE
if (id) {
intrpri_old = intrpri_current;
intrpri_current = n;
if (extintr_id && (intrpri_current > intrpri_old)) {
kx_send(extintr_id, 0, NULL);
}
}
#else
intrpri_old = intrpri_current;
intrpri_current = n;
if (intrpri_current > intrpri_old) {
extintr_handler(SIGHUP);
}
#endif
しかしこれは preproc() の内部で行われているので,kz_precall()に設定したスレッドのディスパッチ前ハンドラ呼び出しによって行われる.なので,割り込み禁止状態で行われる.つまりここの処理は割り込み応答にそのまま影響する.で,最悪の場合には全割り込みをサーチすることになるので,最悪応答時間に計上することになり,最悪応答時間がそれなりに悪化することになる.
う~~~ん,いろいろ考えたんだけど,結局のところ全割り込みサーチはどこかで行われることになるので,最悪応答に必ず計上しなければならないことになるのかなあ...ちなみに以下が,修正後の場合の最悪応答の見積もりのメモ.
画像はこちら
ちなみに,割り込み処理を extintr でなく割り込みハンドラで行う場合,extintr との排他を考える必要がある.どういうことかというと,extintr は通常スレッドからの割り込み監視要求を受け付けて,割り込み処理用のテーブルの設定を行う(extintr_mainloop()の延長で,interrupts[]に設定する).このテーブル設定と,割り込みハンドラでの割り込み処理時のテーブル参照がぶつかると,なんかまずそうな気がするからだ.
まあもっとも現状で動的に設定しているのが interrupts[].id だけで,interrupts[] の他のパラメータは初期化時に設定してそのまま変化しないので,とくに問題は無いような気もする.あと extintr が動くときは allmask() によって割り込み優先度が最高になっているために外部割り込みは入らないので,実際には排他のことは考えなくてもいい気もする.ただ,本来ならばこのような割り込み設定処理は extintr のようなスレッドに任せるのでなく,カーネルがシステムコールを持って,カーネル内部でやるべきなのかもしれない.(extintr が行う場合,extintr の実行中は割り込みマスクしているので,これはこれで最悪応答性に影響する)
うーん,なんか難しいなあ...とりあえず言えることは,KOZOSは今までOSとしての機能を極力スレッドによって実現するというポリシーで作ってきた(割り込み処理を extintr スレッドに任せるなどが代表的).これはこれですっきりしたつくりになるのだが,しかし割り込み処理に関していうと,スレッドに任せるというのはリアルタイム性を考える上で非常によろしくない.KOZOSの現状のソースコードでは,スレッド化というポリシーと,リアルタイム性の追求という方向性がぶつかってしまい,矛盾に陥っているように感じる.
リアルタイム性を考えるならば,割り込み設定処理は専用のシステムコールを作成し,割り込み検知処理は割り込みハンドラ内部で行うことで,すべてカーネル内部で行うべきのように感じる.これらをスレッドに任せると,ディスパッチ時の優先度切替えとか割り込み受け付けとかを非常に気にしなければならなくなり,破綻するようだ.
まあちょっとよくわからなくなってきたので,残念だがこれはこれでとりあえず置いておこう.ちょっと他にやりたいことがたまっているので,今後はそっちを進めることにしよう.
ちょっと extintr.c が複雑化しているので,いっそのことリアルタイム性をまったく考えずにシンプルさとわかりやすさを追求したバージョンを別に作ってもいいかもしれないなあ.実は今後は実ハードウエアへの移植を考えたいのだけど,一回,もうちょっとシンプルに書き直してもいいかもしれない.