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

第18回 セマフォ資源の返却sig_semサービスコールの解説

今回は,セマフォ資源の返却sig_semサービスコールの解説をします.このAPIはASPカーネルでも提供されています.(リアルタイムOSの内部構造を見てみよう!第20回)
また,FMPカーネルのセマフォデータ構造については,第17回で解説しています.
[kernel/ semaphore.c]
155 ER 
156 sig_sem(ID semid)
157 {
158 SEMCB *p_semcb;
159 TCB *p_tcb;
160 ER ercd;
161 bool_t dspreq = false;
162 PCB *p_pcb;
163
164 LOG_SIG_SEM_ENTER(semid);
165 CHECK_TSKCTX_UNL();
166 CHECK_SEMID(semid);
167 p_semcb = get_semcb(semid);
168
169 t_lock_cpu();
170 retry:
171 t_acquire_obj_lock(&GET_OBJLOCK(p_semcb));
172 if (!queue_empty(&(p_semcb->wait_queue))) {
173 p_tcb = (TCB *) (p_semcb->wait_queue.p_next);
174 /* この間は繰り返し実行されるため,非破壊コードでなければならない.*/
175 if ((p_pcb = t_acquire_nested_tsk_lock(p_tcb, &GET_OBJLOCK(p_semcb))) == NULL){
176 goto retry;
177 }
178 queue_delete((QUEUE *) p_tcb);
179 if (wait_complete(p_tcb)) {
180 dspreq = dispatch_request(p_pcb);
181 }
182 release_nested_tsk_lock(p_pcb);
183 ercd = E_OK;
184 }
185 else if (p_semcb->semcnt < p_semcb->p_seminib->maxsem) {
186 p_semcb->semcnt += 1;
187 ercd = E_OK;
188 }
189 else {
190 ercd = E_QOVR;
191 }
192 release_obj_lock_and_dispatch(&GET_OBJLOCK(p_semcb), dspreq);
193 t_unlock_cpu();
194
195 error_exit:
196 LOG_SIG_SEM_LEAVE(ercd);
197 return(ercd);
198 }


全体の流れを図1に示します.



[図1 sig_sem全体の流れ]

167行目:獲得するセマフォIDから,セマフォ管理ブロックのアドレスをp_semcbに格納する.
169行目:CPUロック状態とし,割込み禁止にする.
171行目:オブジェクトロックを取得する.

(a)該当セマフォを待っているタスクがある場合
172行目:セマフォ管理ブロックのセマフォ待ちキューを参照し,待っているタスクがいるか確認する.
173行目:セマフォ待ちキューの先頭のタスクのTCBを取得する.このタスクを待ち解除対象とする.
175行目:待ち解除するタスクのタスクロック(2段目)をt_acquire_nested_tsk_lock()により取得する.

t_acquire_nested_tsk_lock():2段目のロックとして,他タスクのタスクロックを取得する.
引数に1段目に取得しているセマフォのオブジェクトロック変数のアドレスを渡します.詳細は後述します.
  t_acquire_nested_tsk_lock()の戻り値がNULLとなる場合
   ・ロック取得中に割込みが入り,マイグレーションした可能性がある場合
※t_acquire_nested_tsk_lock()の詳細については,後述します.

ここで,タスクロックの取得に失敗する(戻り値がNULL)と,171行目のオブジェクトロックの取得からやり直します.よって,2段目のロックの取得に成功するまで, 171行目から175行目を何度も実行する可能性があります.よって,この間は非破壊コードである必要があります.
※非破壊コードとは,変数(メモリ)の値を変更することがないコードのことをいいます

178行目:待ち解除対象タスクを,セマフォ待ちキューから外します.
179行目:wait_complete()を発行し,待ち解除します.
※wait_complete()の詳細は後述します.

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

180行目:この行を通る時は,いずれかのプロセッサでディスパッチが必要な場合です.dispatch_request()を発行します.

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

182行目:タスクロック(2段目)を解放します.
183行目:戻り値にE_OKを指定します.

(b)該当セマフォを待っているタスクがなく,現在のセマフォカウント値が最大値より小さい場合
186行目:現在のセマフォカウント値を1増やす.
187行目:戻り値に,E_OKを指定する.

(c)それ以外
190行目:戻り値に,E_QOVR(キューイングオーバー)を指定する.

192行目:オブジェクトロックの解放と,dspreqがtrueならディスパッチを行う.
161行目でdspreqはfalseに初期化されています.dspreqがtrueとなるのは,待ち解除した結果,自プロセッサでディスパッチが必要となる場合です.

193行目:CPUロック状態を解除する.

<引数>
待ち解除するタスクのTCBアドレス(p_tcb)
<戻り値>
ディスパッチが必要かどうか
<機能>
p_tcbで指定されるタスクを,待ち解除するようタスク状態を更新します.待ち解除するタスクが実行できる状態になる場合は,レディキューにつなぎます.また,ディスパッチが必要な場合にはtrueを返します.

[kernel/ wait.c]
124 bool_t 
125 wait_complete(TCB *p_tcb)
126 {
127 wait_dequeue_tmevtb(p_tcb);
128 p_tcb->wercd = E_OK;
129 return(make_non_wait(p_tcb));
130 }


127行目:時間待ちのためのタイムイベントブロックが登録されていれば,それを登録解除する
128行目:セマフォ待ちを解除したタスクのTCBのwercdにE_OKを格納する.
129行目:make_non_wait()を発行する.

