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

第06回 システム状態とコンテキスト

あけましておめでとうございます.どんなお正月をお過ごしになりましたか?
私は日本のお正月と,ビーチリゾートのNew Yearの両方を満喫してきました.充電完了です!
本年も,どうぞよろしくお願いいたします.


さて,2008年第一弾は,システム状態とコンテキストについて解説していきます.システム状態やコンテキストにどんな状態があって,どうやって管理するかということを設計する必要があります.
特に,呼び出せるサービスコールが違う次の状態管理が重要になります.(第3回 3-4を参照)

1.実行中のコンテキストがタスクなのかタスクでないのか
2.CPUロック状態か,CPUロック解除状態か
3.割込み優先度マスク
4.ディスパッチ許可か,ディスパッチ禁止状態か
5.ディスパッチ保留か,ディスパッチできる状態か


1.〜3.は,ターゲット依存です.
ここからの話は,ターゲットに依存します.ターゲットに依存する部分は,SH3のms7727cp01のボードを対象に解説していきます.
現在の状態が,割込み処理中かどうかハードウェアで管理している場合もありますが,SH3はそういった機能は持たないので,実行中のコンテキストをソフトウェアで管理する必要があります.ソフトウェアで管理するとは,割込みに入るとフラグをTRUE,割込み処理が終わるとフラグをFALSEにするという考え方が基本です.
しかしこの方法ですと,多重割込みの管理ができないので,ネストレベルで管理するという方法をとっています.つまり,0ならタスクコンテキスト(割込みが発生していない),1以上なら非タスクコンテキストとしています.
このネストレベルをSH3の場合どうやって実装しているかといいますと,もちろんグローバル変数での実装でも構わないのですが,効率が悪いので別の方法をとっています.コンテキストの判定は,頻繁に行いますのでできるだけ効率の良い方法を選択する必要があります.SH3の場合は,レジスタバンクがあります.タスクで使わない割込み用のレジスタが8本もあるのでそのうちの1本をつかって,管理しています.(BANK1のr7)
具体的な処理としては,割込みの入り口処理でこのレジスタの値を増やし,割込みの出口処理で減らすという処理を行います.


コンテキストの参照
タスクか非タスクかを判断する処理です.この処理はターゲットによってことなるので,ターゲット依存部に記述します.SH3の場合は,BANK1のr7(=r7_bank)の値が0より大きければ割込みと判断します.

153 Inline BOOL
154 sense_context(void)
155 {
156 uint32_t nest;
157
158 Asm("stc r7_bank,%0" : "=r"(nest)); //r7_bank(BANK1のr7)で判断
159 return(nest > 0U);
160 }


上記関数は,非タスクコンテキストの場合にTRUEを返します.このように,タスクコンテキストか非タスクコンテキストかどうかは,ソフトウェアで管理しています.
CPUロック状態とは,カーネル管理外のものを除くすべての割込みが禁止された状態のことです.
管理外の割込みとは,カーネルの中で割込み禁止されたくない.その代わり,その割込みからシステムコールが呼べなくてもいい.という割込みのことをいいます.
SH3の割込みレベルは,1~15レベルです. 「nレベルより高い割込みをカーネル管理外とする.」というようにnの値を設定します.たとえば,割込みレベルが,13,14,15のものをカーネル管理外とする場合は,この値を12とします.言い換えるとこのnの値は,CPUロック中の割込み優先度マスクの値となります.以降,このCPUロック中の割込み優先度マスクの値が12と仮定して,話を進めます.




【図6-1 CPUロック中の割込み優先度マスク概念図】
CPUロック中の割込み優先度マスクへの値の設定の実装は以下のようになっています.


170 #define TIPM_LOCK    TMIN_INTPRI   //nの値のマクロ定義(実際には負の値)
175 #define IIPM_LOCK INT_IPM(TIPM_LOCK) //正負ひっくり返し,4ビット左ずらし



ステータスレジスタ(以降,SRと略す)の,4〜7ビット目が割込みマスクビット(IPM)なので,4ビット左シフトした値を作ってIIPM_LOCKとしています.



以下,SRのIPMに設定された値のことを,単にIPMと略します.

