RTOSベース設計におけるセマフォまたはミューテックスでのリソース共有
すべてのタスクが1つのアドレス空間にあり、変数、ポインタ、バッファ、リンク付きリスト、リングバッファなどをグローバルに参照できる場合、タスクはデータを容易に共有することができます。データを共有することでタスク間の情報交換は簡単になりますが、競合やデータ破損が生じないよう、各タスクがデータに排他的にアクセスできるようにすることが重要です。
ここでは、ソフトウェアで実装された簡単な時計計時のタスクを例にして説明します。このタスクは単に時間、分、秒を追跡するだけのものですが、曜日、日付、月、年、うるう年に対応できるよう簡単に拡張できます。実際、そのようなモジュールを、筆者の著書『Embedded Systems Building Blocks』[3]の第6章で紹介しています。
このタスクが、分(Minutes)を0に設定した直後に別のタスクによって(割込みがトリガになり)プリエンプトされ、この別のタスクのほうがTimeOfDayTask()より重要になったとします。ここで、この優先度の高いタスクが現在の時刻を知る(読み出す)必要が生じた場合、どのようなことになるでしょうか。割込み前に時間(Hours)の変数はインクリメントされなかったため、優先度の高いタスクが読み出す時間は不正確なものとなり、この場合、丸一時間も違ったものになってしまいます。
TimeOfDayTask()タスクの変数を更新するコードは、想定されるプリエンプションがある場合は常に、すべての変数を不可分的に(つまりアトミックに)処理する必要があります。時刻変数は共有リソースとみなされ、これらの変数にアクセスするコードはいずれもこの全ての変数に対して排他的にアクセスできなくてはなりません。RTOSでは、このようなサービス(API)をセマフォおよび相互排他セマフォ(別名ミューテックス)を通じて提供します。どちらをどのような場合に使用すればよいかを簡単に説明しましょう。
RTOSベースのシステムでは、リスト2に示すように、タスクは共有リソースをセマフォまたはミューテックスを用いてラップする必要があります。どのAPIを使用すべきかは、お使いのRTOSによって異なります。
uint8_t Hours;
uint8_t Minutes;
uint8_t Seconds;
void TimeOfDayTask (void)
{
Seconds = 0;
Minutes = 0;
Hours = 0;
while (1) {
// Delay (i.e., sleep) task for 1 second
if (Seconds >= 59) {
Seconds = 0;
if (Minutes >= 59) {
Minutes = 0;
if (Hours >= 23) {
Hours = 0;
} else {
Hours++;
}
} else {
Minutes++;
}
} else {
Seconds++;
}
}
}
リスト1. リソース共有保護のない単純な時刻計時
uint8_t Hours;
uint8_t Minutes;
uint8_t Seconds;
void TimeOfDayTask (void)
{
Seconds = 0;
Minutes = 0;
Hours = 0;
while (1) {
// Delay (i.e., sleep) task for 1 second
Acquire Semaphore (or mutex); // API depends on your RTOS
if (Seconds >= 59) {
Seconds = 0;
if (Minutes >= 59) {
Minutes = 0;
if (Hours >= 23) {
Hours = 0;
} else {
Hours++;
}
} else {
Minutes++;
}
} else {
Seconds++;
}
セマフォまたはミューテックスをリリース (お使いのRTOSによりAPIを変更する)
}
}
リスト2. リソース共有保護のある単純な時刻計時
セマフォ
ここでは詳細を省きますが、セマフォとは、(リスト1. に示すように)タスクがリソースへのアクセスを始める前に入手しなくてはならない鍵のことです。この鍵は、タスクが共有リソースへのアクセスを終えたら手放す必要があります。当然のことながら、実際に入手するのは物理的な鍵ではなく、仮想上の鍵です。セマフォは使用前に初期化する必要があります。アプリケーションでは任意の数のセマフォを宣言でき、それぞれが別々のリソースを保護します。セマフォにはバイナリとカウンティングの2種類があります。
バイナリセマフォにある鍵は1つです。この場合、セマフォの値は、鍵が別のタスクで使用中であることを示す0、または、鍵が使用可能なことを示す1です。時刻計時を実行する場合、アプリケーションで必要な計時は1つだけなので、バイナリセマフォを用います。タイムゾーンは、時刻計時にオフセットを付けることで容易に処理できます。
カウンティングセマフォには、同じリソースを複数使用できることから、複数の鍵があります。カウンティングセマフォの値は、すべての鍵がタスクに割り当てられている場合は0、使用可能な鍵がある場合は0より大きい値になります。カウンティングセマフォを使用する例としては、複数の同じバッファからなるバッファプールを管理する場合があります。バッファプールのバッファが必要になると、そのたびにRTOS APIを呼び出して鍵を取得します。使用可能なものがあれば(セマフォの値が0より大きい)、バッファアロケータがバッファを提供します。使用可能なバッファがなければ、いずれかのバッファを保持しているタスクの1つがバッファを解放するまで、呼び出しタスクは中断されます(待機中のタスクの優先度が最高であると仮定します)。
そのため、セマフォを使用するには:
- バイナリセマフォかカウンティングセマフォのどちらを使用するかを決める必要があります
- セマフォは初期化が必要で、バイナリセマフォは1に、カウンティングセマフォはN(同一リソースの共有数)に初期化します
- リソースにアクセスする必要がある場合は必ず、RTOS APIを呼び出してセマフォを取得(鍵を取得)し、完了後はRTOS APIを呼び出してセマフォを解放します
一般的には、セマフォは使用しないことが望ましいとされています。これは優先順位の逆転が無制限に発生する恐れがあるためです。代わりにミューテックスを使用することが推奨されています。優先順位の逆転については、著書『µC/OS-III』[1]、第13章(セクション13.3.5)で詳しく説明しています。
相互排他セマフォ(別名ミューテックス)
ミューテックスとは特別なタイプのバイナリセマフォのことで、優先順位の逆転が無制限に生じることを防止できます。セマフォと同様、ミューテックスも取得や解放を行う前に初期化する必要があります。これは、RTOSが提供するAPIで行うことができます。
優先順位の低いタスクがリソースを保有している状態で、このリソースに順位の高いタスクがアクセスする必要がある場合、RTOSは低順位タスクの優先度を高順位タスクの優先度と一致するように変更します。これにより、ミューテックスの保有者は可能な限り早くリソースへのアクセスを完了し、リソースを解放することができます。リソースが解放されると、この保有者の優先度は元の優先度に戻ります。ミューテックスが解放されると、RTOSは直ちに待機中の高優先度タスクに切替え、リソースへのアクセス権を与えます。
セマフォとミューテックスのどちらを使うべきか?
一般原則として、共有リソースにアクセスする場合はミューテックスを使用し、セマフォの使用はシグナリングメカニズムとして残しておくことを推奨します(別の記事で解説)。
ただし、セマフォAPIの実行時間は、通常、ミューテックスの実行時間より大幅に短くなります。これは、セマフォでは、セマフォ保有者の優先度を変更して、セマフォが解放されると元に戻すことができないためです。したがって、共有リソースを共有するタスクの優先度がすべて同じ場合や、セマフォを使用するタスクに時間制限がない場合、あるいは優先度の逆転の可能性があっても構わない場合は、セマフォを使用して全く問題はありません。
もう1つ考慮すべき重要な点は、セマフォAPIとミューテックスAPIは、数百ものCPUサイクルを消費する可能性があることです。そのため理想的には、共有リソースにアクセスする実行時間は、RTOS APIの実行時間より長くする必要があります。別の言い方をすれば、変数を2、3個更新するだけであれば、割込みをディスエーブルして、変数を更新し、再度割込みをイネーブルする、という方法が望ましいでしょう。もちろん、これは、割込みをディスエーブル/イネーブルできることが前提です。RTOSの中には、アプリケーションのコードをユーザーモード(権限なし)で実行するものもあり、その場合は割込みのディスエーブルができません。お使いのRTOSマニュアルで確認をしてください。
参考文献:
[1] µC/OS-III, The Real-Time Kernel:
µC/OS-III, The Real-Time Kernel, and the Freescale Kinetis ARM Cortex-M4, Jean J. Labrosse, 978-0-9823375-2-3
µC/OS-III, The Real-Time Kernel, and the Infineon XMC4500, Jean J. Labrosse, 978-1-9357722-0-0
µC/OS-III, The Real-Time Kernel, and the NXP LPC1700, Jean J. Labrosse, 978-0-9823375-5-4
µC/OS-III, The Real-Time Kernel, and the Renesas RX62N, Jean J. Labrosse, 978-0-9823375-7-8
µC/OS-III, The Real-Time Kernel, and the Renesas SH7216, Jean J. Labrosse, 978-0-9823375-4-7
µC/OS-III, The Real-Time Kernel, for the STM32 ARM Cortex-M3, Jean J. Labrosse, 978-0-9823375-3-0
µC/OS-III, The Real-Time Kernel, and the Stellaris MCUs,
Jean J. Labrosse, 978-0-9823375-6-1
[2] Weston Embedded Book Downloads
https://weston-embedded.com/micrium-books
[3] Embedded Systems Building Blocks, Complete and Ready-to-Use Modules in C
Jean J. Labrosse
ISBN 978-0879306045
著者について
本稿は、RTOSを使用したアプリケーション開発をテーマとしたシリーズの一部です。
Jean Labrosse(ジーン・ラブロス)氏は、Micriumの創設者であり、広く普及しているuC/OS-IIおよびuC/OS-IIIカーネルの作成者です。組込みソフトウェアのuC/ラインの発展のために積極的に取り組んでいます。
組込みシステム市場での豊富な経験を有し、市場を知り尽くしているLabrosse氏は、Weston Embedded Solutionsの主席アドバイザおよびコンサルタントとして、現行のRTOS製品の将来的な方向性の策定に尽力しています。Weston Embedded Solutionsは、Micriumのコードベースから生まれた信頼性の高いCesium RTOSファミリー製品のサポートと開発を専門としています。