マルチプロセッサ用リアルタイムOSの解説

第12回 act_tskサービスコールの解説

今回は,act_tsk()サービスコールの解説を行います.act_tsk()はASPカーネルでも提供していたサービスコールです.ここでは,FMPカーネルのact_tsk()のコードを読みながら,ASPカーネルとの違いを見ていきます.

※ASPカーネルのact_tsk()のコードについては,「リアルタイムOSの内部構造を見てみよう! 第14回」で解説しています.FMPカーネルのact_tsk()の基本的な処理の流れはASPカーネルと同じですので,そちらを参照してください.

act_tsk処理の概要
対象タスクが休止状態の場合は,対象タスクを実行できる状態にし.割り付けられているプロセッサのレディキューに挿入します.さらに,対象タスクが最高優先順位となった場合はディスパッチを発生させます.
休止状態以外の場合で,起動要求されていなければ,起動要求をキューイングします.
すでに起動要求されていれば,キューイングオーバーエラーとします.

■ASPカーネルとの違い■
(1)ロックの取得

CPU間排他制御のためにロックを取得します.

(2)ディスパッチが必要となった場合の処理 dispatch_request()
対象タスクが自プロセッサ割り付けられている:
ASPカーネルと処理内容はほぼ同じ.ただし,自プロセッサで対象タスクが最高優先順位となった場合には,ロックを解放してから,自プロセッサでディスパッチします.

対象タスクが他プロセッサに割り付けられている:他プロセッサで対象タスクの優先順位が最高になった場合には,CPU間割込みを発生させることで,他プロセッサでディスパッチします.

(3)ロックの解放
(1)で取得したロックを解放します.
図1にact_tskの流れを示します.ASPカーネルとの違う部分は色で示します.



[図1 act_tsk()全体の流れ]

ソースコードを見ていきます.
[kernel/task_manage.c]
282 ER 
283 act_tsk(ID tskid)
284 {
285 TCB *p_tcb;
286 ER ercd;
287 bool_t dspreq = false;
288 PCB *p_pcb;
289
290 LOG_ACT_TSK_ENTER(tskid);
291 CHECK_TSKCTX_UNL();
292 CHECK_TSKID_SELF(tskid);
293
294 t_lock_cpu();
295 p_tcb = get_tcb_self(tskid, get_my_p_pcb());
296 p_pcb = t_acquire_tsk_lock(p_tcb);
297 if (TSTAT_DORMANT(p_tcb->tstat)) {
298 if (make_active(p_tcb)) {
299 dspreq = dispatch_request(p_pcb);
300 }
301 ercd = E_OK;
302 }
303 else if (!(p_tcb->actque)) {
304 p_tcb->actque = true;
305 ercd = E_OK;
306 }
307 else {
308 ercd = E_QOVR;
309 }
310 release_tsk_lock_and_dispatch(p_pcb, dspreq);
311 t_unlock_cpu();
312
313 error_exit:
314 LOG_ACT_TSK_LEAVE(ercd);
315 return(ercd);
316 }


それでは,ソースコードの詳細を見ていきます.

295行目:対象タスクのTCBアドレスを,p_tcbに格納します.
#define get_tcb_self(tskid, my_p_pcb)
((tskid) == TSK_SELF ? (my_p_pcb)->p_runtsk : get_tcb(tskid))

◎ASPカーネルとの違い(ロックの取得)
296行目:対象タスクが割り付けられているプロセッサのタスクロックを取得します.取得に成功すると,p_pcbに対象タスクの割り付けられているPCBアドレスが格納されます.
※ロックの詳細については,後日解説します.

(1)休止状態の場合(297〜302行目)
298行目:make_active()を発行する.

make_active()およびそこから呼び出される関数の処理の概要を説明します.詳細は,「リアルタイムOSの内部構造を見てみよう!第14回」を参照してください.

[make_active()処理概要]
・activate_context()を呼び出す.

[activate_context()の処理概要]
タスクの起動準備(スタックポインタ,プログラムカウンタの設定)を行う
・対象タスクの状態を,実行できる状態へ変更する.
・make_runnable()を呼び出す

[make_runnable()の処理概要]
・該当タスクのTCBを,割り付けられているプロセッサのレディキューに挿入する
・割り付けられているプロセッサの,p_schedtskの更新が必要なら,更新
・戻り値は,ディスパッチできる状態かつ,p_schedtskが更新された場合はtrue
 それ以外は,falseとなります.そしてこの戻り値がそのままmake_active()の戻り値となります.

※p_schedtskの更新が必要な場合
対象タスクが割り付けられているプロセッサで,今まで実行できるタスクがなかったとき,もしくは,対象タスクが割り付けられているプロセッサのp_schedtskの優先度より対象タスクの優先度の方が高い(小さい)場合です.

◎ASPカーネルとの違い(ディスパッチが必要となった場合の処理)
ASPカーネルの場合は,make_active()の戻り値がtrueであれば,ディスパッチしていました.
<ASPカーネルの場合>
70         if (make_active(p_tcb)) {
71 dispatch();
72 }


FMPカーネルの場合は,dispatch_request()を呼び出します.
299行目:make_active()の戻り値がtrueの場合は,dispatch_request()を発行する.
make_active()は,自プロセッサでディスパッチが必要になった場合も,他プロセッサでディスパッチが必要になった場合も両方ともtrueを返します.
このdispatch_request()の戻り値が,dspreqに格納され,この値がtrueの場合は自プロセッサでディスパッチが必要と判断し,310行目のrelease_tsk_lock_and_dispatch(p_pcb, dspreq)でディスパッチします.

[dispatch_request()の処理概要]
対象タスクの割り付けられているプロセッサが自プロセッサの場合は,trueを返します.
対象タスクの割り付けられているプロセッサが他プロセッサの場合は,CPU間割込みを発生させ,falseを返します.
このdispatch_request()の戻り値は,自プロセッサでのディスパッチ必要性の判断に使用されます.よって,対象タスクの割り付けられているプロセッサが他プロセッサの場合は,自プロセッサではディスパッチは発生しません.

[kernel/mp.c]
167 bool_t 
168 dispatch_request(PCB* p_pcb)
169 {
170 if (p_pcb == get_my_p_pcb()) {
171 return(true);
172 }
173 else {
174 target_ipi_raise(p_pcb->prcid);
175 return(false);
176 }
177 }


170行目:自プロセッサと,対象タスクの割り付けられているプロセッサが同じ場合,trueを返す.この値は,自プロセッサでのディスパッチ必要性の判断に使用されます.
173行目:自プロセッサと,対象タスクの割り付けられているプロセッサが異なる場合
174行目:CPU間割込みを発生させる.
175行目:falseを返す.

<<対象タスクが自プロセッサに割り付けられている場合>>
対象タスクの割り付けられているプロセッサが自プロセッサで,対象タスクが最高優先順位となった場合,make_runnable()内で,自プロセッサのp_schedtskを書き換え,ディスパッチが必要となります.dispatch_request()の戻り値trueをdspreqに格納します.
そして,dspreqがtrueなら,release_tsk_lock_and_dispatch()でロックを解放してから,自プロセッサでディスパッチします.
図2に対象タスクが自プロセッサに割り付けられ,対象タスクの起動によって自プロセッサでディスパッチが発生する場合の流れを示します.



[図2 対象タスクが自プロセッサに割り付けられている場合]

<<対象タスクが他プロセッサに割り付けられている場合>>
対象タスクの割り付けられているプロセッサが他プロセッサでかつ対象タスクが最高優先順位となった場合,make_runnable()内で,他プロセッサのp_schedtskを書き換え,ディスパッチが必要となります.しかしそのままでは対象プロセッサで,ディスパッチは発生しません.そこで,dispatch_request()で,CPU間割込みを発生させます.CPU間割込み処理の中で,対象プロセッサのreqflgがtrueとなります.割込み出口処理で,reqflgがtrueであればタスク切り替えが必要であると判断し,ディスパッチャを呼び出します.

