リアルタイムOSの内部構造を見てみよう!

第17回 サービスコールのコード解説(slp_tsk)

外が気持ちいいので,ガーデニングでもしてみようかという気分になりました.そこで,何を植えたいかあげてみました.白もくれん,くちなし,ゆり・・・すると,白くて香りのいい花が好きということに気がつきました.(いただくのは,真紅のバラの花束がうれしいのですけれど.)先週末はまず,白もくれんとオリーブの木を植えました.



今回は,自分自身を起床待ちにするサービスコールslp_tskの解説を行います.サービスコールを発行できるということは,そのタスクは現在実行状態にあります.そして,slp_tskを発行すると,自分自身を起床待ち状態に移行します(図17-1参照).その後,ディスパッチャが起動して,レディキューの中で最高優先度のタスクを実行状態へ移行します.(図17-1の場合,タスクBが次に実行状態となります.)
状態を持たない非タスクコンテキストは,待ち状態になることができませんので,非タスクコンテキストからはこのサービスコールは発行することができません.つまり,islp_tskというサービスコールは存在しません.



【図17-1 slp_tsk発行によるタスクの状態変化】
まず,slp_tsk()の使用をμITRON4.0の仕様書で確認します.

【C言語API】
ER ercd = slp_tsk();
ER ercd = tslp_tsk(TMO tmout);

【パラメータ】
TMO tmout タイムアウト指定(tslp_tskのみ)

【リターンパラメータ】
ER ercd 正常終了(E_OK)またはエラーコード

【エラーコード】
E_PAR パラメータエラー(tmoutが不正;tslp_tskのみ)
E_RLWAI 待ち状態の強制解除(待ち状態の間にrel_waiを受付)
E_TMOUT ポーリング失敗またはタイムアウト(tslp_tskのみ)

【機能】
自タスクを起床待ち状態に移行させます.ただし,自タスクに対する起床要求がキューイングされている場合,具体的には,自タスクの起床要求キューイング数が1以上の場合には,起床要求キューイング数から1を減じ,自タスクを待ち状態に移行させず,そのまま実行を継続します.
tslp_tskは,slp_tskにタイムアウトの機能を付け加えたサービスコールです.tmoutには,正の値のタイムアウト時間に加えて,TMO_POL(=0)と,TMO_FEVR(=-1)を指定することができます.

【補足説明】
このサービスコールは,自タスクに対する起床要求がキューイングされている場合に,自タスクをいったん待ち状態とはしません.そのため,自タスクの優先順位は変化しません.
slp_tskの処理をポーリングで行う専用のサービスコールは用意されていません.(以上,μITORN4.0仕様書より抜粋)

続いて,slp_tskのソースコードを見ていきましょう.

/kernel/task_sync.c
57 ER
58 slp_tsk(void)
59 {
60 WINFO winfo; 
61 ER ercd;
62
63 LOG_SLP_TSK_ENTER();
64 CHECK_DISPATCH(); 
65
66 t_lock_cpu();
67 if (p_runtsk->wupque) { 
68 p_runtsk->wupque = FALSE; 
69 ercd = E_OK;
70 }
71 else {
72 p_runtsk->tstat = (TS_WAITING | TS_WAIT_SLP); 
73 make_wait(&winfo);
74 LOG_TSKSTAT(p_runtsk);
75 dispatch(); 
76 ercd = winfo.wercd;
77 }
78 t_unlock_cpu();
79
80 error_exit:
81 LOG_SLP_TSK_LEAVE(ercd);
82 return(ercd);
83 }



slp_tskの処理概要を,【図17-2】に示します.



【図17-2 slp_tskの概要】

60行目:winfo(待ち情報管理ブロック)をローカル変数として宣言しています.関数のローカル変数は,その関数を呼び出したタスクのスタックに領域が確保され,関数が終了すると解放されます.ですから,winfoはslp_tskを発行したタスクのスタックに確保されます.前回解説しましたが,winfoは待ち状態のときにしか必要ない領域ですのでTCBとは切り離し,ここで初めて領域を確保します.このようにすることで待ち状態に入らない場合はwinfo領域を確保しない仕様ですので,RAM領域の仕様を抑えています.
ここでは,まだ領域が確保されただけで,TCBとは接続されていません.
67行目:該当タスクの起床待ちキューイングがTRUEで,キューイングされている場合
68行目:該当タスクの起床待ちキューイングをFALSEにします.この場合は,該当タスクが実行状態のまま処理を続けます.
69行目:待ち状態とならないので,ここでエラーコードにE_OKを設定します.
71行目:該当タスクが起床待ちキューイングされていない場合
72行目:該当タスクのTCBのtstatを起床待ち状態としています.起床待ち状態は,タスクの状態の待ち状態(TS_WAITING)と,待ち要因の起床待ち(TS_WAIT_SLP)とした2つの定義のORで生成します.(第16回参照)
73行目:make_wait()を呼び出します.後ほど解説します.
75行目:今まで実行状態だったタスクが,待ち状態となるので,必ずディスパッチが必要となります.ここで,ディスパッチャを呼び出します.
76行目:この処理は,起床待ち状態の該当タスクに対して,wup_tskが発行された結果ディスパッチが実行され,該当タスクが実行状態になった場合,すなわちdispatch()からリターンした後に実行されます. wup_tskの処理の中で,該当タスクのwinfoのwercdにE_OKを入れています(第4回wait_complete()で解説済み).ここではその値を取り出しエラーコードとします.

