マルチタスクへの長い道程 その1 — つぎはぎOS

T-Kernel2.0そのものの移植は断念して、includeファイルの定数やシステムコールのextern宣言などのヘッダファイルだけをコピーして、仕様書を読みながら自分なりに「つぎはぎ」に少しずつ構築していくことにした。

割り込み管理機能関係を、まず実装した。割り込みができなければ、マルチタスクは実現できないからだ。割込み関連のシステムコール(割り込みハンドラはタスク独立部、とか仕様書に書いてあった)tk_def_int()、tk_ret_int()、EI()、DI()などの簡単なシステムコールを実装した。T-Kernel/SMにも割り込みコントローラー用のAPI機能があり、自作したプログラムを流用できそうだったので、それも実装した。これでTimer割り込みだけでも動作すれば、ラウンドロビンのマルチタスクのタスクスイッチングを実現できるのではないかと思った。プリエンプティブでないのでリアルタイム性はない。それで、NTR、、、、ではなく、NRT(Non-Real-Time)-OS(仮称)とでも呼ぶことにしようか、、、、

T-Kernel2.0の割り込みマスク関係のシステムコールの仕様では、変更前の割込みフラグの状態を変数へ保持するようになっている。多重割込みへの対応のためか?必要なのだろうか?理由は不明?それで、80386の割り込みフラグはどこにあるかというと、EFLAGSレジスタにあった。pushfdという、EFLAGSをスタックへ書き込むアセンブラ命令があるのを初めて知った。これを使って、フラグレジスタの値を取得することができる。しかし、アセンブルでエラーになった。16bit命令のはずのpushfはOKだった。調べてみると、pushfとpushfdは、どちらも0x9cという命令コードが同じで、実際に実行する時のCPUモードで動きが違うようになるらしい。だから、gasは区別せず、同じものとしてコードを作成するようだ。いいのか?ちょっと不安。
試行錯誤でブートモニターのサブルーチンをT-Kernel用へ修正をしていて、PIC操作の自作ルーチンにバグを見つけた。T-Kernel2.0の割込みコントローラー操作のAPI仕様では、タイマー割込みやキーボード割込みなどを一つずつ割込み許可、禁止の制御をするようになっているが、私はなぜだかPICコマンドのOCW3コマンドで、割込みマスクの現在の状態を保持するIMRレジスタを読み出しするのだと思っていて、steploaderやboot_monitorは、PIC初期化の時に数日試してもOCW3コマンドでIMRが読み出しできなかったので、初期化で一度に全ての割り込みマスク設定をして、個別での変更はできないのだと諦めていた。実際はICW1–>ICW4で初期化した後に、OCW1の読み出しで、いつでもIMRを読み出しできるらしい。マスク設定値を書き込みすることしかできないと思い込んでいた(マスクする時は同じレジスタへ値を書き込む)。
できるのが当たり前と言えば当たり前で、参考文献の解説記事の簡便なサンプルプログラムでは、一度にPICのマスクを設定する仕方は掲載されていたが、現在のマスク値を読みだして、当該ビットだけを変更して再設定するようにはなってなかったため、読み出しできないものと思い込んだようだ。自作OSを始めた初期の頃で、まだ回路関係の知識がない時期に作成した部分なので、こんな初歩的な思い違いをしていた。修正して、当該IRQだけ割り込み許可、禁止できることを確認できた。
少し話が違うが、PICにはIRRとISRというレジスタがあり、IRRは割り込みを「要求」している割り込み番号の情報を保持し、ISRは割り込み「処理」を受け付けた割り込み番号のビットを保持する。割り込みが複数発生して、待ち(CPUへの要求)状態になっている割り込み番号のIRRの各ビットがオンになり、割り込み要求が受け付けられて、割り込みハンドラが起動されると、割り込み処理中ということでIRRのビットがオフになり、ISRの割り込み番号のビットがオンになる。つまり、割り込みハンドラは自分の割り込み番号のIRRは参照できず、他の割り込みの要求状態だけ参照することができるということで、何に使うのかよくわからない。これも多重割り込みのためか?割り込みを禁止して、ポーリングする時に使うのか?とりあえず、T-Kernel2.0/SMにCheckInt()というAPIがあり、今後利用する場面があるかもしれないので、ついでに実装しておいた。

