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

第04回 リアルタイムOSの動作例

いよいよ,カーネルのソースコードを見ていきます.今回はwup_tsk()サービスコールの発行を例にとり,概要を解説します.リアルタイムOSのソースコードの読み方や,処理の流れをつかむことを目的としますので,ソースコード1行1行の解説はせずに,ポイントのみピックアップしています.
今回は,あるタスクが,サービスコールを呼び出すことで,タスク切り替えが必要になった場合について,カーネルのソースコードを見ていきます.
下記に示す,タスクAとタスクBの動きについて解説します.



タスクA,カーネル,タスクBの動きを,【図4-1】で示します.



【図4-1 各タスクとカーネルの処理の流れ】

slp_tsk()発行によって待ち状態のタスクAに対して,タスクBがwup_tsk(タスクA)を発行してから,ディスパッチを行う関数を呼び出す前までの,カーネル内の処理を見ていきます.今回は,大体の流れをつかんでいただくことを目的とします.
wup_tsk(タスクA)が発行されてからの処理の概要を【図4-2】に示します.



【図4-2 wup_tsk(タスクA)発行後のカーネル内の処理の流れ】

実際のソースコードを見ていきますが,今回は図4-2に示した,の流れを見ていきます.
wup_tsk()サービスコールは,起床待ち状態のタスクを,待ち解除するサービスコールです.
実際のソースコードを見ていきましょう.


132 ER
133 wup_tsk(ID tskid)
134 {
135 TCB *p_tcb;
136 ER ercd;
137
138 LOG_WUP_TSK_ENTER(tskid); //ログをとるためのマクロ
139 CHECK_TSKCTX_UNL();  //CPUロック状態でないかどうかをチェック
140 CHECK_TSKID_SELF(tskid);  //タスクIDのチェック
141 p_tcb = get_tcb_self(tskid);  //TCBへのポインタを取り出す ⇒
142
143 t_lock_cpu();  //CPUロック状態へ ⇒
144 if (TSTAT_DORMANT(p_tcb->tstat)) { //そのタスクが休止状態かどうか ⇒
145 ercd = E_OBJ;
146 }
147 else if (TSTAT_WAIT_SLP(p_tcb->tstat)) { //対象タスクが起床待ち状態かどうか ⇒
148 if (wait_complete(p_tcb)) {  //戻り値は,ディスパッチが必要かどうか ⇒
149 dispatch();  //ディスパッチを行う関数 ⇒
150 }
151 ercd = E_OK;
152 }
153 else if (!(p_tcb->wupque)) {
154 p_tcb->wupque = TRUE;
155 ercd = E_OK;
156 }
157 else {
158 ercd = E_QOVR;
159 }
160 t_unlock_cpu();
161
162 error_exit:
163 LOG_WUP_TSK_LEAVE(ercd);
164 return(ercd);
165 }


wup_tsk()は,引数に対象タスク(タスクA)のIDがtskid変数として渡されます.

では,流れを理解する上でポイントとなる部分を解説します.

141行目 対象タスク(タスクA)のTCBポインタを取り出します.→p_tcb(ポインタ)へ格納

143行目CPUロック状態にします.

wup_tsk()サービスコールは,起床待ち状態のタスクを,待ち解除するサービスコールです.対象タスクが,未登録状態や休止状態の時はエラーとなります.今回は,対象タスク(タスクA)が起床待ちなので,起床待ち状態の場合について見ていきます.

144行目〜TCBの中の,タスクの状態を保持しているフィールド(p_tcb->tstat)を参照し,エラーを発行するか,待ち解除するかを判断しています.休止状態のときはエラーにします.

147行目 対象タスク(タスクA)が起床待ち状態か判定します.判定はTRUEです.

148行目 wait_complete(p_tcb)の戻り値をチェックします.→wait_complete()参照
この関数は,ディスパッチが必要かどうかを戻り値とします.

149行目 待ち解除後,ディスパッチが必要なら(wait_complete(p_tcb)の戻り値がTRUEなら),dispatch()を呼びます.dispatch()が呼び出されるとタスクの切り替えが行われます.

また少し話がおおきくなりますので,dispatch()に関しては,後日説明します.
待ち解除する関数


