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

第11回 mig_tskサービスコールの解説(後編)

前回は,mig_tskの概要と,前処理について解説しました.今回は,マイグレーション処理について詳しく見ていきます.

150 ER 
151 mig_tsk(ID tskid, ID prcid)
152 {

184 else if (TSTAT_RUNNABLE(p_tcb->tstat)){
185 /* 実行可能状態 */
186 if(p_tcb == my_p_pcb->p_runtsk) {
187 /* 自タスクに対して発行 */
188 if (!(my_p_pcb->dspflg)) {  ※(1)-a
189 /* ディスパッチ禁止中ならエラー */
190 ercd = E_CTX;
191 }
192 else if (t_p_pcb == my_p_pcb) { ※(1)-b
193 /* 同一プロセッサを指定 */
194 /* 優先順位を同一優先度のタスクの中で最低とする */
195 dspreq = set_lowest_precedence(p_tcb, my_p_pcb);
196 ercd = E_OK;
197 }
198 else {  ※(1)-c
199 /* マイグレーション要求を処理 */
200 LOG_TSKMIG(p_tcb, my_p_pcb->prcid, prcid);
201 dispatch_and_migrate(prcid);
202 /* ここに戻ってくる時にはロックは解放されている */
203 ercd = E_OK;
204 t_unlock_cpu();
205 goto error_exit;
206 }
207 }



図1に処理概要を示します.



[図1 実行状態のタスクに対してmig_tskを発行した場合の処理]

対象タスクが実行状態であることを確認します.
184行目:実行できる状態である
186行目:自タスクが実行状態である

(1-a)ディスパッチ禁止中(188〜191行目)
ディスパッチ禁止中であれば,エラーとする.

(1-b) 移動先に同一プロセッサを指定した場合(192〜197行目)
優先順位を同一優先度のタスクの中で最低とする.自プロセッサでディスパッチ発生.
set_lowest_precedence()は,対象タスクをレデイキューの末尾につなぎなおし,ディスパッチ保留フラグ(dspflg)を戻り値とします.つまり,ディスパッチ保留状態でなければ,ディスパッチが発生します.

(1-c) 移動先に他のプロセッサを指定した場合(198〜206行目)
自プロセッサでディスパッチ発生.移動先プロセッサで最高優先度となった場合は,ディスパッチ発生
(例1)プロセッサ1に割り付けられているTASK1_1が,自分自身をプロセッサ2へマイグレーションするmig_tsk(TASK1_1,2)を発行した場合.

201行目:dispatch_and_migrate()の処理内容

・スクラッチレジスタを除くすべてのレジスタをスタックに保存する.
・スタックポインタを自タスク(TASK1_1)のTCBに保存する.
・dispatch_rを,実行再開番地として自タスク(TASK1_1)のTCBに保存する.
・スタックを非タスクコンテキスト用のスタックに切り替える.
・migrate_self()を呼び出す

migrate_self()の処理内容

・移動元のプロセッサ(プロセッサ1)のレディキューから,自タスク(TASK1_1)のTCB外す.:make_non_runnable()
・自タスク(TASK1_1)のTCBのp_pcbに,移動先のプロセッサ(プロセッサ2)のPCBを指定する.
・自タスク(TASK1_1)のTCBを移動先のプロセッサ(プロセッサ2)のレディキューに挿入し,必要であれば,プロセッサ2のp_schedtsk(※1)を書き換える
・自タスク(TASK1_1)が,移動先プロセッサ(プロセッサ2)で最高優先度であればdispatch_request()(詳細は後述)を発行する.
・移動元プロセッサ(プロセッサ1)でディスパッチを行う.

(※1)p_schedtsk:レディキューの中で最高優先順位のタスクのTCBを示すポインタ(次に動くべきタスク)

(ポイント1)シングルプロセッサ用OS(ASP)との違い[dispatch_request()]
ASPではディスパッチが必要な場合は,p_schedtskを書き換えて,dispatch()を呼びました.マルチプロセッサ用OSであるFMPの場合は,自プロセッサでディスパッチを発生させる場合はASPと同じですが,プロセッサをまたぐ処理を行うことでディスパッチが必要となった場合は処理が異なります.自プロセッサから他プロセッサのp_schedtskを書き換えても,書き換えられたプロセッサではディスパッチが必要となったことを知るすべがありません.そこで,dispatch_request()で,CPU間割込みを発生させます(マルチプロセッサ用リアルタイムOSの解説 第3回参照).CPU間割込み処理の中で,プロセッサ2のreqflgがtrueとなります.割込み出口処理で,reqflgがtrueであればタスク切り替えが必要であると判断し,ディスパッチャを呼び出します.そのディスパッチ処理中で,p_schedtskを実行状態(p_runtsk)にする処理を行うことでタスクの切り替えが行われます.(リアルタイムOSの内部構造を見てみよう 第8,10回参照)



[図2 移動先プロセッサでディスパッチが必要となった場合の処理]

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 }


173行目:自プロセッサと,対象タスクの割り付けられているプロセッサが異なる場合
174行目:CPU間割込みを発生させる.[target_ipi_raise()]
→プロセッサ2でCPU間割込み(ipi_handler()が発生する.)

CPU間割込み処理[ipi_handler()]のソースコードを示します.
[kernel/mp.c]
139 void 
140 ipi_handler(void)
141 {
142 PCB *my_p_pcb = get_my_p_pcb();
143
144 target_ipi_clear();
154
155 my_p_pcb->reqflg = true;
156 }
157


155行目:プロセッサ2のreqflgをtrueにする.
この処理によって,割込み出口処理でタスク切り替えが必要であることをカーネルが認識し,ディスパッチャを呼び出しタスク切り替えが行われます.

(ポイント2)dispatch_and_migrate()で非タスクコンテキスト用のスタックに切り替える理由
migrate_self()内で,自タスクが移動先プロセッサで最高優先順位となった場合には,ディスパッチリクエストを出します.
その後,自プロセッサでもディスパッチを行います.自プロセッサでディスパッチが完了する前に,移動先プロセッサでTASK1_1が最高優先順位である場合は,実行状態になるため,2つのプロセッサでTASK1_1が実行している状態になる可能性があります.非タスクコンテキスト用のスタックに切り替えず,TASK1_1のスタックをそのまま使用していると,TASK1_1のスタック領域を2つのプロセッサが使用することになり危険です.そこで,非タスクコンテキスト用のスタックに一時的に切り替えることで,危険を回避しています.



[図3 スタックの使用方法]



[kernel/task_manage.c]
150 ER 
151 mig_tsk(ID tskid, ID prcid)
152 {

184 else if (TSTAT_RUNNABLE(p_tcb->tstat)){

208 else {
209 /* 他タスクの場合 */
210 if (t_p_pcb == my_p_pcb) {  ※(2)-a
211 /* 同一プロセッサを指定 */
212 /*
213 * 優先順位を同一優先度のタスクの中で最低とする.
214 * 対象のタスクは最高優先順位のタスクでないため,タ
215 * スク切り替えは発生しない
216 */
217 (void)set_lowest_precedence(p_tcb, my_p_pcb);
218 ercd = E_OK;
219 }
220 else { ※(2)-b
221 /* 異なるプロセッサを指定 */
222 /* レディーキューから外す */
223 make_non_runnable(p_tcb);
224 /* pcb の書き換え */
225 p_tcb->p_pcb = t_p_pcb;
226 LOG_TSKMIG(p_tcb, my_p_pcb->prcid, prcid);
227 /* 移行先のプロセッサでmake_runnable する*/
228 if (make_runnable(p_tcb)) {
229 dispatch_request(t_p_pcb);
230 }
231 ercd = E_OK;
232 }
233 }
234 }



図4に処理概要を示します.



[図4 実行可能状態のタスクに対してmig_tskを発行した場合の処理]

(2-a) 移動先に同一プロセッサを指定した場合(208〜219行目)
優先順位を同一優先度のタスクの中で最低とする.
(1-b)と同じように,set_lowest_precedence()を呼び,同一優先度で最低優先順位とします.(1-b)と異なる点は,対象タスクは,実行状態ではないため,自プロセッサではディスパッチは発生しません.よって,(1-b)では,set_lowest_precedence()の戻り値として,ディスパッチ保留状態フラグを受け取り,ディスパッチをするかどうかの判断に使用しましたが,ここではディスパッチしないため,戻り値は必要ないので受け取りません.

(2-b) 移動先に他のプロセッサを指定した場合(220〜232行目)
移動先プロセッサで最高優先度となった場合は,ディスパッチ発生
ここでの処理は,(1-c)のmigrate_self()の処理の一部と同等です.
223行目:移動元プロセッサのレディキューから対象タスクを外す.
225行目:対象タスクのTCBのp_pcbに移動先プロセッサのPCBを指定する.
228〜230行目:移動先プロセッサのレディキューに挿入する.移動先プロセッサで最高優先度であればdispatch_request()を発行する.
移動先が自プロセッサでも,他プロセッサでもディスパッチは発生しない.

[kernel/task_manage.c]
150 ER 
151 mig_tsk(ID tskid, ID prcid)
152 {

235 else if (TSTAT_DORMANT(p_tcb->tstat)) {
236 /* 休止状態 */
237 LOG_TSKMIG(p_tcb, my_p_pcb->prcid, prcid);
238 p_tcb->p_pcb = t_p_pcb;
239 ercd = E_OK;
240 }


238行目:TCBのp_pcbに移動先のプロセッサのPCBを指定する.
移動先が自プロセッサでも,他プロセッサでもディスパッチは発生しない.

[kernel/task_manage.c]
150 ER 
151 mig_tsk(ID tskid, ID prcid)
152 {

241 else {
242 /* 待ち状態 */
243 if ((p_tcb->tmevtb).callback == NULL) {
244 /* 時間待ちでない場合 */
245 LOG_TSKMIG(p_tcb, my_p_pcb->prcid, prcid);
246 p_tcb->p_pcb = t_p_pcb;
247 ercd = E_OK;
248 }
249 else {
250 /*
251 * 時間待ちの場合 グローバルタイマ方式 なら必要なし
252 */
253 #ifdef TOPPERS_SYSTIM_LOCAL
254 /* キューから削除 */
255 left_time = tmevtb_dequeue(f_p_pcb->p_tevtcb, &(p_tcb->tmevtb));
256 LOG_TSKMIG(p_tcb, my_p_pcb->prcid, prcid);
257 /* 移動先のプロセッサのキューに挿入 */
258 tmevtb_insert(t_p_pcb->p_tevtcb,&(p_tcb->tmevtb), base_time(t_p_pcb->p_tevtcb) + left_time);
259 #else /* TOPPERS_SYSTIM_GLOBAL */
260 LOG_TSKMIG(p_tcb, my_p_pcb->prcid, prcid);
261 #endif /* TOPPERS_SYSTIM_GLOBAL */
262 p_tcb->p_pcb = t_p_pcb;
263 ercd = E_OK;
264 }
265 }


(4-a)時間待ちでない場合(243〜248行目)
246行目:TCBのp_pcbに移動先のプロセッサのPCBを指定する.

(4-b)時間待ちの場合(249〜264行目)
ローカルタイマの場合,タイムイベントを移動する.(253〜258行目)
(グローバルタイマの場合は,以下の処理は行わない)
ローカルタイマの場合は,プロセッサごとにタイムイベントを管理しています.そこで,対象タスクが時間待ちの場合は,移動元プロセッサのタイムイベントヒープからタイムイベントを削除し,移動先プロセッサのタイムイベントヒープへ挿入します.

262行目:TCBのp_pcbに移動先のプロセッサのPCBを指定する.
[kernel/task_manage.c]
150 ER 
151 mig_tsk(ID tskid, ID prcid)
152 {

266 release_dual_tsk_lock_and_dispatch(f_p_pcb, t_p_pcb, dspreq);
267 t_unlock_cpu();
268
269 error_exit:
270 LOG_MIG_TSK_LEAVE(ercd);
271 return(ercd);
272 }
273


266行目:174行目で取得した2つのタスクロックの解放と,dspreqがtrueの場合は,自プロセッサでディスパッチを行う.

(1-c)以外は266行目の処理を行います.
157行目で,dspreqはfalseに初期化されています.dspreqがtrueの場合は,自プロセッサでディスパッチが発生します.この値がtrueになるのは,(1-b)[自分自身に対して自プロセッサを指定して,mig_tskを発行]の場合で,ディスパッチが保留状態でない場合です.266行目の処理で自プロセッサでディスパッチが発生する可能性があるのは,(1-b)の場合のみです.他の場合は,タスクロックの解放のみを行います.

(1-c)の場合も自プロセッサでディスパッチが発生しますが,自プロセッサと移動先プロセッサの両方でディスパッチが行われる可能性があるので,他の処理とは違うルートを通ります.
202行目で呼び出すdispatch_and_migrate()および,そこから呼び出すmigrate_self()の処理で,移動先プロセッサにCPU間割込みによってディスパッチを発生させ,2つのロックを解放し,かつ自プロセッサでもディスパッチを行います.つまりdispatch_and_migrate()の処理が終了した時点では,ロックの解放と,必要なディスパッチは行われていることになります.よって,(1-c)の場合は,205行目のgotoで,266行目を通らず,error_exitラベルに飛ぶようにしています.

以上がmig_tskサービスコールの解説となります.次回は,act_tskサービスコールの解説を行います.