キーボード割り込みも自作のものを流用したが、どうしても割り込みハンドラが起動しない。それで思い出したのが、キーボードは入力回路上、マウスと一体になっていて、スレーブPICのマウス割り込みにも接続していることだ。マスターPICのスレーブPICとカスケード接続しているIRQ2の割り込みを許可したら、あっさりキーボード割り込みが起動できた。もう数か月前のことで、うっかり忘れていた。

T-Kernelには割り込みハンドラの高級言語対応ルーチンの機能を持っていて、一体どうやって実現しようか何日も悩んだ。
割り込みが発生して、ハンドラが動き出したらすぐ現在の、タスク実行していた時のレジスタ情報を退避し、終了前に同じ状態へ復帰しなければならない。そこはアセンブラが必要だ。しかし、T-Kernel仕様では割り込みハンドラはiret命令で終了してはならないことになっている。これは割り込みハンドラ内でタスク起動のシステムコールを実行した場合、タスク切り替えをしなければならず、割り込み発生時のタスクには戻らない可能性があるからだ。つまり、メモリの何処かへ退避したレジスタ情報をタスクディスパッチャーへ渡して、そこでタスクの実行中情報として保存してから、新たに実行するタスクのレジスタ情報をCPUへ設定しなければならない。
結論として、80386では高級言語対応ルーチンは実現が難しい、不可能ではないが、効率が悪すぎる、ということが分かって、断念した。理由は、タスク独立部である割り込みハンドラはIDTを利用するプロテクトモードでは、起動されても自分の割り込みベクタ番号を知ることができないからだ。つまり、高級言語対応ルーチンのアセンブラからどのC言語処理関数を実行するべきか判断できない。割り込みベクタごとに関数名を決め打ちにしてしまい、システムコールがなくてもリンクで自動的に結合できるが、最大255個の個別の割り込みハンドラを用意しなければならない。ARM-CPU用のサンプルプログラムでは、ハンドラ起動時のプログラムカウンタから割り込みベクタテーブルの先頭アドレスを引いて、4で割ることでベクタ番号を算出していた。おそらく8086モードのような単純な構造のベクタテーブルなのだろう。IDTはディスクリプタの内部に割り込みハンドラのアドレスを自由に書き込めるので、その方法は使えない。やるとしたら、アセンブラ部分を含めてC言語で丸ごと割り込みサブルーチンを作成するしかない。調べた限りでは、”_attribute__(interrupt)”のような指定をして、インラインアセンブラでレジスタ退避や復帰、tk_ret_int()の実行などを記述しなければならず、そうなると、もはや通常の関数ではないので、TA_ASM指定で登録することになり、せっかくの高級言語対応ルーチンが無意味になる。
割り込みはできるだけ処理を短くしなければならないのだから、アセンブラで記述するメリットはあるし、上記のようにC言語で記述することが生産性に寄与することがないのならば、少なくとも80386の仕様では、できないことはないが効率的ではない、と考えられる。そういうわけで、NTR-OS・・・・じゃなかった、、、NRT-OSでは高級言語対応ルーチンは実装しないことにした。(それとも何か解決策があるのだろうか?全く思いつかない)

T-Kernel2.0/SMのシステムメモリ管理機能のシステムコールを手抜きで実装した。VirtualBoxのRAMは1Gbyteに設定しているので、空き領域が数百Mbyteあるので、払い出しの機能だけで、解放や再配置、再利用はしない(Freeの時、何もしない)ことにした。払い出し用のメモリポインタだけインクリメントで管理し、領域を使い切ったらメモリ不足としてエラーを返す。当面、使い切る可能性はないので、手を抜けるところは抜いて、NRT-OSの機能を増やすことに専念しようと思う。

未だにタスクスケジューリングやタスクディスパッチへ辿り着けないが、長い道程を少しずつ歩いていこうと思う。

カテゴリー: Linux, VirtualBox, つぎはぎOS パーマリンク

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です