TOPPERS Confidential TOPPERSプロジェクト ディスカッションメモ オーバランハンドラに関する設計メモ 作成者: 高田広章(名古屋大学) 最終更新: 2014年9月28日 ○メモの位置付け このドキュメントは,TOPPERS/ASPカーネルのオーバランハンドラ機能拡張に関 する設計メモである. ○データ型と定数の定義 プロセッサ時間を表現するデータ型OVRTIMの定義を,kernel.hに含める. ---------------------------------------- typedef ulong_t OVRTIM; /* プロセッサ時間 */ ---------------------------------------- プロセッサ時間に指定できる最大値は,ターゲット依存部(target_kernel.hま たはそこからインクルードされるファイル)で定義するものとするが,デフォ ルトの定義をkernel.hに含める. ---------------------------------------- #ifndef TMAX_OVRTIM #define TMAX_OVRTIM ULONG_MAX #endif /* TMAX_OVRTIM */ ---------------------------------------- また,オーバランハンドラのデータ型OVRHDRの定義を,kernel.hに含める. ---------------------------------------- typedef void (*OVRHDR)(ID tskid, intptr_t exinf); ---------------------------------------- この他に,オーバランハンドラ機能のサービスコールの宣言と関連する定数の 定義を,kernelに含める. ○用いるハードウェア資源とサポートできない場合の措置 オーバランハンドラを実現するに,タイムティックの割込みを発生させるのと は別のタイマ(以下,これをオーバランタイマと呼ぶ)を用いる.そのため, ターゲットによっては,オーバランハンドラをサポートできない場合も考えら れる.また,オーバランハンドラをサポートすることで,タスク切換えにかか るオーバヘッドが問題になる場合も考えられる. そこで,オーバランハンドラをサポートできる場合には,ターゲット依存部に おいて,TOPPERS_TARGET_SUPPORT_OVRHDRをマクロ定義するものとする. オーバランハンドラ機能拡張のkernel.hでは,TOPPERS_TARGET_SUPPORT_OVRHDR がマクロ定義されていれば,TOPPERS_SUPPORT_OVRHDRを定義する. ---------------------------------------- #ifdef TOPPERS_TARGET_SUPPORT_OVRHDR #define TOPPERS_SUPPORT_OVRHDR /* オーバランハンドラ機能拡張 */ #endif /* TOPPERS_TARGET_SUPPORT_OVRHDR */ ---------------------------------------- オーバランハンドラ機能は,TOPPERS_SUPPORT_OVRHDRが定義されている場合の み組み込む.これにより,オーバランハンドラ機能拡張を使用し,ターゲット 依存部がオーバランハンドラをサポートしている場合のみ,オーバランハンド ラ機能が組み込まれることになる. ○オーバランハンドラに関連するデータ構造 オーバランハンドラを実装するために,TCBに,残りプロセッサ時間を表すフィー ルドleftotmを設ける(task.h). ---------------------------------------- typedef struct task_control_block { ... OVRTIM leftotm; /* 残りプロセッサ時間 */ ... } TCB; ---------------------------------------- オーバランハンドラが動作していない状態の時は,leftotmを0に設定すること とし,make_dormantの中で0に初期化する(task.c). ---------------------------------------- void make_dormant(TCB *p_tcb) { ... p_tcb->leftotm = 0U; ... } ---------------------------------------- オーバランハンドラに対しては,管理ブロックは必要なく,DEF_OVRで定義した 情報を格納した初期化ブロックのみを用意する.初期化ブロックも,単一の要 素で十分であり,配列である必要はない(overrun.h). ---------------------------------------- typedef struct overrun_handler_initialization_block { ATR ovratr; /* オーバランハンドラ属性 */ OVRHDR ovrhdr; /* オーバランハンドラの起動番地 */ } OVRINIB; ---------------------------------------- extern const OVRINIB ovrinib; ---------------------------------------- オーバランタイマが動作中かを示すフラグとして,boot_t型の変数 ovrtimer_flagを用意する(overrun.h,overrun.c). ---------------------------------------- extern boot_t ovrtimer_flag; ---------------------------------------- シングルプロセッサの場合には,オーバランタイマが動作中かは,次の方法で 判別することができる. タスクコンテキストでは,(p_runtsk->leftotm > 0U)の時のみ動作している. 非タスクコンテキストでは,動作していない. そのため,このフラグを用いない実装も可能であるが,マルチプロセッサへの 拡張性やターゲット依存性を下げるために,これを用いる実装としている(実 際,Mac OS Xターゲット依存部では,これを活用している). ○残りプロセッサ時間の保存/復帰処理の内容 ディスパッチャおよび割込み処理/CPU例外処理の出入口で,タスクの残りプロ セッサ時間を保存/復帰する必要がある.具体的には,以下のような処理が必 要である. (a) dispatchへの入口 ovrtimer_flagがtrueであれば(または,p_runtsk->leftotmが0でなければ), オーバランタイマを停止させ,残りプロセッサ時間をp_runtsk->leftotmに格納 する.残りプロセッサ時間が0(またはそれ未満)になっていた場合には, p_runtsk->leftotmに1を格納する. (b) dispatchからの出口 タスク例外処理ルーチンの呼出し前に,p_runtsk->leftotmが0でなければ,残 りプロセッサ時間をp_runtsk->leftotmとしてオーバランタイマを動作開始する. (c) 割込み処理/CPU例外処理の入口 ovrtimer_flagがtrueであれば,オーバランタイマを停止させ,残りプロセッサ 時間をp_runtsk->leftotmに格納する.残りプロセッサ時間が0(またはそれ未 満)になっていた場合には,p_runtsk->leftotmに1を格納する. この処理は,カーネル管理の割込みをすべて禁止した状態で行う必要がある. 割込み/CPU例外発生直後にすべての割込みが禁止されないプロセッサでは,割 込みを禁止した後にこの処理を行う必要がある. (d) 割込み処理/CPU例外処理の出口 タスクコンテキストに戻る場合に,p_runtsk->leftotmが0でなければ,残りプ ロセッサ時間をp_runtsk->leftotmとしてオーバランタイマを動作開始する. (e) タスクの終了時 ovrtimer_flagがtrueであれば(または,p_runtsk->leftotmが0でなければ), オーバランタイマを停止させる.残りプロセッサ時間をp_runtsk->leftotmに格 納する必要はない(make_dormantで0が格納される). (f) タスクの実行開始時 p_runtsk->leftotmが0でなければ,残りプロセッサ時間をp_runtsk->leftotmと してオーバランタイマを動作開始する. ○ターゲット依存部のインタフェース オーバランハンドラ機能のターゲット依存部では,オーバランハンドラ用のタ イマ(以下,オーバランタイマと呼ぶ)を操作するための機能を提供する. まず,次の定数をマクロ定義する. (1) TMAX_OVRTIM プロセッサ時間としてオーバランハンドラ用タイマに設定できる最大の値.単 位はマイクロ秒とする.ターゲット依存部で定義しない場合には,kernel.hで ULONG_MAXに定義する. また,次の5つの関数を用意する. (1) void target_ovrtimer_initialize(intptr_t exinf) オーバランタイマの初期化処理を行う.タイマの動作開始は行わない. この関数は,target_timer.cfg中に記述する静的APIにより,初期化ルーチンと してカーネルに登録することを想定している. (2) void target_ovrtimer_terminate(intptr_t exinf) オーバランタイマの停止処理を行う. この関数は,target_timer.cfg中に記述する静的APIにより,終了処理ルーチン としてカーネルに登録することを想定している. (3) void target_ovrtimer_start(OVRTIM ovrtim) オーバランタイマを,ovrtimで指定した時間が経過したら割込みが発生するよ うに設定し,動作開始する.ovrtimの単位はマイクロ秒とする. (4) OVRTIM target_ovrtimer_stop(void) オーバランタイマを停止し,タイマの残り時間を読み出す.もし残り時間が 0(またはそれ未満)になっていた場合には,1を返す.また,オーバランタイ マからの割込みをクリアする. (5) OVRTIM target_ovrtimer_get_current(void) オーバランタイマの残り時間を読み出す.もし残り時間が0(またはそれ未満) になっていた場合には,0を返す.オーバランタイマからの割込みはクリアしな い. ○残りプロセッサ時間の保存/復帰の実装(ターゲット非依存部) 前記の処理内容の中で,(a)と(c)は,呼出し条件が違うだけで処理内容は同一 であるため,これを実現する関数ovrtimer_stopをターゲット非依存部に設け, ターゲット依存部の該当箇所から呼び出すようにする. ---------------------------------------- void ovrtimer_stop(void) { if (ovrtimer_flag) { assert(p_runtsk->leftotm > 0U); p_runtsk->leftotm = target_ovrtimer_stop(); ovrtimer_flag = false; } } ---------------------------------------- また(b),(d),(f)も,呼出し条件が違うだけで処理内容は同一であるため,これ を実現する関数ovrtimer_startをターゲット非依存部に設け,ターゲット依存 部の該当箇所から呼び出すようにする. ---------------------------------------- void ovrtimer_start(void) { if (p_runtsk->leftotm > 0U) { target_ovrtimer_start(p_runtsk->leftotm); ovrtimer_flag = true; } } ---------------------------------------- (e)は,残りプロセッサ時間をp_runtsk->leftotmに格納する必要はない点で (a),(c)と処理内容が異なるが,p_runtsk->leftotmはmake_dormantで0に初期化 されるため,make_dormantを呼び出す前であれば,ovrtimer_stopを流用するこ とができる.そこで,ext_tskに次の修正を加える(task_manage.c). ---------------------------------------- ER ext_tsk(void) { ... (void) make_non_runnable(p_runtsk); |#ifdef TOPPERS_SUPPORT_OVRHDR | ovrtimer_stop(); |#endif /* TOPPERS_SUPPORT_OVRHDR */ make_dormant(p_runtsk); ... } ---------------------------------------- ○残りプロセッサ時間の保存/復帰の実装(ターゲット依存部) (a) dispatchへの入口 ---------------------------------------- void dispatch(void) { |#ifdef TOPPERS_SUPPORT_OVRHDR | ovrtimer_stop(); /* オーバランタイマの停止 */ |#endif /* TOPPERS_SUPPORT_OVRHDR */ スクラッチレジスタを除くすべてのレジスタをスタックに保存する ... } ---------------------------------------- (b) dispatchからの出口 ---------------------------------------- void dispatch(void) { ... dispatch_r: スクラッチレジスタを除くすべてのレジスタをスタックから復帰する |#ifdef TOPPERS_SUPPORT_OVRHDR | ovrtimer_start(); /* オーバランタイマの動作開始 */ |#endif /* TOPPERS_SUPPORT_OVRHDR */ calltex(); … (*b) } ---------------------------------------- (c) 割込み処理/CPU例外処理の入口 割込み処理/CPU例外処理の入口は次の通りに修正する. ---------------------------------------- void <割込みの出入口処理>(void) { スクラッチレジスタをスタックに保存する if (タスクコンテキストで割込み発生) { |#ifdef TOPPERS_SUPPORT_OVRHDR | (少なくとも)カーネル管理の割込みを禁止した状態にする | ovrtimer_stop(); /* オーバランタイマの停止 */ | (必要なら)元の状態に戻す |#endif /* TOPPERS_SUPPORT_OVRHDR */ スタックを非タスクコンテキスト用のスタックに切り換え, 非タスクコンテキストに切り換える } ... } ---------------------------------------- void (void) { スクラッチレジスタをスタックに保存する if (タスクコンテキストでCPU例外発生) { |#ifdef TOPPERS_SUPPORT_OVRHDR | (少なくとも)カーネル管理の割込みを禁止した状態にする | ovrtimer_stop(); /* オーバランタイマの停止 */ | (必要なら)元の状態に戻す |#endif /* TOPPERS_SUPPORT_OVRHDR */ スタックを非タスクコンテキスト用のスタックに切り換え, 非タスクコンテキストに切り換える } ... } ---------------------------------------- (d) 割込み処理/CPU例外処理の出口 割込み処理/CPU例外処理の出口は次の通りに修正する. ---------------------------------------- void <割込みの出入口処理>(void) { ... ret_int_r: スクラッチレジスタを除くすべてのレジスタをスタックから復帰する } |#ifdef TOPPERS_SUPPORT_OVRHDR | ovrtimer_start(); /* オーバランタイマの動作開始 */ |#endif /* TOPPERS_SUPPORT_OVRHDR */ calltex(); … (*b) } |#ifdef TOPPERS_SUPPORT_OVRHDR | else { | (少なくとも)カーネル管理の割込みを禁止した状態にする | ovrtimer_start(); /* オーバランタイマの動作開始 */ | } |#endif /* TOPPERS_SUPPORT_OVRHDR */ } ... } ---------------------------------------- void (void) { ... ret_exc_r: スクラッチレジスタを除くすべてのレジスタをスタックから復帰する } |#ifdef TOPPERS_SUPPORT_OVRHDR | ovrtimer_start(); /* オーバランタイマの動作開始 */ |#endif /* TOPPERS_SUPPORT_OVRHDR */ calltex(); … (*b) } |#ifdef TOPPERS_SUPPORT_OVRHDR | else { | (少なくとも)カーネル管理の割込みを禁止した状態にする | ovrtimer_start(); /* オーバランタイマの動作開始 */ | } |#endif /* TOPPERS_SUPPORT_OVRHDR */ } CPU例外処理からのリターンで,CPUロック状態/ロック解除状態が CPU例外発生時の状態に戻るように準備する スクラッチレジスタをスタックから復帰する CPU例外処理からのリターン } ---------------------------------------- (e) タスクの終了時 ターゲット非依存部のext_tskで対応した. (f) タスクの実行開始時 ---------------------------------------- void activate_context(TCB *p_tcb) { ... start_r: |#ifdef TOPPERS_SUPPORT_OVRHDR | ovrtimer_start(); /* オーバランタイマの動作開始 */ |#endif /* TOPPERS_SUPPORT_OVRHDR */ CPUロック解除状態にする 自タスク(p_runtsk)の起動番地を,拡張情報をパラメータとして呼び出す ext_tskに分岐する ... (*c) } ---------------------------------------- ○オーバランハンドラの呼出しの実装 オーバランタイマが満了し,割込みが発生した場合には,ターゲット依存部の 割込みハンドラ(または,割込みサービスルーチン)から,ターゲット非依存 部のcall_ovrhdrを呼び出す. ここで,オーバランタイマ割込みハンドラの起動と,sta_ovr/stp_ovrの呼出 しの競合の問題がある.具体例として,オーバランタイマが満了した直後に, 他の高優先度の割込みが発生し,その処理中でオーバランハンドラが再動作開 始(残りプロセッサは更新される)された場合や停止された場合が問題になる. この場合,オーバランタイマ割込みハンドラの中で,オーバランハンドラを呼 び出さないようにすべきである. ターゲット非依存部のcall_ovrhdrの実装は次の通り. ---------------------------------------- void call_ovrhdr(void) { assert(sense_context()); assert(!sense_lock()); assert(ovrinib.ovrhdr != NULL); i_lock_cpu(); if (p_runtsk!= NULL && p_runtsk->leftotm == 1U) { p_runtsk->leftotm = 0U; i_unlock_cpu(); LOG_OVR_ENTER(p_runtsk); ((OVRHDR)(ovrinib.ovrhdr))(TSKID(p_runtsk), p_runtsk->p_tinib->exinf); LOG_OVR_LEAVE(p_runtsk); } else { /* * このルーチンが呼び出される前に,オーバランハンドラの起動が * キャンセルされた場合 */ i_unlock_cpu(); } } ---------------------------------------- p_runtskがNULLの場合を考慮しているのは,スプリアス割込みに対するロバス ト性を確保するためである. オーバランハンドラの呼出し後に,呼出し前の状態(CPUロック,割込み優先度 マスク)に戻さないのは,このルーチンからのリターン後に,割込み出口処理 で元の状態に戻すためである. call_ovrhdrは,割込みハンドラから(または,割込みハンドラとして)呼び出 されるため,このルーチンに来るまでに,ovrtimer_stopが呼ばれている(すな わち,オーバランタイマが停止している). 割込みハンドラの設定は,以下のような静的APIを,target_timer.h中に記述す ることで行うものとする(ターゲットの事情で変更してよい). ---------------------------------------- #ifdef TOPPERS_SUPPORT_OVRHDR ATT_INI({ TA_NULL, 0, target_ovrtimer_initialize }); ATT_TER({ TA_NULL, 0, target_ovrtimer_terminate }); CFG_INT(INTNO_OVRTIMER, { TA_ENAINT | INTATR_OVRTIMER, INTPRI_OVRTIMER }); DEF_INH(INHNO_OVRTIMER, { TA_NULL, target_ovrtimer_handler }); #endif /* TOPPERS_SUPPORT_OVRHDR */ ---------------------------------------- これらの静的API中の,INHNO_OVRTIMER,INTNO_OVRTIMER,INTPRI_OVRTIMER, INTATR_OVRTIMERの4つの定数は,target_timer.h中で定義する. ○オーバヘッドの低減方法 以上で説明した方法では,ターゲット依存部のアセンブリ言語で記述すること を想定したコードから,ターゲット非依存部のovrtimer_startと ovrtimer_stopを呼び出しているが,これらの関数は短いもので,アセンブリ言 語の中に展開した方が効率がよい.これらの関数をアセンブリ言語の中に展開 する場合には,それぞれ,OMIT_OVRTIMER_STARTとOMIT_OVRTIMER_STOPをマクロ 定義する. ○マルチプロセッサ対応カーネルへの対応に関するメモ マルチプロセッサ対応カーネルにおいて,sta_ovr/ista_ovr,stp_ovr/ istp_ovrを,呼び出した処理単位と異なるプロセッサに割り付けられたタスク を対象に発行した場合の実装は工夫を要する. 基本的には,対象タスクが割り付けられたプロセッサに対してプロセッサ間割 込みをかけることによって,対象タスクのオーバランハンドラの動作を開始/ 停止させることが必要であるが,割込みの入口処理で呼び出すovrtimer_stopで, ovrtimer_flagと(p_runtsk->leftotm > 0U)が一貫しなくなるため,工夫を要す るものと思われる. 以上