IPMの値が12の場合とは
CPUロック中は,CPUロック中の割込み優先度マスク(IIPM_LOCK)が,SRに設定されるので,IPMは12になります.では,IPMが12なら,CPUロック状態と判断できるでしょうか?
残念ながらできません.なぜなら,IPMが12となる状況は複数考えられるからです.

 CPUロック状態でIIPM_LOCKの値が設定されて,12となった
◆.皀妊訃紊粒箙み優先度を変更する,chg_ipm()システムコールの発行によって,12となった
 割込み優先度が12の割込みが入ったことにより,12となった

このように,IPMでCPUロック状態かどうかを判断することができないため,やむをえずグローバル変数lock_flgを用いて管理しています.

CPUロック状態へ移行

CPUロック状態への移行は,少し複雑なステップを踏みます.まず,フローと概念図をみてみましょう.【図6-2,6-3】

STEP1:IPMの値を取得し,CPUロック中の割込み優先度マスクの値と比較
STEP2:CPUロック中の割込み優先度マスクの値の方が大きければ,その値をIPMに設定
STEP3:変更前のIPMの値を,保存退避
STEP4:CPUロックフラグをTRUE






【図6-2 CPUロック状態へ移行の処理フロー】 



【図6-3 CPUロック状態へ移行概要】

208 Inline void
209 x_lock_cpu(void)
210 {
211 uint8_t iipm;
212
219 iipm = current_iipm();  // SRレジスタを取ってきて,4ビット分を取り出す
220 if (IIPM_LOCK > iipm) {  // STEP1: IIPM_LOCK(CPUロック中の割込み優先度マスク)がIPMより大きければ
221 set_iipm(IIPM_LOCK); // STEP2:IIPM_LOCKをSRにセット
222 }
223 saved_iipm = iipm; // STEP3:変更前のIPMの値を保存
224 lock_flag = TRUE;  // STEP4:ロックフラグをTRUEへ
225 }


図6-3 にあらわしたように,CPUロック状態を表現するのに,lock_flagとsaved_iipmの2つのグローバル変数を用いています.

CPUロック状態の解除
CPUロック状態の解除は,CPUロック状態への移行に比較して単純です.【図6-4】

STEP1:CPUロックフラグをFALSE
STEP2:saved_iipmの値をIPMへ復帰



【図6-4 CPUロック状態の解除概要】

239 Inline void
240 x_unlock_cpu(void)
241 {
242 lock_flag = FALSE;   // STEP1:CPUロックフラグをFALSE
243 set_iipm(saved_iipm); // STEP2:保存しておいたIPMをレジスタへ復帰 
244 }


CPUロック状態の参照
ここまで見てきたように,CPUロック状態か解除状態かは,lock_flagで管理しています.ですから,CPUロック状態かどうかを判断する処理では,lock_flagの値を返すだけの処理となっています.

252 Inline BOOL
253 x_sense_lock(void)
254 {
255 return(lock_flag);
256 }

ASPカーネルは,TOPPERS標準割込み処理モデルに準拠していますので,CPUロック状態と割込み優先度マスクの両方の状態があるとしています(TOPPERS標準割込み処理モデルについては,後日解説します).
SH3の場合は,ハードウェア的にCPUロック状態を管理する仕組みがないので,IPMと2つの補助変数を使用して,CPUロック状態と割込み優先度マスクの両方の機能があるように見せかけています.この割込み優先度マスクをIPMと区別するため,以降は「モデル上の割込み優先度マスク」という表現をします.


モデル上の割込み優先度マスクの参照
割込み優先度マスクは,モデル上の概念です.どのようにその概念を作り出しているのかを解説します.

CPUロック中
CPUロック中のIPMは,CPUロック中の割込み優先度マスクと,モデル上の割込み優先度マスクの高いほうに設定されています.その間モデル上の割込み優先度マスクの値は,saved_iipmに退避されています.よって,CPUロック中のモデル上の割込み優先度マスクは,saved_iipmの値を参照します.【図6-5】



【図6-5 CPUロック中のモデル上の割込み優先度マスク】

CPUロック解除中
CPUロックが解除されている間は,IPMにはモデル上の割込み優先度マスクが設定されています.よって,IPMがモデル上の割込み優先度マスクになります.【図6-6】



【図6-6 CPUロック解除中のモデル上の割込み優先度マスク】

以下に,モデル上の割込み優先度マスクの参照部分のソースコードを示します.ソースコードを見ていただくと,より上記の解説を理解していただくことができると思います.