107 wait_complete(TCB *p_tcb)
108 {
109 wait_dequeue_tmevtb(p_tcb);
110 p_tcb->p_winfo->wercd = E_OK; // ⇒
111 return(make_non_wait(p_tcb)); // ⇒


110行目 待ち解除時にエラーコードを入れる場所に,E_OKを入れている.→起床待ちの管理方法については,後日説明します.

111行目 make_non_wait()を呼び出します.→make_non_wait()参照
待ち状態といっても,いろいろな状態があります.待ち状態管理について,後日詳しく説明します.この関数では,対象タスクがどんな待ち状態なのかをチェックして, 二重待ち状態ではなく単なる待ち状態で,今回実行できる状態にしていいかどうかチェックします.


80 Inline BOOL
81 make_non_wait(TCB *p_tcb)
82 {
83 assert(TSTAT_WAITING(p_tcb->tstat)); //待ち状態もしくは,二重待ち状態か ⇒
84
85 if (!TSTAT_SUSPENDED(p_tcb->tstat)) { //二重待ち状態でなければ(つまり待ち状態)⇒
86 /*
87 * 待ち状態から実行できる状態への遷移
88 */
89 return(make_runnable(p_tcb));  // ⇒
90 }
91 else {
92 /*
93 * 二重待ち状態から強制待ち状態への遷移
94 */
95 p_tcb->tstat = TS_SUSPENDED;
96 LOG_TSKSTAT(p_tcb);
97 return(FALSE);
98 }
99 }


83行目 待ち状態か,もしくは,二重待ち状態か,チェックします.

85行目 対象タスク(タスクA)が,強制待ち状態でなく,単なる待ち状態であることを確認して,実行できる状態へ移行します.

89行目 実行できる状態へ移行します.→make_runnable()参照
実行できる状態へ移行します.


232 BOOL
233 make_runnable(TCB *p_tcb)
234 {
235 uint_t pri = p_tcb->priority;
236
237 p_tcb->tstat = TS_RUNNABLE; //対象タスクTCBの現在の状態を「実行できる状態」へ ⇒
238 LOG_TSKSTAT(p_tcb);
239 queue_insert_prev(&(ready_queue[pri]), &(p_tcb->task_queue));
240 primap_set(pri);
241
242 if (p_schedtsk == (TCB *) NULL || pri < p_schedtsk->priority) { //⇒
243 p_schedtsk = p_tcb; //⇒
244 return(dspflg); //⇒
245 }
246 return(FALSE);
247 }


237行目 対象タスク(タスクA)の,TCBの現在の状態(p_tcb->tstat)フィールドを「実行できる状態」にする.
239,240行目 該当タスクをレディキューの該当優先度の末尾に挿入する.→レディキューの構造と,管理方法については,後日詳細は説明します.

Point!
タスクスケジューリングをスムーズにするために,レディキューの中で最高優先順位のタスクを,p_schedtsdk変数で管理しています.


p_schedtsdkを更新する必要があるかどうかを判定します.
タスクAが実行できる状態になった今,最高優先順位のタスク,つまり,p_schedtsdkを更新する必要があるのは,以下の2つの条件のうちのいずれかです.

242行目
1.実行できるタスクが現在ない場合
    p_schedtsk == (TCB *) NULL

2.対象タスク(タスクA)の優先度が,現在の最高優先順位のタスクより高い場合
    pri < p_schedtsk->priority (優先度は,値が小さい方が高い)

243行目 このいずれかの場合は,p_schedtskを,対象タスク(タスクA)を指すようにします.

244行目 対象タスク(タスクA)が最高優先順位になったので(p_schedtsdkが更新されたので),ディスパッチが必要になりました.ここで,return(dspflg)を実行すると,wup_tsk()の,148行目のwait_complete(p_tcb)呼び出しまで順番に戻ります.dspflgはディスパッチ保留状態のときFALSE,ディスパッチできる状態のときはTRUEとなります.この場合は,disflgがTRUEですので,dispatch()が発行されるという流れです.dispatch()関数については,後日解説します.


今回は,wup_tsk()サービスコールを例にとり,リアルタイムOSのソースコードのほんの一部を見てきました.さらに詳しく見るためには,ターゲットのアーキテクチャを知る必要があります.次回はSH3アーキテクチャの解説をします.