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

前回に引続き,最悪応答時間についてもう少し考えてみよう.

前回は,割り込み検知の際に割り込みレベルに応じて割り込みチェックするように修正した.で,現在のソースで実際に割り込みが入った場合のことを考えてみる.

たとえばある割り込み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 が複雑化しているので,いっそのことリアルタイム性をまったく考えずにシンプルさとわかりやすさを追求したバージョンを別に作ってもいいかもしれないなあ.実は今後は実ハードウエアへの移植を考えたいのだけど,一回,もうちょっとシンプルに書き直してもいいかもしれない.
AD
(注意)このブログは本家 のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.

さて,ここまででKOZOSのリアルタイムOS化を進めてきたのだけど,いよいよ大詰めだ.

まず手始めとして,割り込みの最悪遅延時間について考えてみよう.

前回説明した通り,KOZOSはOS内部は割り込み禁止で走っている. なので割り込みが入ったときに,実際にはすぐに割り込みハンドラなどが 呼ばれるわけではなく,ある程度待たされる.問題は,これが最悪の場合に どれくらい待たされるのかということだ.この最悪遅延時間がきちんと見積もれる ことは,リアルタイムOSとしての必須条件だ.

まず前回までの復習だが,カーネル内部処理(システムコール処理)中に割り込みを 受け付けると,割り込み処理後にカーネル内部処理に戻って来なければならず, 割り込み処理後のスレッドの動作のリアルタイム性を保証できない. 割り込みハンドラにレベルを設けて,ハンドラ実行中の(もっと優先度の高い) 割り込みを受け付けたとしても,同様の問題が発生する. これは既知の問題で,この防止策としてKOZOSではOS内部処理 (kz_sethandler()で設定した割り込みハンドラ実行中や,システムコールの処理中) は割り込み禁止にしている.

また,割り込みハンドラ中でキューの検索とか行うと,割り込み禁止区間の処理時間が 見積もれないことになり,リアルタイム性が無くなる原因になる.よって割り込み ハンドラで,リアルタイム性の無い(処理時間を見積もれない)処理をしてはいけない.

結局のところ割り込みハンドラで行うのは,割り込みの刈り取りとデータ受信と スレッドへのメッセージ送信くらいだ.これは kz_setsig() により extintr で 行って,スレッドはメッセージを受けるようにすれば,割り込みハンドラは不要と なる.なのでKOZOSは割り込みハンドラは使わずに,kz_setsig() & extintr で 割り込み処理するという方向性になっている. (kz_sethandler() もいちおう残してはいるが,推奨しない)

また割り込みハンドラからのシステムコール呼び出しだが,割り込み中の割り込みに なってしまい,そのままでは呼び出せない (割り込み中は割り込み禁止.OS内部は割り込み禁止で走っている). まあ実際にはソフトウエア割り込みなので,特別扱いしてもいいような気もするし, システムコール割り込み(実体はSIGSYS)だけ蓋を開けることはできるのだが, なんか気持が悪いので,OSのサービスを,スレッド用のもの(システムコール)と 非コンテキスト用のもの(サービスコール)で明確に分けるようにした. で,サービスコールは単なる関数呼び出し(カーネル内のサービス関数を直接呼び出す) になっている.これは前回の話.

さらに現状で,KOZOSのカーネル内部では,キューの検索などといったような, 処理時間を見積もれないような箇所は無い. これは前回にちょっと説明を忘れたことなのだけど,前回新設したサービスコールに しても,処理時間を見積もれない処理は無く,すべて処理時間が見積もれる (kx_kmalloc()によるメモリ獲得も,キュー検索などはしないように書いてある). これはリアルタイムOS化する上で,非常に重要なことだ.

なお extintr は最優先のスレッドなので,extintr の場合は全割り込みマスクする. でないとあるスレッドの実行中に割り込みが入りextintrが実行され, ここで割り込みマスクが開いてしまうと,もっと優先度の低い割り込みを 受け付けてしまい,もとのスレッドが割り込みマスクしている意味が無くなって しまうので.

で,割り込み最悪応答時間なのだけど,たとえばある割り込みAを受けてスレッドAが 動作するとして,割り込み要因発生からスレッドAが動作するまでの動作を 考えてみよう.
  1. 割り込みAが発生.(子プロセスが SIGHUP を発行)
  2. KOZOSの割り込みハンドラ(thread_intr())が呼ばれ,その延長でextintrに向けて メッセージ発行される.(extintrをkz_setsig()で登録してあるので)
  3. 割り込み処理の終了後にスケジューリングが行われ,extintrがスケジュール される.(extintrは最高優先度なので)
  4. extintrのディスパッチ前ハンドラが呼ばれて割り込みが全マスクされる.
  5. extintrがディスパッチされる.
  6. extintrがメッセージを受け,extintr_handler()により割り込みチェックを行う. 割り込みAが発生しているので,その処理スレッド(スレッドA)に対して メッセージを投げる.
  7. extintrの処理が終了し,スレッドAがスケジューリングされる.
  8. スレッドAのディスパッチ前ハンドラが呼ばれ,(extintr実行によって) マスクされていた割り込みが解除される.
  9. スレッドAがディスパッチされる.