308 Inline PRI
309 x_get_ipm(void)
310 {
311 uint8_t iipm;
312
313 if (!lock_flag) {     //CPUロック解除中
314 iipm = current_iipm(); //IPMを参照
315 }
316 else { //CPUロック中
317 iipm = saved_iipm; //saved_iipmを参照
318 }
319 return(EXT_IPM(iipm));
320 }


ディスパッチ禁止状態,保留状態というは,プロセッサが持っている情報ではなく,カーネルの状態なので,ソフトウェアで管理します.つまり,ターゲット依存部ではなく非依存部にあります.

ディスパッチ禁止状態の管理
・ターゲット非依存部に,変数disdspを宣言し管理

ディスパッチ保留状態の管理
・ターゲット非依存部に,変数dspflgを宣言して管理
ディスパッチ保留を FALSE,ディスパッチできる状態をTRUEとする.

ディスパッチ保留状態になる条件には,以下の4つがあります.
・非タスクが動いている
・CPUロック状態
・ディスパッチ禁止である
・割込み優先度マスクが全解除でない

ディスパッチ保留かどうかの判断が必要なのは,ディスパッチャを起動するときです.その処理部分にはCPUロック状態や非タスクコンテキストでは到達しないようになっています.よって,ディスパッチ保留かどうかは,.妊スパッチ禁止かどうか,割込み優先度マスクが全解除か,だけが判断材料になります.その二つを考慮して,ディスパッチ保留状態かどうかを,dspflgで表現しています.
つまり,ディスパッチ禁止にするときは,無条件にdspflgをFALSEにします.また,ディスパッチ許可にするときは,割込み優先度マスクが全解除のときは,TRUEとし,全解除状態でないときは,FALSEとします.

【図6-7】に,ディスパッチ禁止にするときと,ディスパッチ許可にするときのdspflgの変化を示します.



【図6-7 dspflgの変化】


ディスパッチの禁止
disdspをTRUEにして,dspflgをFALSEにします.

273 dis_dsp(void)
274 {
275 ER ercd;
276
277 LOG_DIS_DSP_ENTER();
278 CHECK_TSKCTX_UNL();
279
280 t_lock_cpu();
281 disdsp = TRUE;  // ディスパッチ禁止へ
282 dspflg = FALSE; // ディスパッチ保留へ
283 ercd = E_OK;
284 t_unlock_cpu();
285
286 error_exit:
287 LOG_DIS_DSP_LEAVE(ercd);
288 return(ercd);
289 }


ディスパッチの許可
 まず,disdspをFALSEにします.次に,ディスパッチできる状態にしていいかどうかを確認し,さらに,ディスパッチ保留中にタスク切り替えが要求されていた場合は,ディスパッチャを起動します.【図6-8】

STEP1:disdspをFALSEにして,ディスパッチ禁止を解除
STEP2:モデル上の割込み優先度マスクが全解除状態かどうか
(TIPM_ENAALLは全解除を表すマクロ)
STEP3:dspflgをTRUEにし,ディスパッチできる状態へ
STEP4:p_runtsk とp_schedtskが一致していなかったら,タスク切り替えが要求されていたがディスパッチ保留状態でタスク切り替えが保留されていた状態なので,ディスパッチャを起動






【図6-8 ディスパッチ許可処理フロー】

298 ER
299 ena_dsp(void)
300 {
301 ER ercd;
302
303 LOG_ENA_DSP_ENTER();
304 CHECK_TSKCTX_UNL();
305
306 t_lock_cpu();
307 disdsp = FALSE;  // STEP1:ディスパッチ禁止を解除
308 if (t_get_ipm() == TIPM_ENAALL) { // STEP2:モデル上の割込み優先度マスクが全解除状態なら
309 dspflg = TRUE;  // STEP3:ディスパッチできる状態へ
310 if (p_runtsk != p_schedtsk) {    // STEP4:保留中にタスク切り替えが要求されていた場合
311 dispatch();      //      ディスパッチャを起動
312 }
313 }
314 ercd = E_OK;
315 t_unlock_cpu();
316
317 error_exit:
318 LOG_ENA_DSP_LEAVE(ercd);
319 return(ercd);
320 }


次回は,ディスパッチャのしくみについて詳しく解説します.