[kernel/ wait.c]
84 Inline bool_t 
85 make_non_wait(TCB *p_tcb)
86 {
87 assert(TSTAT_WAITING(p_tcb->tstat));
88
89 #if TTYPE_KLOCK != G_KLOCK
90 /* 優先度変更フラグチェック */
91 if (p_tcb->pend_chgpri) {
92 p_tcb->priority = p_tcb->pend_newpri;
93 /* 優先度変更フラグのクリア */
94 p_tcb->pend_chgpri = false;
95 }
96
97 /* タスク強制待ち解除保留クリア */
98 p_tcb->pend_relwai = false;
99 #endif /* TTYPE_KLOCK != G_KLOCK */
100
101 if (!TSTAT_SUSPENDED(p_tcb->tstat)) {
102 /*
103 * 待ち状態から実行できる状態への遷移
104 */
105 p_tcb->tstat = TS_RUNNABLE;
106 LOG_TSKSTAT(p_tcb);
107 return(make_runnable(p_tcb));
108 }
109 else {
110 /*
111 * 二重待ち状態から強制待ち状態への遷移
112 */
113 p_tcb->tstat = TS_SUSPENDED;
114 LOG_TSKSTAT(p_tcb);
115 return(false);
116 }
117 }


89〜99行目:ジャイアントロック方式以外を採用した場合の,デッドロック回避のためのコードです.第14回で解説しましたが,2つロックを取得する場合は,オブジェクトロック→タスクロックの順に取得することを原則としました.しかし,サービスコールによってはタスクロック→オブジェクトロックの順に取得する必要のあるものもあります.そのようなサービスコールにはデッドロック回避のための仕組みが採用されています.そのための対処です.詳細は後日解説します.

二重待ち状態でない場合(101〜108行目)
105行目:タスクの状態を,実行できる状態とする.
107行目:make_runnableを発行する.

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

二重待ち状態の場合(109〜116行目)
113行目:タスクの状態を,強制待ち状態にする.
115行目:戻り値をfalseとする.
→make_non_wait(),wait_complete()の戻り値となります
たとえば,セマフォ資源の返却時に,該当セマフォで待っている他タスクをセマフォ待ち解除する際に,待ち解除するタスクのタスクロックを取得するために使用します(175行目).実行状態のタスクが他タスクのタスクロックを取得します.

<関数名>
t_acquire_nested_tsk_lock()
<引数>
タスクのTCBアドレス
1段目に取得しているロック変数のアドレス
<戻り値>
PCB:取得成功
NULL:ロック取得中に割込みが入り,マイグレーションした可能性がある場合

・プロセッサロック方式,細粒度ロック方式
375 PCB* 
376 t_acquire_nested_tsk_lock(TCB *p_tcb, LOCK *p_objlock)
377 {
378 PCB *my_p_pcb;
379 PCB *p_pcb;
380
381 while(true) {
382 my_p_pcb = get_my_p_pcb();
383 my_p_pcb->p_firstlock = p_objlock;
384 p_pcb = p_tcb->p_pcb;
385 if (t_acquire_nested_lock(&(p_pcb->tsk_lock))) {
386 return(NULL);
387 }
388 my_p_pcb->p_firstlock = NULL;
389 if (p_pcb != p_tcb->p_pcb) {
390 /* 対象タスクがマイグレートした場合 */
391 x_release_lock(&(p_pcb->tsk_lock));
392 } else {
393 return(p_pcb);
394 }
395 }
396 }


376行目:ロックを取得するタスクのTCBアドレスと,1段目に取得しているロック変数のアドレスを受け取る.
sig_sem()から呼び出された場合は,待ち解除するタスクのTCBアドレスと,該当セマフォのオブジェクトロックのアドレスを受け取ります.
382行目:自プロセッサのPCBアドレスを,my_p_pcbに格納する.
383行目:引数で受け取ったロック変数のアドレスを,取得済みロック管理変数(p_firstlock)へ格納する.
※取得済みロック管理変数(p_firstlock)
割込み要求をチェックできないターゲットが多いため,割込みが入ったかどうかを判断するために使用する変数.詳細は,第15回を参照していください.

385,386行目:2段目のロック取得関数(t_acquire_nested_lock())の戻り値がtrueなら,NULLを返し終了.
※2段目のロック取得関数(t_acquire_nested_lock())の戻り値
true:取得中に割込みが入った場合
false:取得成功
t_acquire_nested_lock()の詳細については,第15回を参照してください.

2段目のロックの取得に成功した場合は処理を続けます.
388行目:取得済みロック管理変数(p_firstlock)をクリアする.
※2段目のロック取得関数(t_acquire_nested_lock())がfalseで返ってきたということは,自プロセッサで割込みが入っていないということです.よって,自タスクを実行するプロセッサが変わることがないため,my_p_pcbは有効です.
389行目:対象タスクがマイグレートした場合
391行目:タスクロックを解放して,381行目から再試行.
393行目:対象タスクがマイグレートしていない場合は,対象タスクのPCBアドレスを戻り値として終了.

参考までに,ジャイアントロック方式の場合のソースコードを以下に掲載します.
[kernel/ mp.h]
200 Inline PCB* 
201 t_acquire_nested_tsk_lock(TCB *p_tcb, LOCK *p_objlock)
202 {
203 return(p_tcb->p_pcb);
204 }