cpu_support.asmには移植の二つの山場のうちのひとつ、dispatch()関数がアセンブリ言語で記述されて収められています。dispatch()関数は現在実行中のタスクのコンテキストをタスクスタックに退避した後、タスクスタックと実行再開番地をruntskが示すTCBに退避します。そして、schedtskが示す次のタスクにコンテキストを切り替えて実行します。
dispatch()関数の中身は
の4つに分かれます。
dispatch()の先頭はコンテキスト保存部です。ここでは実行中のタスクの実行コンテキストをタスクスタックに保存し、スタックポインタと再開番地をruntsk->TCBに保存します。
なお、実行再開番地は常にdispatch_rです。
_dispatch: [--sp] = (r7:4, p5:3); [--sp] = rets; [--sp] = fp; p0.H = _runtsk; p0.L = _runtsk; p0 = [p0]; // p0 を runtsk に [p0+TCB_sp] = sp; // タスクスタックを保存 p1.H = dispatch_r; // 実行再開番地を保存 p1.L = dispatch_r; [p0+TCB_pc] = p1; jump dispatcher;
コンテキスト保存部で保存されたタスクはかならずこのアドレスから再開されます。なお、この部分へはタスク割り当て部から飛んできます。飛んできた時点でタスクスタック・ポインタは復帰しています。
実行再開部では本当にタスクに戻る前にタスク例外要求の有無を調べます。もしタスク例外の処理が必要ならば、call_texrtn()を呼びます。
dispatch_r: fp = [sp++]; p1.H = _runtsk; p1.L = _runtsk; p0 = [p1]; r0 = [p0+TCB_texptn]; // runtsk->texptn cc = r0; cc = !cc; // texptnが0なら1 r0 = [p0+TCB_enatex]; r1.L = TCB_enatex_mask&0xffff; r1.H = TCB_enatex_mask>>16; r0 = r0 & r1; cc |= az; // texptrnが0か、enatexが0なら if cc jump dispatch_r_1; // そのときは即リターン sp += -12; // ダミー引数領域確保 call _call_texrtn; sp += 12; // ダミー引数領域開放 dispatch_r_1: rets = [sp++]; (r7:4, p5:3) = [sp++]; rts; _dispatch.end:
タスク割り当て部は次に実行するべく用意されているタスクにコンテキストを切り替えます。次に実行するタスクは必ずschedtskに用意されており、これがNULLの場合は次に実行するタスクはありません。
割り当てられたタスクがない場合には割り込み待ちに移ります。
dispatcher: /* * ここでは割込み禁止状態でなければならない. */ p0.H = _schedtsk; p0.L = _schedtsk; p1.H = _runtsk; p1.L = _runtsk; r0 = [p0]; [p1] = r0; // schedtsk を runtskに cc = r0; if !cc jump dispatcher_1; // runtskが無ければ割り込み待ちに。 p0 = r0; // p0はruntsk sp = [p0+TCB_sp]; // タスクスタック復帰 p1 = [p0+TCB_pc]; // 実行再開番地復帰 jump (p1); // 実行再開番地へ飛ぶ
割り込み待ち部はIVG14状態で割り込みを待ちます。IVG14状態にいったん遷移するため、割り込みハンドラ内部では「非タスクコンテキストで割り込みが起きた」と認識します。そのため、割り込みハンドラの出口でdispatch()が再帰呼び出しをかけられる心配はありません。
割り込み発生後、reqflgを確認します。reqflgが0なら再び割り込みを待ちます。
dispatcher_1: csync; raise 14; // 割り込み待ち状態に移行 csync; /* * 割り込み待ちはIVG14の割り込み状態で行う。ここでIVG14に遷移するのは * どのスタックを使うかという問題の解決と,割込みハンドラ内で * のタスクディスパッチの防止という2つの意味がある. * * 割込みを待つ間は,runtsk を NULL(=0)に設定しなければなら * ない.このように設定しないと,割込みハンドラから iget_tid * を呼び出した際の動作が仕様に合致しなくなる. * * 割り込み待ち状態はidleになるため、実際には割り込みではなくwakeup * イベントが捕捉され、それに伴う割り込みを処理することになる。 * したがって SIC_IMASKとSIC_IWRは同じにしておかなければならない。 * これはアプリケーションプログラマの責任で行う。 */ p1.L = _reqflg; p1.H = _reqflg; r0 = [p1]; // reqflag取得 cc = r0; if !cc jump dispatcher_1; // reqflgが0なら割り込み待ち r0 = 0; [p1] = r0; // reqflgをクリア jump dispatcher; dispatcher.end:
これは初めて実行するタスクのためのエントリポイントです。
_activate_r: r0 = 0xffff(z); sti r0; // CPU アンロック p0 = [sp++]; // タスクエントリーを取り出す jump (p0); // タスクの開始 _activate_r.end:
この関数はタスクが実行を終了した場合と、カーネルの実行開始時に呼ばれます。この時点で使用されているスタックは単に廃棄されます。
_exit_and_dispatch: // start.asm は kernel_startをCPUロック状態、タスク優先順位で呼ぶ。 // kernel_startはそのままexit_and_dispatchを呼ぶのでここでは何もしなくていい。 jump dispatcher; _exit_and_dispatch.end: