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

第15回 サービスコールのコード解説(ext_tsk chg_pri)

先日,あまおうをいただきました,福岡県産の新しい品種のイチゴです.あまいだけでなく,しっかり酸味もあって,そしてイチゴの青臭さがないのでお菓子のようでした.こちら名古屋では,なかなか普段使いのスーパーではお見かけしません.食べ物の品種もどんどん変わります.そういえば,子どものころ親しんでいたササニシキ(お米),最近見かけなくなりました.



今回も前回に引き続き,サービスコールのコード解説を行います.今回は,自タスクを終了させるext_tsk,,タスクの優先度を変更するchg_priの2つのサーボスコールのコード解説を行います.
ext_tskの仕様を,μITRON4.0の仕様書で確認します.

void ext_tsk();

【パラメータ】
 なし

【リターンパラメータ】
 このサービスコールからはリターンしない

【機能】
 自タスクを終了させます.具体的には,自タスクを実行状態から休止状態に移行させ,タスクの終了時に行うべき処理を行います.自タスクに対する起動要求がキューイングされている場合,具体的には,自タスクの起動要求キューイング数が1の場合には,起動要求キューイング数から1を減じ,自タスクを実行可能状態へ移行させます.この時,タスク起動時に行うべき処理を行います.よって,タスクの起動直後の状態に初期化されます.タスクを起動する際のパラメータとしては,タスクの拡張情報を渡します.再起動されたタスクの優先順位は,同じ優先度を持つタスクの中で最低の優先順位となります.(以上,μITRON4.0仕様書より抜粋)

ext_tskのソースコードを見ていきます.

kernel/task_manage.c
163 ER
164 ext_tsk(void)
165 {
166 ER ercd;
167
168 LOG_EXT_TSK_ENTER();
169 CHECK_TSKCTX();
170
171 if (t_sense_lock()) {
172 /*
173 * 何もしない
176 */
177 }
178 else {
179 t_lock_cpu();
180 }
181 if (disdsp) {
186 disdsp = FALSE;
187 }
188 if (t_get_ipm() != TIPM_ENAALL) {
194 t_set_ipm(TIPM_ENAALL); /* IPMをTIPM_ENAALLに*/
195 }
196 dspflg = TRUE;
197
198 (void) make_non_runnable(p_runtsk);
199 make_dormant(p_runtsk);
200 if (p_runtsk->actque) {
201 p_runtsk->actque = FALSE;
202 (void) make_active(p_runtsk);
203 }
204 exit_and_dispatch(); 
205 assert(0); 
206
207 error_exit:
208 LOG_EXT_TSK_LEAVE(ercd);
209 return(ercd);
210 }



以下の例で解説します.レディキューに,優先度(高)のタスクA,タスクBがこの順番に,優先度(低)のタスクCがつながっています.タスクAは起動要求キューイングされているとします.この状態を初期状態とします.【図15-1】【図15-2】





【図15-1 レディキューの初期状態】



【図15-2 初期状態の状態遷移図】

ext_tskは自分自身を終了するサービスコールです.つまり,自分がサービスコールを発行できる状態,つまり自分自身がp_runtskです.上記例で,タスクAがext_tskを発行したものとします.

本来,ext_tskは,CPUロック状態,ディスパッチ禁止状態,割込み優先度マスクが全解除状態でない時には,呼び出すことはできません.しかし,万が一呼ばれた場合もカーネル内の処理で救済しています.第1回で解説しましたが,ASPカーネルの設計方針の一つに,高信頼性・安全なシステム構築を支援する,というものがあります.μITRONおよび,JSPカーネルでは,アプリケーションが約束事を守らなければ,動作の保証はしません.というスタンスでした.それに対して,ASPカーネルでは,万が一,アプリケーションが約束事に反する処理をしても,妥当なオーバーヘッドで救済できる場合は救済する.という方針にしています.
171〜196行目では,この救済措置を行っています.

171〜180行目:CPUロック状態で呼ばれた場合は,エラーにせず,何もしません,CPUロック状態でない場合はCPUロック状態にします.
181〜187行目:ディスパッチ禁止状態のときは,ディスパッチ許可状態とします.
188行目:割込み優先度マスク全解除状態でない場合は,全解除状態にします.
196行目:ディスパッチできる状態にします.dspflgはディスパッチ可能な状態がTRUEとなります.

198行目:make_non_runnable()を呼びだします.make_non_runnable()の詳細は,第13回を参照してください.
make_non_runnable()では,まず自分自身をレディキューから外します.レディキューの状態は【図15-3】になります.そして,p_schedtskの更新が必要であれば更新します.通常であれば,make_non_runnable()はディスパッチするかどうかを戻り値としますが,ここでは戻り値は使いません.ext_tskが呼ばれたということは,今まで実行状態であった自分自身が休止状態となるわけですから,必ず最終的にはタスク切り替えが必要です.しかし,第8回で解説したようにdispatch()を呼び出すと,まず今まで実行状態であったタスクのコンテキストを保存します.しかし今回の場合は,該当タスク(タスクA)は終了するので,コンテキストを保存する必要がありません.よって,別の方法でディスパッチします.(→204行目)



【図15-3 レディキューの状態 

199行目:タスクAを休止状態へ移行します.make_dormant()については後ほど解説します.タスクAが休止状態になると,状態遷移図は【図15-4】のようになります.



【図15-4 タスクの状態遷移図 

200〜203行目:起動要求がキューイングされている場合は,タスクを起動させます.まずTCBの起動要求キューイングをFALSEにします.次に,make_active()を呼び出して,実行できる状態にします.もう一度レディキューに自分自身をつなぎます.同じ優先度の中では最低の優先順位となりますので,レディキューは【図15-5】になります.make_active()については,第14回を参照してください.




【図15-5 レディキューの状態◆

204行目:タスクを終了してディスパッチします.ディスパッチ後のタスクの状態遷移図は【図15-6】のようになります.



【図15-6 タスクの状態遷移図◆

第8回で解説した,wup_tsk発行によるタスク切り替えの際は,ディスパッチャコア(dispatcher)に入る前に,コンテキストを保存する必要がありました.しかしext_tskでは保存する必要はないので,直接ディスパッチャコアに飛び込みます.(【図15-7】参照)

null

【図15-7 ディスパッチャコアへの入り方】
タスクを休止状態へ移行します.ext_tskからは,自分自身のTCBをレディキューから外した後に呼び出されます.TCBのタスクの状態をTS_DORMANTにすることによって,休止状態にしています.



292 make_dormant(TCB *p_tcb)
293 {
294 p_tcb->tstat = TS_DORMANT;
295 p_tcb->priority = p_tcb->p_tinib->ipriority;
296 p_tcb->wupque = FALSE;
297 p_tcb->enatex = FALSE;
298 p_tcb->texptn = 0U;
299 LOG_TSKSTAT(p_tcb);
300 }


休止時に,初期化できるものはしておきます.この初期化はタスクを休止状態から実行できる状態にするmake_active()の中で行ってもいのですが,起動を早くするというポリシーのもと,ここで行っています.

294行目:TCB中のタスクの状態を休止状態(TS_DORMANT)にします.
295行目:TCB中の現在の優先度を起動時の優先度に設定します.
296行目:TCB中の起床要求キューイングをFALSEにします.
297行目:TCB中のタスク例外処理許可状態をFALSEにします.
298行目:TCB中の保留例外要因を0にします.
chg_priの仕様を,μITRON4.0の仕様書で確認します.

ER ercd = chg_pri(ID tskid,PRI tskpri);

【パラメータ】
ID tskid 変更対象のタスクのID番号
PRI tskpri 変更後のベース優先度

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

【エラーコード】
E_ID 不正ID番号(tskidが不正あるいは使用できない)
E_NOEXS オブジェクト未生成(対象タスクが未登録)
E_PAR パラメータエラー(tskpriが不正)
E_ILUSE サービスコール不正使用(上限優先度の違反)
E_OBJ オブジェクト状態エラー(対象タスクが休止状態)


【機能】
tskidで指定されるタスクのベース優先度を,tskpriで指定される値に変更します.それに伴って,タスクの現在優先度も変更します.
tskidにTSK_SELF( = 0 )が指定されると,自タスクを対象タスクとします.また,tskpriにTPRI_INI( = 0 )が指定されると,対象タスクのベース優先度をタスクの起動時優先度に変更します.
このサービスコールを実行した結果,対象タスクの現在優先度が変化した場合,および現在優先度がベース優先度に一致している場合には次の処理を行います.対象タスクが実行できる状態である場合,タスクの優先順位を変更後の優先度にしたがって変化させます.変更後の優先度と同じ優先度をもつタスクの間では,対象タスクの優先順位を最低とします.対象タスクがなんらかのタスク優先度順の待ち行列につながれている場合も,その待ち行列の中での順序を,変更後の優先度にしたがって変化させます.変更後の優先度と同じ優先度を持つタスクの間では,対象タスクを最後につなぎます.(以上,μITRON4.0仕様書より抜粋)

次に,chg_priのソースコードを見ていきます.

kernel/task_manage.c
266 ER
267 chg_pri(ID tskid, PRI tskpri)
268 {
269 TCB *p_tcb;
270 uint_t newpri;
271 ER ercd;
272
273 LOG_CHG_PRI_ENTER(tskid, tskpri); 
274 CHECK_TSKCTX_UNL();
275 CHECK_TSKID_SELF(tskid);
276 CHECK_TPRI_INI(tskpri);  
277 p_tcb = get_tcb_self(tskid);
278 newpri = (tskpri == TPRI_INI) ? p_tcb->p_tinib->ipriority
279 : INT_PRIORITY(tskpri);
280
281 t_lock_cpu();
282 if (TSTAT_DORMANT(p_tcb->tstat)) { 
283 ercd = E_OBJ;
284 }
285 else {
286 if (change_priority(p_tcb, newpri)) {
287 dispatch();
288 }
289 ercd = E_OK;
290 }
291 t_unlock_cpu();
292
293 error_exit:
294 LOG_CHG_PRI_LEAVE(ercd);
295 return(ercd);
296 }


このサービスコールは第1引数(ID tskid)として,優先度を変更するタスクIDを受け取り,第2引数(PRI tskpri)として変更する優先度を受け取ります.

277行目:第1引数で受け取ったタスクIDから,該当タスクのTCBへのポインタを取得し,p_tcbへ格納します.
278行目:変更する優先度をnewpriに設定します.第2引数で受け取った変更する優先度tskpriが0の場合は,該当タスクのTINIBに設定されている初期優先度をnewpriに設定します.0以外の場合は,第2引数で受け取った値をINT_PRIORITYマクロによって,0オリジンに変更して,newpriに設定します.このnewpriには優先度の内部表現(0〜15)を格納します.アプリケーションエンジニアは,タスクの優先度を1〜16の値で設定します.しかし,カーネルのソースコードでは,第13回で解説したように,レディキューを構成するダブルリンクキュートップは,優先度をインデックスとした配列で管理しています.よって,優先度は0〜15の値で管理します.アプリケーションエンジニアが指定した優先度(1〜16)を,内部表現(0〜15)に変更するのが,INT_PRIORITYマクロです.

282〜284行目:該当タスクが休止状態の場合は,E_OBJエラーとします.休止状態のタスクに,chg_priは発行できません.
285行目:休止状態でない場合は,change_priority()を呼び出します.change_priority()の戻り値はディスパッチが必要かどうかです.change_priority()については,後ほど解説します.
286行目:change_priority()の戻り値がTRUEならば,dispatch()を呼び出します.

続いて,change_priority()のソースコードを見ていきます.

/kernel/task.c
332 BOOL
333 change_priority(TCB *p_tcb, uint_t newpri)
334 {
335 uint_t oldpri;
336
337 oldpri = p_tcb->priority;
338 p_tcb->priority = newpri;
339
340 if (TSTAT_RUNNABLE(p_tcb->tstat)) {
344 queue_delete(&(p_tcb->task_queue));
345 if (queue_empty(&(ready_queue[oldpri]))) {
346 primap_clear(oldpri); 
347 }
348 queue_insert_prev(&(ready_queue[newpri]), &(p_tcb->task_queue));
349 primap_set(newpri);
350
351 if (p_schedtsk == p_tcb) { 
352 if (newpri >= oldpri) { 
353 p_schedtsk = search_schedtsk(); 
354 return(p_schedtsk != p_tcb && dspflg);
355 }
356 }
357 else { 
358 if (newpri < p_schedtsk->priority) { 
359 p_schedtsk = p_tcb;
360 return(dspflg);
361 }
362 }
363 }
364 else {
365 if (TSTAT_WAIT_WOBJCB(p_tcb->tstat)) {
370 wobj_change_priority(((WINFO_WOBJ *)(p_tcb->p_winfo))->p_wobjcb,
371 p_tcb);
372 }
373 }
374 return(FALSE);
375 }


まず,概要を【図15-8】に示します.364行目以降の,タスクが待ち状態の場合についての説明は省略します.



【図15-8 change_priority概要】

337行目:該当タスクの現在の優先度をoldpriとします.
338行目:引数で受け取った変更する優先度(newpri)をTCBの現在の優先度に格納します.

340行目:該当タスクが実行できる状態のとき

レディキューの中での位置を変更します.該当タスクをレディキューから外し,新しい優先度のレディキューの末尾に挿入します.
344行目:まず,該当タスクのTCBをレディキューから外します.
345〜347行目:元の優先度のレディキューが空になったのであれば,該当優先度のビットマップをクリアします.
348行目:変更する優先度のレディキューの末尾に該当タスクのTCBを挿入します.
349行目:変更する優先度のビットマップをセットします.

レディキューのつなぎ換えの結果,最高優先順位のタスクを示す,p_schedtskの更新が必要かどうかをチェックし,必要であればp_schedtskを更新します.

p_schedtskを更新する場合は次の2点です.【図15-8】に示しました.

パターン
p_tcbが最高優先順位のタスクであって,その優先度を下げた(もしくは同じ優先度に変更した)場合

パターン
p_tcbが最高優先順位のタスクではなく,変更後の優先度が最高優先順位のタスクの優先度よりも高い場合


パターン
【図15-9】の例で見ていきます.最高優先順位であるタスク1の優先度を下げます.



【図15-9 パターン 檻院

351行目:該当タスク(タスク1)が今まで最高優先順位だった場合
352行目:変更する優先度が元の優先度より低い場合
353行目:最高優先順位のタスクを検索し(search_schedtsk()),タスク2をp_schedtskとします.
354行目:p_schedtskが該当タスク(タスク1)でなくなり,かつ,ディスパッチできる状態のときはTRUEを返します.


では,パターン,亮,領磴鮓てみましょう.



【図15-10 パターン 州押

この場合は,今まで最高優先順位であったタスク1に対して,同じ優先度に優先度を変更するchg_priが発行された場合です.この場合は,同じ優先度のレディキューの末尾に接続しなおします.よって,最高優先順位のタスクはタスク1でしたが,タスク2に変更になります.


最後に,パターン,領磴鬚發Π譴銚ていきましょう.



【図15-11 パターン 檻魁

この場合は,今まで最高優先順位であったタスク1が優先度を下げました.しかし,変更後もタスク1は最高優先順位のままです.この場合は,タスク切り替えの必要がありません.この場合を考慮して,354行目では,単にdspflgを戻り値とするのではなく,優先度の変更後も該当タスクが最高優先順位のままの場合は,ディスパッチする必要がないので,return(p_schedtsk != p_tcb && dspflg)という記述にしています.

パターン
【図15-11】の例で見ていきます.最高優先順位でないタスク3の優先度を,最高優先順位のタスク1の優先度より高くします.



【図15-11 パターン◆

357行目:該当タスク(タスク3)が今まで最高優先順位でなかった場合
358行目:変更後の優先度の方が,今までの最高優先順位のタスク(タスク1)の優先度より高い場合
359行目:p_schedtskを該当タスク(タスク3)にします.
360行目:ディスパッチできる状態かどうかを戻り値とします.