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

第22回 ASPカーネル新機能の紹介<優先度データキュー>

前回に引き続き,ASPカーネルの新機能の紹介を行います.今回は新機能「優先度データキュー」の紹介を行います.優先度データキューというのは,メッセージに優先度をつけて送ることができる機能です.データキューとメールボックスが融合した機能と考えてください.
まず,ASPで新たに優先度データキューが取り入れられた経緯を説明します.
まず,メールボックスという機能があります.これは,データキューと違って,可変長のメッセージを送るための機能です.送るデータが可変長ですので,送り側でメッセージ領域を用意し,そのポインタを送る方式です.このように,送信データ領域は,送り側で準備する仕組みですから,メールボックスはデータキューと違って,フルにならない(送信側が待ち状態にならない)というメリットがあります.また,メッセージに優先度をつけることもできます.
フルにならないというメリットがある反面,危険性もあります.送信側が用意する領域の中に,カーネルがリンクに用いるための領域があります.つまりアプリケーションエンジニアが,メールボックス機能を使用する際には,カーネルが使用するための領域を確保するソースコードを記述する必要があります.そのアプリケーションが用意した領域をカーネルがリングバッファのようにつないでいくといったことをしています.このアプリケーションで確保した先頭の領域をカーネルが使用するので,アプリケーションが誤ってこの部分を書き換えてしまうと,カーネルの中で無限LOOPに陥る可能性があります.

null

【図1 メールボックスにおける領域の使用例】

図1を使って説明します.たとえば,あるメールボックスに3つのメッセージパケットが送信されているとします.カーネル内にはメールボックスの管理領域があります.その管理領域では先頭のメッセージパケット(メッセージパケット 砲離▲疋譽垢函に尾のメッセージパケット(メッセージパケット)のアドレスのみを管理しています.そして図1に示すように,メッセージパケット,離悒奪世砲蓮ぅ瓮奪察璽献僖吋奪鉢△離▲疋譽垢格納され,メッセージパケット,鉢△リンクされています.そして,そのアドレスを格納したのはもちろんカーネルです.同じように,メッセージパケット△鉢がリンクされています.そこへ,新たにメッセージパケットい送信されたとします.すると(FIFOの場合は)今まで末尾だったメッセージパケットのヘッダには,次にい鮴楝海垢襪燭瓩法ぅーネルがメッセージパケットい離▲疋譽垢鬮のヘッダに書き込みます.このように,アプリケーションエンジニアが用意した領域をカーネルが使用します.裏を返せば,このカーネルが使用する領域をアプリケーションが誤って書き換えてしまう可能性が十分あるといえます.
保護機能拡張仕様では,このようなことが発生しては問題ですので,メールボックスの機能を同じ仕様では,そのまま採用できません.そのため,μITRON4.0仕様,保護機能拡張(PX仕様)では,カーネルの用いる管理領域を分離するように,メールボックス仕様を変更しました.つまりアプリケーションで用意した先頭数バイトを使わずに,カーネル内にあらかじめ用意した領域を使うようにしたわけです.しかし,そうすると従来のメールボックスと互換性がなくなります.たとえば,メールボックスがフルになって送信側が待ち状態になることがあります.つまり,snd_mbx(メールボックスの送信サービスコール)の振る舞いが変わってきてしまうのです.
今後メモリ保護機能付きカーネルもASPを拡張する形で作成する予定ですので,ASPカーネルではメールボックスはそのままの仕様で残します(使用は推奨しない)が,優先度付きでメッセージを送ることができる機能を別に作ろう.ということで優先度データキューが追加されました.
ではまず,優先度データキューの仕様をTOPPERS新世代カーネル統合仕様書で確認します.

» Read More

では次に,優先度データキュー待ちの時に構成される構造を図2に示します.詳細は19回を参照してください.

null

【図2 優先度データキュー待ち状態時のデータ構造 

次に,送信されたデータを格納する領域(優先度データキュー管理領域)について解説します.
優先度データキューを生成するためには次の,静的APIを発行します.

CRE_PDQ(優先度データキューID,{優先度データキュー属性,データキュー容量,データ優先度の最大値,バッファ場所})

データキュー容量が3だった場合の優先度データキュー管理領域は以下のようになります.

null

【図3 優先度データキュー管理領域】

このように,1つのデータに対して,次のデータをさす領域,データを格納する領域,優先度を格納する領域の3つの領域が確保されます.この領域がデータ容量分管理されます.
図3の例では,データ容量が3の優先度データキューに対して,優先度が高,低,中の3つのデータが格納されています.この場合,データは優先度順に管理されますので,次のデータをさす領域を使って,優先度順のキューを形成しています.また,この優先度データキュー管理領域のアドレスは,優先度データキュー初期化ブロック(PDQINIB)のp_pdqmbに格納され,図4のような構造を形成します.



【図4 優先度データキュー待ち状態時のデータ構造◆

では,優先度データキューからの受信,rcv_dtqサービスコールから見ていきます.
優先度データキューの受信には,状況に応じて3つのパターンが考えられます.

パターンA:優先度データキュー管理領域にデータがあり,データを受信する.
パターンB:優先度データキュー管理領域にデータがなく,送信待ちタスクがいる場合,送信タスクから受信する.(このパターンは,データ容量が0の場合に存在します.)
パターンC:優先度データキュー管理領域にデータがなく,送信待ちタスクもない場合,受信待ちとなる.


null

kernel/pridataq.c

391 rcv_pdq(ID pdqid, intptr_t *p_data, PRI *p_datapri)
392 {
393 PDQCB *p_pdqcb;
394 WINFO_PDQ winfo_pdq;
395 BOOL reqdsp;
396 ER ercd;
397
398 LOG_RCV_PDQ_ENTER(pdqid, p_data, p_datapri);
399 CHECK_DISPATCH();
400 CHECK_PDQID(pdqid);
401 p_pdqcb = get_pdqcb(pdqid);
402
403 t_lock_cpu();
404 if (receive_pridata(p_pdqcb, p_data, p_datapri, &reqdsp)) {
405 if (reqdsp) {
406 dispatch();
407 }
408 ercd = E_OK;
409 }
410 else {
411 p_runtsk->tstat = (TS_WAITING | TS_WAIT_RPDQ);
412 make_wait((WINFO *) &winfo_pdq);
413 queue_insert_prev(&(p_pdqcb->rwait_queue), &(p_runtsk->task_queue));
414 winfo_pdq.p_pdqcb = p_pdqcb;
415 LOG_TSKSTAT(p_runtsk);
416 dispatch();
417 ercd = winfo_pdq.winfo.wercd;
418 if (ercd == E_OK) {
419 *p_data = winfo_pdq.data;
420 *p_datapri = winfo_pdq.datapri;
421 }
422 }
423 t_unlock_cpu();
424
425 error_exit:
426 LOG_RCV_PDQ_LEAVE(ercd, *p_data, *p_datapri);
427 return(ercd);
428 }


391行目:rcv_pdqサービスコールの引数で渡された*p_dataに受信したデータ,*p_priに受信したデータの優先度を格納することで,呼び出し元に受信データを渡します.
404行目:receive_pridata()を呼び出します.

183 BOOL
184 receive_pridata(PDQCB *p_pdqcb, intptr_t *p_data,
185 PRI *p_datapri, BOOL *p_reqdsp)
186 {
187 TCB *p_tcb;
188 intptr_t data;
189 PRI datapri;
190
191 if (p_pdqcb->count > 0U) {
192 dequeue_pridata(p_pdqcb, p_data, p_datapri);
193 if (!queue_empty(&(p_pdqcb->swait_queue))) {
194 p_tcb = (TCB *) queue_delete_next(&(p_pdqcb->swait_queue));
195 data = ((WINFO_PDQ *)(p_tcb->p_winfo))->data;
196 datapri = ((WINFO_PDQ *)(p_tcb->p_winfo))->datapri;
197 enqueue_pridata(p_pdqcb, data, datapri);
198 *p_reqdsp = wait_complete(p_tcb);
199 }
200 else {
201 *p_reqdsp = FALSE;
202 }
203 return(TRUE);
204 }
205 else if (!queue_empty(&(p_pdqcb->swait_queue))) {
206 p_tcb = (TCB *) queue_delete_next(&(p_pdqcb->swait_queue));
207 *p_data = ((WINFO_PDQ *)(p_tcb->p_winfo))->data;
208 *p_datapri = ((WINFO_PDQ *)(p_tcb->p_winfo))->datapri;
209 *p_reqdsp = wait_complete(p_tcb);
210 return(TRUE);
211 }
212 else {
213 return(FALSE);
214 }
215 }


191行目:優先度データキュー管理領域内にデータがあるか?
パターンA:優先度データキュー管理領域にデータがあり,データを受信する.
192行目:ある場合は,優先度データキュー管理領域からデータを取り出します.
193行目:送信待ちキューにタスクがいるか?
194行目〜:いる場合は,先頭タスクが送信するデータと優先度を,優先度データキュー管理領域へ格納します.
198行目:該当タスクを送信待ち解除します.その結果ディスパッチが必要であれば,reqdspをTRUEにします.
201行目:送信待ちキューにタスクがいない場合は,reqdspをFALSEにします.
203行目:ここで(優先度データキュー管理領域からデータを取り出せた場合)TRUEを戻り値とします.

205行目:優先度データキュー管理領域内にデータがなく,かつ,送信待ちキューにタスクがいるか?
パターンB:優先度データキュー管理領域にデータがなく,送信待ちタスクがいる場合,送信タスクから受信する.
206行目〜:いる場合は,先頭タスクが送信するデータと優先度を受信します.
209行目:該当タスクを送信待ち解除します.その結果ディスパッチが必要であれば,reqdspをTRUEにします.
210行目:ここで(送信待ちキューからデータを受信した場合)TRUEを戻り値とします.

213行目:それ以外の場合は,FALSEを戻り値とします.

rcv_dtq()に戻ります.
404行目:receive_pridata()の戻り値がTRUE,つまり受信できた場合
405行目:req_dspがTRUEであればディスパッチします.ここでディスパッチが必要な場合は,rcv_pdqを発行したタスクが優先度データキュー管理領域から受信した結果,優先度データキュー管理領域に空きができ送信待ちが解除された場合,もしくは,送信待ちをしていたタスクから直接rcv_pdqを発行したタスクにデータを渡すことにより,送信待ち解除となった場合です.
パターンC:優先度データキュー管理領域にデータがなく,送信待ちタスクもない場合,受信待ちとなる.
410行目:データを受信できず,優先度データキュー受信待ちとなる場合にここに来ます.
411行目:TCBのタスク状態を優先度データキュー受信待ちとします.
412行目:待ち状態へ移行します.
413行目:優先度データキュー受信待ちキューにTCBを挿入します.
414行目:WINFO_PDQとPDQCBを接続します.
416行目:rcv_pdqを発行した現在実行中のタスクが待ち状態になるので,無条件にディスパッチします.

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

418行目:ここで待ち状態を解除になった理由を知ることができます.E_OKであれば,他のタスクがsnd_pdqを発行したことにより,待ち解除になったことがわかります.
419,420行目:データを受信します.

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

図5に,rcv_pdqの処理フローを示します.



【図5 rcv_pdqの処理フロー】
次に,優先度データキューへの送信,snd_dtqのサービスコールについて見ていきます.
優先度データキューの送信には,状況に応じて3つのパターンがあります.

パターンA:優先度データキュー受信待ちタスクがいる場合,受信タスクに送信する.
パターンB:優先度データキュー管理領域にデータがあり,かつ空きがある場合,優先度データキュー管理領域に送信する.
パターンC:優先度データキュー管理領域に空きがなく,かつ受信待ちタスクもいない場合,送信待ちになる.




kernel/pridataq.c

225 snd_pdq(ID pdqid, intptr_t data, PRI datapri)
226 {
227 PDQCB *p_pdqcb;
228 WINFO_PDQ winfo_pdq;
229 BOOL reqdsp;
230 ER ercd;
231
232 LOG_SND_PDQ_ENTER(pdqid, data, datapri);
233 CHECK_DISPATCH();
234 CHECK_PDQID(pdqid);
235 p_pdqcb = get_pdqcb(pdqid);
236 CHECK_PAR(TMIN_DPRI <= datapri && datapri <= p_pdqcb->p_pdqinib->maxdpri);
237
238 t_lock_cpu();
239 if (send_pridata(p_pdqcb, data, datapri, &reqdsp)) {
240 if (reqdsp) {
241 dispatch();
242 }
243 ercd = E_OK;
244 }
245 else {
246 winfo_pdq.data = data;
247 winfo_pdq.datapri = datapri;
248 p_runtsk->tstat = (TS_WAITING | TS_WAIT_SPDQ);
249 wobj_make_wait((WOBJCB *) p_pdqcb, (WINFO_WOBJ *) &winfo_pdq);
250 dispatch();
251 ercd = winfo_pdq.winfo.wercd;
252 }
253 t_unlock_cpu();
254
255 error_exit:
256 LOG_SND_PDQ_LEAVE(ercd);
257 return(ercd);
258 }


239行目:send_pridata()を発行します.

154 BOOL
155 send_pridata(PDQCB *p_pdqcb, intptr_t data, PRI datapri, BOOL *p_reqdsp)
156 {
157 TCB *p_tcb;
158
159 if (!queue_empty(&(p_pdqcb->rwait_queue))) {
160 p_tcb = (TCB *) queue_delete_next(&(p_pdqcb->rwait_queue));
161 ((WINFO_PDQ *)(p_tcb->p_winfo))->data = data;
162 ((WINFO_PDQ *)(p_tcb->p_winfo))->datapri = datapri;
163 *p_reqdsp = wait_complete(p_tcb);
164 return(TRUE);
165 }
166 else if (p_pdqcb->count < p_pdqcb->p_pdqinib->pdqcnt) {
167 enqueue_pridata(p_pdqcb, data, datapri);
168 *p_reqdsp = FALSE;
169 return(TRUE);
170 }
171 else {
172 return(FALSE);
173 }
174 }


159行目:優先度データキュー受信待ちキューにタスクがいるか?
パターンA:優先度データキュー受信待ちタスクがいる場合,受信タスクに送信する.
160行目:いる場合,受信待ちキューの先頭タスクのWINFO_PDQにデータと優先度を格納する.
163行目:待ち状態を解除し,その結果ディスパッチが必要であれば,reqdspをTRUEにします.
164行目:戻り値をTRUEとします.

166行目:現在のデータ数が,優先度データキューの容量より小さいか?つまり,優先度データキュー管理領域に空きがあれば,TRUEとなります.
パターンB:優先度データキュー管理領域にデータがあり,かつ空きがある場合,優先度データキュー管理領域に送信する.
167行目:優先度データキュー管理領域にデータを優先度順に挿入します.
168行目:待ち解除になったタスクはないので,ディスパッチは必要ないため,reqdspはFALSEにします.
169行目:戻り値をTRUEにします.

171行目;その他の場合は,戻り値をFALSEにします.

snd_pdq()に戻ります.
239行目:send_pridata()の戻り値は,送信できた場合はTRUE,できなかった場合はFALSEとなります.
240行目:送信できた場合(send_pridataの戻り値がTRUE),reqdspがTRUEであればディスパッチします.
243行目:エラーコードをE_OKとします.

パターンC:優先度データキュー管理領域に空きがなく,かつ受信待ちタスクもいない場合,送信待ちになる.
245行目;送信できなかった場合(send_pridataの戻り値がFALSE),優先度データキュー送信待ちとします.
246〜行目:データと優先度を,WINFO_PRIに格納し,TCBのタスク状態を優先度データキュー送信待ちとします.
249行目:
同期・通信オブジェクト待ちにする共通関数wobj_make_wait()を呼び出します.
wobj_make_wait()では,レディキューから該当タスクを外し,p_schedtskを更新します.そして,図2の各構造体同士のリンク構造を形成します.(第19回で,セマフォの例で解説しています.)

250行目:
snd_pdqを発行した実行中のタスクが優先度データキュー送信待ちになるので,無条件にディスパッチします.

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

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

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



【図6 snd_pdqの処理フロー】