注意として,途中で割り込みAよりももっと優先度の高い別の割り込みがかかることは 考えない.というのは,優先度の高い割り込みがかかったらスレッドAの処理を後回し にしてそっちを優先的に処理する必要があるので,割り込みAの応答は遅れて当然 だからだ. (たとえば優先度の高い割り込みがかかりっぱなしになったらスレッドAは一切動作 できないので,スレッドAの最悪応答時間は無限大になってしまうが,これは当り前の ことである.なので,割り込みAよりももっと優先度の高い割り込みがかかることを 考える意味は無い)

で,上記4の動作の部分には現状でちょっと問題がある.ここで割り込みは全マスク され,その後は8の動作までマスクされたままなので,この間に優先度の低い 割り込みが発生することは無い.マスクされているので,割り込み要因が発生しても SIGHUPが発行されないわけだ. (まあもっとも現状では子プロセスが割り込みコントローラを模しているので, 子プロセスの動作が遅れたらすれ違いでSIGHUPが発行されてしまう可能性はあるの だが,実ハードウエアでは割り込みマスクはレジスタ操作で即有効になるので, ここではそのようなことは考えないことにする)

もしも上記6の動作の前に,割り込みAよりも優先度の低い別の割り込みが発生していた 場合のことを考えよう.この場合,優先度の低い割り込みはマスクされているので SIGHUPは発行されないのだが,6の処理では以下のようにして,割り込み状態を 総なめで調べている.

static void extintr_handler() { int i; struct interrupts *intp; for (i = 0; i < EXTINTR_INTERRUPTS_NUM; i++) { intp = &interrupts[i]; if (intp->type == INTERRUPTS_TYPE_UNKNOWN) continue; if (intp->checkintr(intp)) { intp->intr(intp); } } return; }

なので,優先度の低い割り込みがかかっていた場合に,その割り込みが検知され, 処理関数が呼ばれてしまうことになる.まあ処理関数と言っても実際には サービスコールによってスレッドにメッセージを投げるだけなのだが, 優先度ということを考えると,なんかいまいちな作りだ. (割り込み応答性能を考える際に,優先度の低い割り込みの処理時間もすべて計上 しなければならないことになる)

まずいのは上記4の動作の直前に優先度の低い割り込みが発生した場合だ. この場合にはマスク前なので,SIGHUPが発行されてしまう.この場合には,4の直後 (ディスパッチ直前)の割り込み蓋開け処理でシグナルハンドラが呼ばれることになる. で,extintr_handler()による割り込み検知が行われてしまうことになる (検知するのが優先度の高い割り込みならばこれはしょうがないのだが, 優先度の低い割り込みであっても検知してしまう). なので最悪応答時間の見積もりでは,4の直後にSIGHUPの処理が行われる, ということも考慮しなければならない.

まあもっともこれらはすべてカーネル内部処理と extintr の処理なので, 最悪値として見積もることはできるのだが. (カーネル内部処理中と割り込みハンドラ実行中の割り込みを許していない (カーネル内部処理はネストできない)ので,割り込み応答の最悪時間が長くなるのは しかたがないといえる)

このように割り込みAの処理中に優先度の低い割り込みが検知されてしまう可能性が あるのだが,優先度の低い割り込みを検知したところで,その割り込み処理スレッド が動作できるのはずっと後だ(割り込みAとスレッドAのほうが優先度が高いので, その前にそっちの処理が行われるから).ということで,extintr_handler() で 割り込み検知する際に,割り込み優先度に応じて必要なぶんだけ調べるように改良 しよう.これにより,優先度の低い割り込みの処理は先送りされるので,割り込みの 最悪応答時間の見積もりが楽になる.(先述したように,途中でもっと優先度の高い 割り込みがかかった場合のことは考えない)

ただしそのように修正すると,優先度の低い割り込みのSIGHUPが空打ちされてしまい (たとえば割り込み禁止区間で割り込みAと同時に優先度の低い割り込みが発生すると, SIGHUPは1発しか発行されない),スレッドAの処理が完了した後で,その優先度の 低い割り込みの処理が行われない可能性がある. (extintr_handler()が呼ばれれば割り込み検知されるのだが, SIGHUPが消えてしまっているので,extintr_handler()が呼ばれるきっかけが無い)

この対策として,割り込みの優先度を上げる場合には,extintr に対してSIGHUP発生時 相当のメッセージを投げて,extintr_handler()が呼ばれるように手を入れる.

ソースは以下のような感じ. 修正内容は上に書いた通り. ついでに extintr.c:preproc() で,必要なもののみマスク処理を行うように修正した.

さらに,前回の修正では実はサービスコール呼び出し時に変数 current がそのまま 残っていて,kz_send() が誤動作していた.この対策として,サービスコールの 呼び出し(srvcall_proc())の先頭で current をNULLにするように修正.

