RTOSを使った組み込みソフトウェアの仕事をしていました。
RTOS について研修などで勉強してきても、知識と結びつけてソースコードが読めず、ソースコードを理解できない方が多かったように思います。
何かのきっかけになるかもしれないので、雑多な内容で記事にします。
タスクは無限ループ
タスクは何かしらの処理を行う責務が定義されたものだと思います。
その責務が1回だけ処理して終わることは稀で、何回も処理することが多いと思います。
このため、タスクは無限ループの関数になることが多いと思います。
無限ループの関数は、タスクの生成時に引数でセットしていると思います。
セットした無限ループの関数は、タスクをスタートするとOSが呼び出して動き始めます。
TaskA_main(){
InitTask();
//自作の関数
//タスクの初期化など
//一度行えばいい処理
//無限ループに入る前に行う
for(;;){
ReceiveQueue( ID_QUEUE_TASK_A );
//システムコール
//処理が発生するまで待つ
ExecEvent();
//自作の関数
//受け取ったキューに応じた処理を行う
}
}
処理が発生して待ち状態が解除されたら、
発生した処理を実行し、
ループで戻り待ち状態になります。
このとき、
既に処理が発生していれば、上記の「待ち状態が解除されたら」に該当しますので、もう一周することになります。これを処理が無くなるまで繰り返します。
待ち状態を解除する仕組みは自モジュールで提供
自タスクが待ち状態になっているとき、待ち状態を解除するには、他タスクや割り込みなどの自タスク外から待ち状態を解除します。
待ち状態になっているタスクは関数をコールできないので、自タスクの待ち状態を解除する関数をコールできないためです。
待ち状態を解除する仕組みは、自モジュールがAPIとして提供し、そのAPI内で待ち状態を解除するシステムコールを記述します。
他モジュールはAPIをコールするだけにします。
自モジュールのタスクがどのような仕組みで待ち状態になっているかを、他モジュールに意識させません。
ApiTaskA_foo(){
SendQueue( ID_QUEUE_TASK_A );
//システムコール。
//TaskAの待ち状態を解除する。
//fooの処理をさせる情報もセットする
}
関数を実行する優先度は、その関数をコールしたタスクの優先度
自タスクが無限ループで動作し、自モジュールがAPIを提供するのと同様に、
他タスクも無限ループで動作し、他モジュールはAPIを提供します。
自タスクが他モジュールのAPIをコールしたとき、
自タスクの無限ループで他モジュールのAPIをコールしていますので、
自タスクの優先度で他モジュールのAPIは実行されます。
同様に、
他タスクが自モジュールのAPIをコールしたとき、
他タスクの無限ループで自モジュールのAPIをコールしていますので、
他タスクの優先度で自モジュールのAPIは実行されます。
APIやコールバック関数など、他モジュールがコールする関数は、他タスクの優先度で実行されます。
関数が記述されたモジュールの優先度で実行されるわけではないので、ご注意ください。
優先度が異なる複数のタスクからコールされる関数は、同じタイミングでコールされた場合、関数内で追い抜かれることがあります。
実装によっては排他処理が必要になることがあります。
処理の実行順序
全く意味のない処理ですが、
タスクAとタスクBの2つのタスクがあり、お互いのAPIを呼び合うとします。
TaskA_main(){
InitTaskA();
for(;;){
ReceiveQueue( ID_QUEUE_TASK_A ); //*1
ApiTaskB_foo(); //*2
}
}
TaskB_main(){
InitTaskB();
for(;;){
ReceiveQueue( ID_QUEUE_TASK_B ); //*3
ApiTaskA_foo(); //*4
}
}
ApiTaskA_foo(){
SendQueue( ID_QUEUE_TASK_A );
}
ApiTaskB_foo(){
SendQueue( ID_QUEUE_TASK_B );
}
2つのタスクともに起動しただけでは、タスクAは*1で待ち状態になり、タスクBは*3で待ち状態になります。
別のタスクCから ApiTaskA_foo() がコールされた場合、
タスクAの待ち状態は解除され、 ApiTaskB_foo() をコールします。
これにより、
タスクBの待ち状態は解除され、 ApiTaskA_foo() をコールします。
これにより、
タスクAの待ち状態が解除され、 ApiTaskB_foo() をコールします。
元に戻っており、今後も同じことが続くので、2つのタスクがお互いのAPIを呼ぶループになっていることが分かります。
このとき、タスクAとタスクBの優先度により、処理の順番が異なります。
タスクが切り替わるのはシステムコールを呼んだタイミングですが、
どのようなルールで切り替わるか確認します。
タスクAのほうが優先度が高い場合
*2 → *1 → *4 → *2 → *1→ *3 → *4 → *2
となり、以降 *2 → *1 → *3 → *4 → のループになります。
タスクCの ApiTaskA_foo() でタスクAの待ち状態が解除され、*2を実行します。
タスクBの待ち状態は解除されますが、タスクAのほうが優先度が高いため、*1を実行します。
タスクAは待ち状態になったため、タスクBに処理がまわり*4を実行します。
タスクAの待ち状態は解除され、優先度の高いタスクAに処理がまわり、*2を実行します。
タスクBのキューに積まれますが、タスクAのほうが優先度が高いため、*1を実行します。
タスクAは待ち状態になったため、タスクBに処理がまわり、*3を実行します。
タスクBはキューが積まれているので待ち状態にならず、*4を実行します。
タスクAの待ち状態は解除され、優先度の高いタスクAに処理がまわり、*2を実行します。
以降、*2 → *1 → *3 → *4 → のループになります。
タスクBのほうが優先度が高い場合
*2 → *4 → *3 → *1 → *2
となり、以降 *2 → *4 → *3 → *1 → のループになります。
タスクCの ApiTaskA_foo() でタスクAの待ち状態が解除され、*2を実行します。
タスクBの待ち状態は解除され、優先度の高いタスクBに処理がまわり、*4を実行します。
タスクAのキューに積まれますが、タスクBのほうが優先度が高いため、*3を実行します。
タスクBは待ち状態になったため、タスクAに処理がまわり*1を実行します。
タスクAはキューが積まれているので待ち状態にならず、*2を実行します。
以降、*2 → *4 → *3 → *1 → のループになります。
コメント