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

第15回 ロックの詳細(2)ロック取得の理想形と,FMPカーネルでの実装

 

前回に引き続き,ロックの詳細を解説します.
今回は,ロック取得の理想形と,理想形を採用できなかった理由,そして,FMPカーネルでのロックの実装について解説します.
FMPカーネルでのロック取得関数の実装方法の説明の前に,API中でロックを取得する理想系を説明します.
※ロックを2つ取得する必要がある場合は,最初にロックを取得することを1段目,次にロックを取得することを2段目と表現します.

ロック取得関数の理想的な仕様は以下の通りです.

・ロックが取得できれば,falseを返す. 
・ロックが取得できなければ,割込みの発生の有無をチェックし,割込みが発生して入ればtrueを返す.割込みが発生していなければロックの取得を再度試みる.

   以上より,2段ロックを取得する場合の理想的な流れを図4に示します.



[図4 2段ロックを取得する場合の理想的な流れ]

しかし,FMPカーネルがサポートしているターゲットで,低オーバヘッドで割込み要求をチェックできるものは少なく,図4の理想系を適用するのは一般的ではないといえます.

割込み要求のチェックできない場合の代替案
そこで,割込み要求をチェックできないターゲットでは以下の2つから実装方法を選択する必要があります.

(代替案1)
ロックの取得に成功するまでロックの取得を繰り返す.
問題点:割込み応答性が悪くなる.

(代替案2)
1回の試行の結果,ロックが取得できなければtrueを返す.
問題点:2段目のロック取得で失敗すると,図5のように毎回必ず1段目のロックも解放しなくてはいけないので,スループットが悪化する可能性がある.



[図5 (代替案2)]
ロック取得関数を,ターゲット依存部に設けます.共通部に置くロック関数では,ターゲット依存部のロック取得関数によりロックを取得後,必要に応じて,マイグレーションチェック,RUNNABLEチェックを行います.

(5-1)ターゲット依存部に置く,ロック所得関数
ターゲット依存部に,1段目のロックを取得する関数t_acquire_lock()と,2段目以上のロックを取得する関数,t_acquire_nested_lock ()を設置します.

割込み応答性とスループットの確保
(4)で述べたように,FMPカーネルがサポートしているターゲットで,低オーバヘッドで割込み要求をチェックできるものは少ないため,図4のような理想形を採用するのは一般的ではありません.そこで,代替案を検討する必要がありました.しかしそれぞれの案には,割込み応答性が悪くなる,スループットが悪化するという問題点がありました.よって,FMPカーネルでは,割込み応答性とスループットを確保するために,次の方法をとっています.

取得済みロック管理変数を設けて,割込み発生有無の判断に利用する
プロセッサごとに取得したロックを記憶するグローバル変数(取得済みロック管理変数)を用意します.取得済みロック管理変数には,現在取得しているロックを登録します.

<割込みハンドラでの処理>
割込みハンドラの先頭では,取得済みロック管理変数をチェックして,ロックを取得している場合はそのロックを解放し,取得済みロック管理変数をクリアします.

<ロック取得関数での処理>
・1段目のロック取得関数
ロック取得に失敗すると,一旦割込みを許可して,割込み応答性を確保した後,割込み禁止にして,再度ロックの取得を試みます[図6].
・2段目のロック取得関数
1段目のロック(オブジェクトロック)を取得すると,そのポインタを取得済みロック管理変数(p_firstlock)に保存します.2段目のロック取得ルーチンでは,ロックの取得を試み,ロックが取得できればそのまま進みます.ロックが取得できなかった場合は一旦割込みを許可して割込みの応答性を確保します.
割込みの禁止後,取得済みロック変数(p_firstlock)をチェックして,クリアされていれば割込みが入ったと判断し,trueを返します
取得済みロック変数(p_firstlock)がクリアされていなければ,割込みが入っていないと判断し,2段目のロック取得を再試行します. [図7].



[図6 1段目のロック取得関数]



[図7 2段目のロック取得関数] 

(5-2)共通部に置くロック関数
サービスコール発行時に必要なロックの取得パターンに応じて,共通部にロック関数がおかれます.このロック関数は,(4-1)で述べた,ターゲット依存部に置くロック取得関数
を使用してロックを取得し,さらに,必要なチェックを行います.

ロック取得後のマイグレーションチェック
特定のタスクに関連付けられたタスクロックを取得するには,割り付けられているプロセッサのPCBのタスクロック用の変数を用いてタスクロックを取得します.ロックを取得する前に,一旦CPUロック状態としますが,ロックの取得の試行において割込み応答性の確保のために割込みを許可しています.そのため,ロックの取得試行中に割込みが入り,その結果該当タスクがマイグレーションされる可能性があり,取得したPCBへのポインタは,ロック取得後には有効でない場合があります[図8].
そのため,ロックを取得する前のPCBと取得後のPCBが同じかどうかを確認し,もし違っていれば,該当タスクがマイグレーションされているため,取得したタスクロックが有効ではありません.その場合は,ロックを解放しもう一度ロックの取得をはじめから試みる必要があります.
タスクロックの取得には,自タスクのタスクロックを取得する場合と,他タスクのタスクロックを取得する場合があります.自タスクのロックを取得する場合は,自タスクのマイグレーションチェックが必要となります[図8].他タスクのタスクロックを取得する場合は,サービスコール発行元タスク,サービスコール発行先タスクの両方がマイグレーションする可能性がありますが,API処理中では,サービスコール発行先のタスクのマイグレーションチェックのみを行います[図9].



