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

第20回 サービスコールのコード解説(wai_sem,sig_sem)

昨年冬から連載してきましたこのブログも,今回で予定していた内容は終了です.今後は不定期に,補足的な内容を更新していきます.


前回は,セマフォ待ちのときに形成するデータ構造(図1)と,それを扱う関数について解説しました.今回は,その構造を使用するセマフォ関連のサービスコールについて解説します.


19-1.jpg


【図1 セマフォ待ち状態時のデータ構造】
まず,セマフォ関係のサービスコールの仕様をμITRON4.0仕様書で確認します.

<セマフォ資源の獲得>
wai_sem 
pol_sem (ポーリング)
twai_sem(タイムアウトあり)

【C言語API】
ER ercd = wai_sem(ID semid);
ER ercd = pol_sem(ID semid);
ER ercd = twai_sem(ID semid, TMO tmout);

【パラメータ】
ID semid 資源獲得対象のセマフォID番号
TMO tmout タイムアウト指定(twai_semのみ)


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


【エラーコード】
E_ID 不正ID番号(semidが不正あるいは使用できない)
E_NOEXS オブジェクト未生成(対象セマフォが未登録)
E_PAR パラメータエラー(tmoutが不正;twai_semのみ)
E_RLWAI 待ち状態の強制解除(待ち状態の間にrel_waiを受付;pol_sem以外)
E_TMOUT ポーリング失敗またはタイムアウト(wai_sem以外)
E_DLT 待ちオブジェクトの削除(待ち状態の間に対象セマフォが削除;pol_sem以外)


【機能】
semidで指定されるセマフォから資源を1つ獲得する.具体的には,対象セマフォの資源数が1以上の場合には,セマフォの資源数から1を減じ,自タスクを待ち状態とせずにサービスコールの処理を終了する.対象セマフォの資源数が0の場合には,自タスクを待ち行列につなぎ,セマフォ資源の獲得待ち状態に移行させる.この時,対象セマフォの資源数は0のまま変化しない.
他のタスクがすでに待ち行列につながっている場合,自タスクを待ち行列につなぐ処理は以下のように行う.セマフォ属性にTA_TFIFO(=0x00)が指定されている場合には,自タスクを待ち行列の末尾につなぐ.TA_TPRI(=0x01)が指定されている場合には,自タスクを優先度順で待ち行列につなぐ.同じ優先度のタスクの中では,最後につなぐ.
pol_semはwai_semの処理をポーリングで行うサービスコール,twai_semは,wai_semにタイムアウトの機能を付け加えたサービスコールである.tmoutには,正の値のタイムアウト時間に加えて,TMO_POL(=0)とTMO_FEVR(=-1)を指定することができる.

<セマフォ資源の返却>
sig_sem
isig_sem

【C言語API】
ER ercd = sig_sem(ID semid);
ER ercd = isig_sem(ID semid);


【パラメータ】
ID semid 資源返却対象のセマフォID番号

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


【エラーコード】
E_ID 不正ID番号(semidが不正あるいは使用できない)
E_NOEXS オブジェクト未生成(対象セマフォが未登録)
E_QOVR キューイングオーバーフロー(最大資源数を超える返却)


【機能】
semidで指定されるセマフォに対して,資源を1つ返却する.具体的には,対象セマフォに対して資源の獲得を待っているタスクがある場合には,待ち行列の先頭のタスクを待ち解除する.この時,対象セマフォの資源数は変化しない.また,待ち解除されたタスクに対しては,待ち状態に入ったサービスコールの返値としてE_OKを返す.資源の獲得を待っているタスクがない場合には,対象セマフォの資源数に1を加える.
セマフォの資源数に1を加えるとセマフォの最大資源数を超える場合には,E_QOVRエラーを返す.
(以上,μITRON4.0仕様から抜粋)
では,まずwai_semサービスコールのソースコードから見ていきます.

