xv6のプロセス切り替えの仕組み
xv6をMIPSに移植しながら、プロセス切り替えの仕組みを確認しているんだけれども、ISSの機能が実装できてなさ過ぎて泣けてくる。 xv6が最初に立ち上がったとき、処理はmpmain()へと移り、それからスケジューラに移動する。基本的には、以下のような動きが最初の処理になる。
schedによりコンテキストが切り替わり、まずはforkret関数が呼ばれ、fork時の最初の処理が呼ばれる。 次に本来ならば実際のコンテキストに切り替えが行われるのだが、最初のプロセスが立ち上がるときには、ログの領域を確保するためにinitlog()が呼ばれてるんだよな。 これがISSを実装して、プロセス切り替えができているかを確認するための弊害になるやっかいなところだ。
void finalizefork(void) { static int first = 1; release(&ptable.lock); if (first) { first = 0; initlog(); } }
さて、initlogではIDEにアクセスし、ログ出力をするための初期設定を行うのだが、IDEは反応が長いので、その間ずっとポーリングしているのは暇なので、sleep()処理、というかプロセスの切り替えが発生する。
void sleep(void *chan, struct spinlock *lk) { if(proc == 0) panic("sleep"); if(lk == 0) panic("sleep without lk"); if(lk != &ptable.lock){ //DOC: sleeplock0 acquire(&ptable.lock); //DOC: sleeplock1 release(lk); } proc->chan = chan; proc->state = SLEEPING; sched(); // Tidy up. proc->chan = 0; if(lk != &ptable.lock){ //DOC: sleeplock2 release(&ptable.lock); acquire(lk); } }
sleepって、自分のプロセスの状態を切り替えた上でschedを呼び出すのだけれども、schedも実際にはCPUの保持しているコンテキストと、現在のプロセスのコンテキストを切り替えて、元に戻すだけである。 なので、実際にはプロセス切り替えをした直後の、scheduler関数の途中に戻ってくるのが正しい。
で、さらに現在のプロセス一覧の中からRUNNABLEのプロセスを選択するのだが、実際にはまだOS立ち上がったばかりだし、まだRUNNABLEなプロセスは存在しないので、schedulerの中で何度もぐるぐる回っていることになる。 このループを抜けるのは、IDEの割り込みが発生し、initlog()が完了する準備が整ってからだ。
こうしてideの割り込みを無事に受け付けて始めて、initprocに処理が移るわけだ。まあ大変!
ちなみに、xv6ではこの割り込み処理のために、8256PICというICをリファレンスにしている。まあ、x86でも同様の機能を持っているため、x86で動かしやすいという訳だ。MIPSに移植するならば、この辺りの機構も変えなければならないのだけど、今は勉強がてら8256PICのモデルで行こう。
ちなみにどちらの記事もそれぞれ説明不足なので、両方読むことをおすすめする。