[図8 マイグレーションチェックの必要性]




[図9 他タスクのマイグレーションチェック]

ロック取得後のRUNNABLEチェック
自タスクのタスクロックを取得する場合は,割込み禁止をしてから,ロックを取得するまでの間に他のプロセッサに割り付けられているタスクから,sus_tsk()が発行され,強制待ち状態となる可能性があります.強制待ち状態のままサービスコールの処理を続けると不整合が発生するため,ロックの取得後自タスクの状態がRUNNABLEかチェックする必要があります.RUNNABLE状態でない場合は,ロックを解放し,一旦割込みを受け付けディスパッチを発生させます.

ロックの取得パターンの分類
サービスコールの中でのロックの取得パターンは,以下の5つに分類できます.

’ぐ侫廛蹈札奪気離織好ロックの取得 
プロセッサIDを指定して,プロセッサのタスクロックを取得する.
PCB* t_acquire_tsk_lock_prcid(ID prcid)
PCB* i_acquire_tsk_lock_prcid(ID prcid)
<使用個所例>
mrot_rdq(PRI tskpri, ID prcid) タスクの優先順位の回転(他プロセッサ用)
優先順位を回転するプロセッサをprcidで指定.指定したプロセッサの,タスクロックを取得してから処理を行う.

⊆プロセッサのタスクロックの取得
自プロセッサのタスクロックを取得する.
 
[1段目]
PCB* t_acquire_tsk_lock_self(void)
PCB* i_acquire_tsk_lock_self(void)
<使用個所例>
ER rot_rdq(PRI tskpri); タスクの優先順位の回転(自プロセッサ用)
自プロセッサのタスクロックを取得して,処理を行う.

[2段目]
PCB* t_acquire_nested_tsk_lock_self(LOCK *p_objlock)
<使用個所例>
ER wai_sem(ID semid); セマフォの獲得
まず,オブジェクトロック(1段目)を取得し,セマフォが獲得できなかったときに,自タスクをセマフォ待ち状態へ遷移するために,自プロセッサのタスクロック(2段目)を取得して処理を行う.

Gぐ侫織好のタスクロックの取得 
タスクを指定して,タスクが割り付けられているプロセッサのタスクロックを取得する.
[1段目]
PCB* t_acquire_tsk_lock(TCB *p_tcb)
PCB* i_acquire_tsk_lock(TCB *p_tcb)
<使用個所例>
ER act_tsk(ID tskid);
対象タスクが割り付けられているプロセッサのタスクロックを取得し,処理を行います.

[2段目]
PCB* t_acquire_nested_tsk_lock(TCB *p_tcb, LOCK *p_objlock)
PCB* i_acquire_nested_tsk_lock(TCB *p_tcb, LOCK *p_objlock)
<使用個所例>
ER sig_sem(ID semid); セマフォの返却
まず,オブジェクトロック(1段目)を取得し,返却するセマフォを待っているタスクがある場合は,そのタスクの割り付けられているプロセッサのタスクロック(2段目)を取得し処理を行う.

2個のタスクロックの取得
タスクとプロセッサIDを指定して,タスクが割り付けられているプロセッサと,移動先のプロセッサのタスクロックを取得する.マイグレーション処理時に使用.
[1段目]
void t_acquire_dual_tsk_lock(TCB *p_tcb, ID dstprcid, PCB **pp_srcpcb, PCB **pp_dstpcb)
void i_acquire_dual_tsk_lock(TCB *p_tcb, ID dstprcid, PCB **pp_srcpcb, PCB **pp_dstpcb)
<使用個所例>
ER mig_tsk(ID tskid, ID prcid); タスクの割り付けプロセッサの変更
ER mact_tsk(ID tskid, ID prcid); プロセッサを指定したタスクの起動
対象タスクの割り付けられているプロセッサと,移動先のプロセッサのタスクロックを取得する.

[2段目]
bool_t  t_acquire_nested_dual_tsk_lock(TCB *p_tcb, ID dstprcid, LOCK *p_objlock,PCB **pp_srcpcb, PCB **pp_dstpcb)
<使用個所例>
ER ter_tsk(ID tskid); タスクの終了
対象タスクが,オブジェクト待ち,かつ,起動キューイングしていて,かつ起動プロセッサが他プロセッサの場合,マイグレーションが発生する.オブジェクトロック(1段目)を取得した後,対象タスクが割り付けられているプロセッサと,移動先プロセッサのタスクロック(2段目)を取得する.

ゥブジェクトロックの取得
オブジェクトを指定して,オブジェクトロックを取得する.
void t_acquire_obj_lock(LOCK *p_objlock);
void i_acquire_obj_lock(LOCK *p_objlock);

<使用個所例>
ER wai_sem(ID semid); セマフォの獲得
獲得するセマフォのオブジェクトロックを取得し,処理を行う.