CPU間割込み処理
139 void 
140 ipi_handler(void)
141 {
142 PCB *my_p_pcb = get_my_p_pcb();
143
144 target_ipi_clear();
145
155 my_p_pcb->reqflg = true;
156 }


155行目で,reqflgをtrueにしています.
ここで,ディスパッチが必要であることをreqflgに登録します.
その後,割込み出口の処理で,reqflgがtrueなら,ディスパッチャを呼び出します(図3).



[図3 対象タスクが他プロセッサに割り付けられている場合]

※act_tskソースコードに戻ります.

(2)休止状態以外(303〜309行目)
起動要求がキューイングされていない場合
303行目:休止状態でなく,起動要求されていなければ

TCBの起動要求キューイング(actque)メンバは,起動要求されているかどうか返すbool型変数です.FMPカーネルはキューイング回数を1回としていますので,キューイングされているかどうかを,bool型変数で表現します.

304行目:起動要求キューイングをTRUEにします.

起動要求がキューイングされている場合
308行目:すでに,起動要求キューイングされている場合は,エラーコードにE_QOVERを設定します.

◎ASPカーネルとの違い(ロックの解放)
310行目:release_tsk_lock_and_dispatch(p_pcb, dspreq);
296行目で取得した対象タスクが割り付けられているプロセッサのタスクロックを解放し,dspreqがtrueの場合は自プロセッサでディスパッチします.
287行目で,dspreqはfalseに初期化されています.この値がtrueになるのは,対象タスクが休止状態かつ自プロセッサに割り付けられ,起動により最高優先順位となる場合で,ディスパッチが保留状態でない場合です.
以下は,make_active(),make_runnable()の解説です.ASPカーネルと処理内容はほぼ同じです.

288 bool_t 
289 make_active(TCB *p_tcb)
290 {
291 activate_context(p_tcb);
292 p_tcb->tstat = TS_RUNNABLE;
293 LOG_TSKSTAT(p_tcb);
294 return(make_runnable(p_tcb));
295 }


291行目:activate_contextを呼び出す.
スタックポインタとプログラムカウンタを初期化します.
292行目:対象タスクの状態を,実行できる状態に変更する.
294行目:make_runnable()を呼び出す.
対象タスクのTCBをレディキューに挿入します.その結果,ディスパッチが必要になったかどうかを戻り値とします.

make_runnable()
205 bool_t 
206 make_runnable(TCB *p_tcb)
207 {
208 uint_t pri = p_tcb->priority;
209 PCB *p_pcb;
210
211 p_pcb = p_tcb->p_pcb;
212 queue_insert_prev(&((p_pcb->ready_queue)[pri]), &(p_tcb->task_queue));
213 primap_set(p_pcb, pri);
214
215 if (p_pcb->p_schedtsk == (TCB *) NULL
216 || pri < p_pcb->p_schedtsk->priority) {
217 p_pcb->p_schedtsk = p_tcb;
218 return(p_pcb->dspflg);
219 }
220 return(false);
221 }


212行目:該当タスクのTCBをレディキューに挿入する
215〜219行目:p_schedtskの更新が必要なら,更新

※p_schedtskの更新が必要な場合
対象タスクが割り付けられているプロセッサで,今まで実行できるタスクがなかったとき,もしくは,対象タスクが割り付けられているプロセッサのp_schedtskの優先度より対象タスクの優先度の方が高い(小さい)場合です.

218行目:p_schedtskを更新した場合は,dspflgを戻り値とする.
219行目:p_schedtskを更新しなかった場合は,falseを戻り値とする.

つまり,この関数の戻り値は,ディスパッチできる状態かつ,p_schedtskが更新された場合はtrue,それ以外は,falseとなります.そしてこの戻り値がそのままmake_active()の戻り値となります.

◎ASPカーネルとの違い
ASPカーネルでは,システム全体でレディキューやp_schedtskは1つしかありませんが,FMPカーネルではプロセッサの数分存在します.そしてこれらは,PCB内のメンバとして管理されています(第9回参照).たとえばp_schedtskには,PCBを介して,p_pcb->p_schedtskというようにアクセスします.