あと,これは前回ちょっと誤解していたのだけど,割り込み処理からシステムコールを 呼んだ場合には current == NULL となっているので, thread_intrvec() の

if (current == NULL) { return; }

という処理でそのままシグナルハンドラから戻り,前の処理が続行されるように なっていた.なので,前回言った 「システムコールのあとにそのまま処理が戻ってこない」というのは間違いだった. ただ,サービスコール呼び出し時に current を NULL にするように修正したので, 上記処理が残っているとまずいし,そもそもサービスコールを呼んだ場合に current が変更されてしまい,NULLになっている可能性もあるので,やっぱし 上記処理はまずい.もともと上記の処理は第32回に割り込みハンドラ中から システムコールを呼べるようにするために追加した処理で,これは前回サービスコール を実装することで廃止した動作なので,今となっては不要な処理だ. ということで,上記の処理は削除する.

ついでに前回のコードだと,たまに動作が止まってしまうことがあったのだが, 今回の修正でなんかそれが発生しなくなった.優先度がらみで割り込みがマスクされ, そのままコンテキストが変わってしまうと,発生したシグナルが消えてしまうのかも しれない.まあちょっとよくわかっていないのだが.うーん不明.
AD

紹介マンガを描いた

テーマ:

OSCに出展するのにパンフの裏に入れるマンガを描いたのだけど、せっかくなので公開してみる。

気が向いてなんとなくシャレで描いてみたのだけどどうでしょう。



KOZOSのブログ-紹介マンガ1

KOZOSのブログ-紹介マンガ2

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

2/20(金)~21(土)に大久保の日本電子専門学校で開催された,オープンソースカンファレンス2009 Tokyo/Springに出展してきました.

はりぼて友の会として出展してきたのですが,KOZOSははりぼてOSをベースにしているというわけではないのでちょっと異質というか間借りするような感じで申し訳ないなーと思っていたのだけど,ほとんどのひとに「はりぼて友の会は自作OSの会と言ってもいいのだから全然OKでしょう」と言ってもらえました.感謝感謝ですね.

いやー,OSC楽しかったなー.やっぱり自分の作った成果を紹介するってのは楽しいもんだねえ.思った以上にアットホームで(というより,まったく予想していなかった),他のコミュニティのひとや来場者などいろんな人と話せたし,初参加にもかかわらずとても楽しむことができました.KOZOSの説明を聞いてくれた大勢の方々はわたしの拙い説明に時間を割いていただいて,どうもありがとうございました.はりぼて友の会のひとたちやその他のブースの方々もいいひとばかりで,いろいろ面倒見ていただいて感謝です.

次回はぜひ独自ブースで参加したいなあ.あ,でもそのためにはプロジェクト名というか団体名(現状でぼくひとりなのだけど)を決めないといかんなあ.

で,KOZOSの今後なのだけど,いろんなひとと話して思ったことなのだが,やっぱし実ハードウエアでも動かないとインパクトが弱いかな,と.実はとあるPowerPCのボード上で動いた実績はあって,まあ公開せずにそのまま放置してあるのだけど,今後はもっと実ハードウエアへの移植を積極的にやっていこうかな,と思う.展示でも,デモで実ボードで動かしていたら興味そそられるしね.

ということで,リアルタイム性の追求がひと段落したら,次は実ハードウエアへの移植を進めていきたい.KOZOSの方針として「パッと試せる」というものがあるので,手に入りやすいボードをターゲットにして,クロスコンパイルのビルドとか開発環境の構築,ファームウエアのダウンロード方法なども詳しく解説したい.

ターゲットCPUは,まずはPowerPC,あとSHかアーム,個人的に好きなMIPSかな.まあ割り込み処理とスタートアップとコンテキストスイッチ部分だけアセンブラで書けばいいだけなので,それほど難しくはないだろう.とりあえずひととおりやってみたい.

問題はターゲットボードなのだけど,入手が用意で安価で使いやすいもの,という条件になるけど,うーん何がいいだろう.MicroblazeになっちゃうけどSUZAKUとかいいかもなあ(Virtexのやつは高そうだし).もしくは(Interfaceについてる付録基板とかいいかも.PowerPCならOpenBlocks玄箱かな.ていうか玄箱ってARMのやつもあるのね.それでもいいかも.SHだったら...うーんどうしよう.

あとはやっぱし組み込みOSとしてのウリをつけたいなあ.今考えているネタはちょっといろいろ(山のように)あるのだけど,もうちょっと発展させていきたいね.

配布用CDを作ってみた

テーマ:

あさってはオープンソースカンファレンスということで、配布用のCDを作ってみた。


KOZOSのブログ-配布用CD

学習用ということで、入門っぽいやさしい感じにしてみた。

カラフルでなかなかいいね。きれいにできるもんだなあ。

ふつうのCD-Rに、ラベル用紙に印刷して貼り付けだだけなのだけど。


で、やっとこさ40枚ほど作ったところ。ねむいー。

何枚くらい作っとけばいいんだろうなあ。