以下の例で,細かく動きを見ていきます.

  

初期状態は,優先度が高いタスクAが実行状態で,タスクBは実行可能状態とします.タスクAがslp_tsk()を発行し,slp_tsk()の処理中,75行目でdispatch()を発行すると,ディスパッチャが呼び出され,タスクBが実行状態となります.
タスクBがwup_tsk(TASKA)を発行し,wup_tsk() の処理中で,起こすタスクAのwinfoのwercdにE_OKを設定しています.その後,dispatch()を発行し,タスクAが実行状態となります.このときタスクAはslp_tskの76行目の処理から再開します.ここで,先ほどwup_tsk()の処理で設定されたwercdを使用しています.(【図17-3】)



【図17-3 エラーコード設定タイミング】

どうして,このエラーコードのwinfo.wercdへの登録をslp_tsk側で行わず,wup_tsk側で行うのかを解説します.起床待ちになっているタスクの待ち解除になる可能性としては,tslp_tskの場合のタイムアウトを除いて,wup_tskが発行されることと,rel_waiが発行されることの2つあります.(【図17-4】)



【図17-4 起床待ちを解除するサービスコール】

※rel_wai(待ち状態の強制解除)サービスコールの概要

ER ercd = rel_wai(ID tskid)

tskidで指定されるタスクが待ち状態にある場合に,強制的に待ち解除を行います.すなわち対象タスクが待ち状態の時は,実行可能状態に,二重待ち状態の時は強制待ち状態へ移行します.

つまり,slp_tskの76行目にくるのは,wup_tskが発行された場合だけでなく,rel_waiが発行された場合にも来る可能性があります.よって,どちらのサービスコールによって実行状態になったか区別をするために,wup_tskの処理の中では,E_OKを設定し,rel_waiの処理の中では,E_RLWAIを設定します.これが,slp_tskの処理中でエラーコードを設定していない理由です.


次にmake_wait()の解説を行います.slp_tskの73行目で呼び出しています.この関数は,待ち状態へ移行するという処理を行います.
/kernel/wait.h
81 Inline void
82 make_wait(WINFO *p_winfo)
83 {
84 (void) make_non_runnable(p_runtsk); 
85 p_runtsk->p_winfo = p_winfo; 
86 p_winfo->p_tmevtb = NULL;
87 }


84行目:make_non_runnable()を呼び出します.maek_non_runnable()はレディキューから該当タスクのTCBを外します.そして,p_schedtskを更新し,ディスパッチが必要かどうかを戻り値とします(第13回 解説済み).しかし,今回は実行状態であったタスクがslp_tskを発行して,起床待ち状態になるので,必ずディスパッチが必要です.よって,make_non_runnable()の戻り値でディスパッチするかどうかの判断をする必要ないのでvoid型でキャストしています.

85行目:slp_tskの中でスタック上に確保した,winfoをTCBと接続します.(図17-5  
86行目:タイムアウトしないので,winfoのp_tmevtbはNULLを設定します.(図17-5 ◆



【図17-5 make_wait概要】

前回の復習になりますが,WINFOの構造についてもう一度見ておきます.
/kernel/task.h
157 typedef union waiting_information {
158 ER wercd; /* 待ち解除時のエラーコード */
159 TMEVTB *p_tmevtb; /* 待ち状態用のタイムイベントブロック */
160 } WINFO;

この構造は,待ち状態になるタスクごとに待ち情報を管理するものです.この構造体で管理する情報は,待ち解除時のエラーコード(wercd)と,待ち状態用のタイムイベントブロックへのポインタ(p_tmevtb)です.wercdには待ち解除後にエラーコードをいれますので,このスペースが必要となるのは待ち解除後です.それに対して,p_tmevtbは待ち解除後には必要のない領域ですので,WINFOはこの2つのメンバを共用体として持つことにより,RAM使用量を抑えています.
make_waitの86行目では,この構造体のp_tmevtbにタイムイベントブロックのポインタを格納します.今回は,タイムアウトしませんので,タイムイベントブロック(TMEVTB)は使用しないためNULLを設定します.このようにTMEVTBの構造をWINFOから分離することで,slp_tskのようにタイムアウトしないサービスコールの場合はRAM使用量を抑えることができます.このように,RAM使用量を抑えるために様々な工夫がされています.

次回は,タイムアウト付きのtslp_tskの解説を行います.