/kernel/semaphore.c
171 ER
172 wai_sem(ID semid)
173 {
174 SEMCB *p_semcb;
175 WINFO_SEM winfo_sem; 
176 ER ercd;
177
178 LOG_WAI_SEM_ENTER(semid);
179 CHECK_DISPATCH();
180 CHECK_SEMID(semid);
181 p_semcb = get_semcb(semid);
182
183 t_lock_cpu();
184 if (p_semcb->semcnt >= 1) { 
185 p_semcb->semcnt -= 1;
186 ercd = E_OK;
187 }
188 else {
189 p_runtsk->tstat = (TS_WAITING | TS_WAIT_SEM);
190 wobj_make_wait((WOBJCB *) p_semcb, (WINFO_WOBJ *) &winfo_sem);
191 dispatch();
192 ercd = winfo_sem.winfo.wercd; 
193 }
194 t_unlock_cpu();
195
196 error_exit:
197 LOG_WAI_SEM_LEAVE(ercd);
198 return(ercd);
199 }



175行目:STEP1
WINFO_SEM領域を確保しています.実行中のタスクが使用しているスタック上に確保されます.
181行目:該当セマフォのセマフォ管理ブロックのアドレスを,p_semcbに格納します.

184行目:STEP2
セマフォ資源数が1以上であれば(セマフォ獲得可能)
セマフォ資源数は,セマフォ管理ブロックのsemcntで管理しています.

185行目:STEP3
セマフォ資源数を-1します.セマフォを獲得できたので,タスクは待ち状態に入らず,wai_semの処理を終了します.

188行目:セマフォ資源数が1未満(セマフォを獲得できない)
189行目:STEP4
実行中のタスクの状態を,セマフォ待ち状態にします.

190行目:STEP5
同期・通信オブジェクト待ちにする共通関数wobj_make_wait()を呼び出します.
wobj_make_wait()では,レディキューから該当タスクを外し,p_schedtskを更新します.そして,図1の各構造体同士のリンク構造を形成します.(第19回を参照してください)

191行目:STEP6
実行中のタスクがセマフォ待ちになるので,無条件にディスパッチします.

192行目:
191行目でディスパッチして,待ち状態になっていたタスクが,実行状態になってこの行を実行します.セマフォ待ちから実行可能状態になるためには,該当セマフォに対して,sig_sem()が発行される.もしくは,待ち状態の強制解除のためのサービスコールrel_wai()が発行されるという2とおりの可能性があります.そこで,sig_sem()の中では,セマフォ待ち情報管理ブロックの待ち解除時のエラーコード(wercd)に,E_OKを,rel_wai()の処理の中では,E_RLWAIを設定します.そうすることで,セマフォ待ち状態から解除した理由を知ることができます.

※クリティカルセクション
183行目:CPUロック状態へ移行
194行目:CPUロック状態の解除
183行目から194行目までを,クリティカルセクションとして,排他的に処理を行うように制御しています.他のタスクや割込み処理によって,状態が変わってしまっては問題である区間をクリティカルセクションとして,t_lock_cpu()とt_unlock_cpu()で囲みます.

図2に,wai_semの処理フローを示します.

null


【図2 wai_semの処理フロー】


次に,セマフォ資源返却のサービスコールsig_semのソースコードを見ていきます.

/kernel/semaphore.c
89 ER
90 sig_sem(ID semid)
91 {
92 SEMCB *p_semcb;
93 TCB *p_tcb;
94 ER ercd;
95
96 LOG_SIG_SEM_ENTER(semid);
97 CHECK_TSKCTX_UNL();
98 CHECK_SEMID(semid);
99 p_semcb = get_semcb(semid);
100
101 t_lock_cpu();
102 if (!queue_empty(&(p_semcb->wait_queue))) { 
103 p_tcb = (TCB *) queue_delete_next(&(p_semcb->wait_queue));
104 if (wait_complete(p_tcb)) { 
105 dispatch();
106 }
107 ercd = E_OK;
108 }
109 else if (p_semcb->semcnt < p_smcb->p_seminib->maxsem) { 
110 p_semcb->semcnt += 1;
111 ercd = E_OK;
112 }
113 else {
114 ercd = E_QOVR;
115 }
116 t_unlock_cpu();
117
118 error_exit:
119 LOG_SIG_SEM_LEAVE(ercd);
120 return(ercd);
121 }


102行目:STEP1
該当タスクの待ちキューが空かどうかを確認します.queue_emptyはキューが空であれば,TRUEを返します.

103行目:STEP2
待ちキューが空でなければ,該当セマフォ待ちキューの先頭タスクのTCBをキューから外し,外したタスクのTCBをp_tcbに格納します.
待ちキューが空でないということは,セマフォ資源数が0であることです.この場合は,資源数は0のまま変わりません.

104行目:STEP3 wait_complete()
p_tcbで示すタスクを待ち解除し,その結果,ディスパッチが必要であれば,ディスパッチします.ここでp_tcbで示すタスクは,セマフォ待ち状態から,実行可能状態になりますが,他のタスクの状態によっては実行状態にはなれない可能性があります.
wait_complete()の処理の中で,p_tcbで示すタスクのセマフォ待ち情報管理ブロックのwercdにE_OKを設定します.

109行目:STEP4
該当セマフォのセマフォ資源数が,静的APIで指定した最大資源数かどうかをチェックします.

110行目:STEP5
最大資源数でなければセマフォカウントに+1します.これで,セマフォを返却したことになります.

113,114行目:STEP6
MAXであれば,エラーコード(E_QOVR)を返します.

※クリティカルセクション
101行目:CPUロック状態へ移行
116行目:CPUロック状態の解除
101行目から116行目までを,クリティカルセクションとして,排他的に処理を行うように制御しています.



図3にsig_semの処理フローを示します.




【図3 sig_semの処理フロー】


●isig_semとsig_semの違い

次に,タスクから呼び出すsig_semと非タスクコンテキストから呼び出すisig_semの違いについて見ていきましょう.まず,isig_semのソースコードを示します.

/kernel/semaphore.c
130 ER
131 isig_sem(ID semid)
132 {
133 SEMCB *p_semcb;
134 TCB *p_tcb;
135 ER ercd;
136
137 LOG_ISIG_SEM_ENTER(semid);
138 CHECK_INTCTX_UNL();
139 CHECK_SEMID(semid);
140 p_semcb = get_semcb(semid);
141
142 i_lock_cpu();
143 if (!queue_empty(&(p_semcb->wait_queue))) {
144 p_tcb = (TCB *) queue_delete_next(&(p_semcb->wait_queue));
145 if (wait_complete(p_tcb)) {
146 reqflg = TRUE; 
147 }
148 ercd = E_OK;
149 }
150 else if (p_semcb->semcnt < p_semcb->p_seminib->maxsem) {
151 p_semcb->semcnt += 1;
152 ercd = E_OK;
153 }
154 else {
155 ercd = E_QOVR;
156 }
157 i_unlock_cpu();
158
159 error_exit:
160 LOG_ISIG_SEM_LEAVE(ercd);
161 return(ercd);
162 }


isig_semは非タスクコンテキストから呼び出されるサービスコールです.sig_semとほとんど処理内容は変わりません.sig_semとの違いは,ディスパッチの代わりに,reqflgをTRUEにすることです(146行目,図4).割込み処理中はディスパッチが必要となっても保留されます(遅延ディスパッチ).isig_semを発行した割込みハンドラの処理終了後,カーネルで行われる割込み出口処理で,reqflgがTRUEの場合はディスパッチが行われます.




【図4 isig_semとsig_semの違い】


●pol_semとwai_semとの違い

pol_semはwai_semの処理をポーリングで行うサービスコールです.つまり,セマフォを獲得できない場合は,待ち状態とならずにエラーを返します.
pol_semのソースコードを示します.

208 ER
209 pol_sem(ID semid)
210 {
211 SEMCB *p_semcb;
212 ER ercd;
213
214 LOG_POL_SEM_ENTER(semid);
215 CHECK_TSKCTX_UNL();
216 CHECK_SEMID(semid);
217 p_semcb = get_semcb(semid);
218
219 t_lock_cpu();
220 if (p_semcb->semcnt >= 1) {
221 p_semcb->semcnt -= 1;
222 ercd = E_OK;
223 }
224 else {
225 ercd = E_TMOUT; 
226 }
227 t_unlock_cpu();
228
229 error_exit:
230 LOG_POL_SEM_LEAVE(ercd);
231 return(ercd);
232 }



wai_semの処理の中で,セマフォを獲得できなかった時に待ち状態へ移行する処理,STEP4〜STEP6の処理の代わりに,タイムアウトエラーとします.待ち状態へ移行しません.
セマフォが獲得できる場合の処理は,wai_semと同じです.




【図5 pol_semnとwai_semの違い】


●twai_semとwai_semとの違い

twai_semは,wai_semの処理をタイムアウトつきで行うサービスコールです.セマフォを獲得できなかった時に,待ち状態に入る際,タイムアウトするための処理が追加されています.
twai_semのソースコードを示します.

241 ER
242 twai_sem(ID semid, TMO tmout)
243 {
244 SEMCB *p_semcb;
245 WINFO_SEM winfo_sem;
246 TMEVTB tmevtb;
247 ER ercd;
248
249 LOG_TWAI_SEM_ENTER(semid, tmout);
250 CHECK_DISPATCH();
251 CHECK_SEMID(semid);
252 CHECK_TMOUT(tmout);
253 p_semcb = get_semcb(semid);
254
255 t_lock_cpu();
256 if (p_semcb->semcnt >= 1) {
257 p_semcb->semcnt -= 1;
258 ercd = E_OK;
259 }
260 else if (tmout == TMO_POL) {
261 ercd = E_TMOUT; 
262 }
263 else {
264 p_runtsk->tstat = (TS_WAITING | TS_WAIT_SEM);
265 wobj_make_wait_tmout((WOBJCB *) p_semcb, (WINFO_WOBJ *) &winfo_sem,
266 &tmevtb, tmout);
267 dispatch();
268 ercd = winfo_sem.winfo.wercd;
269 }
270 t_unlock_cpu();
271
272 error_exit:
273 LOG_TWAI_SEM_LEAVE(ercd);
274 return(ercd);
275 }


wai_semとの違いは,265行目でwobj_make_wait()の代わりに,wobj_make_wait_tmout()を呼び出していることです.では,wobj_make_wait_tmout()のソースコードを以下に示します.

207 void
208 wobj_make_wait_tmout(WOBJCB *p_wobjcb, WINFO_WOBJ *p_winfo_wobj,
209 TMEVTB *p_tmevtb, TMO tmout)
210 {
211 make_wait_tmout((WINFO *) p_winfo_wobj, p_tmevtb, tmout);
212 wobj_queue_insert(p_wobjcb);
213 p_winfo_wobj->p_wobjcb = p_wobjcb;
214 LOG_TSKSTAT(p_runtsk);
215 }


wobj_make_wait()との違いは,make_wait()の代わりに,make_wait_tmout()を呼び出していることです.make_wait_tmout()では,待ち情報管理ブロックとタイムイベントブロックを接続します.そして,タイムイベントブロックにコールバック関数と引数を登録します.そのタイムイベントブロックを,タイムイベントヒープの該当箇所に入れることで,指定した時間にタイムアウトします.(第18回を参照してください.)

図6にtwai_semの処理フローを示します.



【図6 twai_semnとwai_semの違い】