こんにちは、ナナです。
ここまで「セマフォ」や「ミューテックス」を利用した排他制御について解説してきました。
注意することとして、これらの排他制御は、タスク間において利用する機能であるということです。
しかし、ITRONでは「非タスク」と呼ばれる割り込み処理が存在します。
ここで問題となるのが、「タスク」と「割り込み」の間で行うべき排他処理は「セマフォ」や「ミューテックス」は使えないということなのです。
それでは、割り込みにおける排他制御方法を解説しましょう!
割り込みコンテキストで使えないセマフォとミューテックス
師匠!割り込みって組み込み開発でよく使いますよね。割り込みのプログラムから「セマフォ」や「ミューテックス」が使えないってどういうことなんですか?
そうだね。使えないんだね。それは「割り込みコンテキスト」を正しく理解できていれば、理由がわかるんだよ。
割り込みコンテキストについて忘れてしまった人は『ITRON入門 周期ハンドラから学ぶ割り込み制御【CRE_CYC】』を先に見ておきましょう。
割り込みコンテキストでセマフォとミューテックスが使えない理由
タスクコンテキストとは、リアルタイムOS上における労働者でした。
そして、割り込みコンテキストとは、タスク以外に存在するもうひとつの労働者でしたね。
割り込みとは「非タスク」とも呼ばれる、システムの中でこっそり動く裏方の労働者です。非タスクであるがゆえに割り込みは「タスク状態」を持ちません。
このタスク状態を持たないということが「セマフォ」と「ミューテックス」による排他制御ができない理由です。
これらの排他機構を利用するためには、「wai_sem」や「loc_mtx」といった待ち状態に入る可能性のあるサービスコールを呼ぶ必要があります。
しかし、タスク状態を持たない割り込みでは、これらのサービスコールを呼ぶことができないのです。
割り込みとは非常に大きな制約を持った労働者なのです。
RTOSを使う時にはとある処理が「タスクコンテキスト」か「割り込みコンテキスト」で動くのかを意識する必要があります。
割り込みコンテキストで排他するための方法
割り込み処理から「セマフォ」や「ミューテックス」が使えないってことは、排他ができないってことですよね。
でも、そもそも割り込み処理から排他制御って必要なんですか?
排他制御が必要かと聞かれれば、必要となるケースは結構あるんだよ。
でも、「セマフォ」や「ミューテックス」は使えない。この問題を解決する必要があるんだよ。
割り込み処理でも排他すべき共有資源
代表的な共有資源といえばグローバル変数でした。グローバル変数は、割り込みにおいてもアクセスしたい共有資源です。
そのため、「タスク」からも「割り込み」からもアクセスするグローバル変数の場合は、排他制御が必要となります。
2つ以上の労働者から共有してアクセスする資源は、タスクであろうと割り込みであろうと排他制御が必要となります。
CPUロック(割り込み禁止)を行うサービスコール
割り込みコンテキストが関与する排他制御は「CPUロック」と呼ばれる割り込み禁止状態を作り出すことで行います。
CPUロック状態はITRON仕様において次のように定義されています。
このCPUのロック/アンロックは、次のサービスコールにて実施します。
ER loc_cpu(); // CPUロック
ER unl_cpu(); // CPUアンロック
ER iloc_cpu(); // CPUロック(割り込み用)
ER iunl_cpu(); // CPUアンロック(割り込み用)
引数には何も指定する必要がありません。つまり、CPUロックは「セマフォ」や「ミューテックス」のようなオブジェクトの生成が不要です。
サービスコールの先頭に「i」が付いているものは割り込みコンテキストから呼べるサービスコールでしたね。
CPUロックを使った排他制御のプログラム例
それでは具体的にCPUロックを使ったプログラムを紹介しましょう。
タスクと割り込みからアクセスするグローバル変数「globalPos」に対して排他制御を行ったプログラムです。
割り込みハンドラとして周期ハンドラを利用しています。
タスクコンテキスト
void MAIN(VP_INT exinf)
{
while(1)
{
loc_cpu();
if (globalPos.x == 139 && globalPos.y == 35)
{
Sci_putString("Tokyo\n"); // 東京
}
else if(globalPos.x == 127 && globalPos.y == 26)
{
Sci_putString("Okinawa\n"); // 沖縄
}
else
{
Sci_putString("unknown\n"); // 不明
}
unl_cpu();
dly_tsk(100);
}
}
割り込みコンテキスト
void CYC1(VP_INT exinf)
{
static cnt = 0;
if (cnt == 0)
{
// 東京座標書き込み
globalPos.x = 139;
globalPos.y = 35;
cnt = 1;
}
else
{
// 沖縄座標書き込み
globalPos.x = 127;
globalPos.y = 26;
cnt = 0;
}
}
このようにタスク処理において、「loc_cpu」と「unl_cpu」で排他必要な区間を挟み込むことで排他を行います。
割り込み処理側でCPUロックをしていない理由
「タスク」と「割り込み」の関係性において、優先順位は「割り込み」の方が強いのです。
つまり、割り込み処理が実施されている最中にタスクが動き出すことはありません。
そのため、割り込み処理側においては排他制御用の「loc_cpu/unl_cpu」は実施する必要がありません。
それでは、「iloc_cpu/iunl_cpu」という割り込みから呼ばれるためのサービスコールは何のために存在するのかというと、「多重割り込み」と呼ばれる割り込み中に別の割り込みが発生する現象を抑えるためです。
つまり、「割り込み」と「割り込み」の間で必要な排他制御では必要になるということです。
2者間の排他方法をまとめると次のようになります。
タスク | 割り込み | |
タスク | セマフォ or ミューテックス | CPUロック |
割り込み | CPUロック | CPUロック |
排他対象の資源が一体どのコンテキストからアクセスされる可能性があるかを常に意識する必要があります。
タスクのみであれば「セマフォ」「ミューテックス」ですし、割り込みも含まれるのであれば「CPUロック」を使う必要があります。
割り込み禁止をした場合の注意点
なるほど~、割り込みではCPUロックをすれば排他が実現できるのですね。私これからバンバン使っちゃいますっ!
いやいや、バンバン使わないでね。必要な時に必要なだけ使ってほしいよ。「割り込み禁止」とは乱発するものではないんですよ。
割り込み禁止を使う際の注意点①:排他区間の極小化
本来、「割り込み」というものは超緊急事態の処理を行うための処理です。
割り込み禁止中に発生した割り込みシグナルは、割り込みが許可されるまで保留されます。
そのため、タスク処理において「割り込み禁止」~「割り込み許可」のクリティカルセクションは極力小さくすることが求められます。
そうすることで、保留されていた割り込みがわずかな遅延で対処することができます。遅延時間が長くなりすぎると、割り込み機能の意味がなくなってしまうため注意しましょう。
クリティカルセクション内では時間のかかる処理は避けましょう。これはCPUロックに限らず、セマフォやミューテックスにおいても同じですよ。
割り込み禁止を使う際の注意点②:サービスコールの限定
CPUロックを行っている区間は、特別なCPUロック状態というものになっています。
このCPUロック状態では呼び出せるサービスコールは限定されていることに注意しましょう。
CPUロック状態は非常に特別な状態に入っていることを忘れてはいけません。排他区間の処理は制限されていることを覚えておきましょう。
割り込みにおける排他制御のまとめ
それでは本内容を振り返りましょう。
- 割り込みが関係する排他制御はCPUロックで行う
- タスクと割り込みという2つの関係性において排他制御方法は変化する
- CPUロックの区間は制約があり、時間が掛かる処理はしない、呼び出せるサービスコールは限定される。