はじめに
基礎編・設計編ではRTOSの概念と設計の勘所を整理しました。API編ではTOPPERS/ATK1の具体的なシステムコールを取り上げます。
前編ではタスクを起動・終了・切り替えるための ActivateTask()・TerminateTask()・ChainTask()、そしてタスク間の同期に使うイベントAPI・リソースAPIを見ていきます。
タスク管理API
ActivateTask ― タスクを起動する
ActivateTask() は休止状態のタスクを実行可能状態にするAPIです。
StatusType ActivateTask(TaskType TaskID);
TaskID には起動したいタスクのIDを指定します。タスクIDはOILファイルで定義され、自動生成されます。
/* タスクを起動する例 */
StatusType ret = ActivateTask(task_control);
if (ret != E_OK) {
/* エラー処理 */
}
注意点
- 呼び出したタスクより高優先度のタスクを起動すると、即座にプリエンプションが発生します
- 同優先度または低優先度のタスクを起動した場合、現在のタスクの処理が継続します
- すでに起動済みの基本タスクに対して呼ぶと、OILファイルの
ACTIVATIONパラメータでMULTIPLEを指定していれば起動要求をキューに積めます。1(デフォルト)の場合はE_OS_LIMITが返ります
TerminateTask ― タスクを終了する
TerminateTask() は実行中のタスクを終了して休止状態に戻すAPIです。自タスクのみを終了できます。
StatusType TerminateTask(void);
基本タスクは必ず TerminateTask() または ChainTask() で終了しなければなりません。return で関数から抜けてはいけません(スタック破壊・未定義動作の原因になります)。
TASK(task_sensor) {
read_sensor();
process_data();
TerminateTask(); /* 必ずここで終了する */
}
TerminateTask() を呼ぶと、スケジューラが次に実行すべきタスクを選択します。
ChainTask ― タスクを終了して別のタスクを起動する
ChainTask() は現在のタスクを終了しつつ、指定したタスクを起動するAPIです。TerminateTask() と ActivateTask() を合わせた動作ですが、アトミックに実行されます。
StatusType ChainTask(TaskType TaskID);
TASK(task_A) {
do_something();
ChainTask(task_B); /* タスクAを終了してタスクBを起動 */
}
TerminateTask() + ActivateTask() と比べたメリットは、タスクの切り替えの間に別のタスクが割り込む余地がない点です。処理の連鎖を確実に行いたい場合に使います。
自タスクを指定すると、タスクを再起動(再初期化)する効果があります。
TASK(task_cyclic) {
do_cyclic_process();
ChainTask(task_cyclic); /* 自タスクを再起動 */
}
GetTaskID・GetTaskState ― タスクの状態を取得する
デバッグや状態確認のために使うAPIです。
/* 現在実行中のタスクIDを取得 */
TaskType task_id;
GetTaskID(&task_id);
/* タスクの状態を取得 */
TaskStateType state;
GetTaskState(task_control, &state);
/* state: RUNNING / READY / SUSPENDED / WAITING(WAITINGは拡張タスクのみ) */
エラー処理
OSEK/ATK1のAPIは StatusType を返します。正常終了は E_OK、エラー時は各APIに定義されたエラーコードが返ります。
StatusType ret = ActivateTask(task_control);
switch (ret) {
case E_OK:
break;
case E_OS_LIMIT:
/* タスクの起動要求数が上限を超えた */
break;
default:
/* その他のエラー */
break;
}
本番コードではエラー処理を省略しがちですが、デバッグ時は必ず確認する習慣をつけてください。
同期API
基礎編でイベントとリソースの考え方を整理しました。ここではTOPPERS/ATK1の具体的なAPIを、イベント系とリソース系に分けて解説します。
イベントAPI
SetEvent ― イベントを設定する
StatusType SetEvent(TaskType TaskID, EventMaskType Mask);
指定したタスクに対してイベントを設定します。そのイベントを WaitEvent() で待っているタスクがあれば、実行可能状態に遷移します。
/* ISR2やタスクからイベントを設定する */
SetEvent(task_control, event_sensor_ready);
Mask には複数のイベントをORで組み合わせて指定できます。
SetEvent(task_control, event_sensor_ready | event_timer);
ClearEvent ― イベントをクリアする
StatusType ClearEvent(EventMaskType Mask);
自タスクのイベントフラグをクリアします。WaitEvent() の後に必ず呼んでください。
TASK(task_control) {
while (1) {
WaitEvent(event_sensor_ready);
ClearEvent(event_sensor_ready); /* 必ずクリアする */
process_sensor_data();
}
}
WaitEvent ― イベントを待つ
StatusType WaitEvent(EventMaskType Mask);
指定したイベントが設定されるまでタスクを待ち状態にします。拡張タスクのみが呼べます。
複数のイベントをORで指定すると、いずれか一つが設定された時点で返ってきます。
WaitEvent(event_sensor_ready | event_timeout);
どのイベントで起きたかを確認するには GetEvent() を使います。
EventMaskType events;
GetEvent(task_control, &events);
if (events & event_sensor_ready) {
ClearEvent(event_sensor_ready);
/* センサーデータ処理 */
}
if (events & event_timeout) {
ClearEvent(event_timeout);
/* タイムアウト処理 */
}
GetEvent ― イベントの状態を取得する
StatusType GetEvent(TaskType TaskID, EventMaskRefType Event);
指定したタスクの現在のイベントフラグを取得します。
リソースAPI
GetResource ― リソースを取得する
StatusType GetResource(ResourceType ResID);
指定したリソースを取得します。リソースを取得している間、タスクの優先度がそのリソースの天井優先度まで引き上げられるため、同リソースを競合するタスクにプリエンプトされなくなります(PCPの効果)。
GetResource(res_shared_data);
/* クリティカルセクション */
shared_data = new_value;
ReleaseResource(res_shared_data);
ReleaseResource ― リソースを解放する
StatusType ReleaseResource(ResourceType ResID);
取得したリソースを解放します。GetResource() と ReleaseResource() は必ずペアで使い、取得した順番と逆順に解放してください。
/* 複数リソースの取得・解放 */
GetResource(res_a);
GetResource(res_b);
/* 処理 */
ReleaseResource(res_b); /* 逆順に解放 */
ReleaseResource(res_a);
リソースと割り込み禁止の使い分け
共有データの保護には、リソースの他に割り込み禁止(SuspendOSInterrupts() など)も使えます。
| リソース | 割り込み禁止 | |
|---|---|---|
| 保護対象 | タスク間 | タスクと割り込み間 |
| オーバーヘッド | 小 | 非常に小 |
| 割り込み応答性への影響 | なし | あり |
タスク間の排他制御にはリソース、割り込みハンドラとタスクの間の排他制御には割り込み禁止を使うのが基本です。ただし割り込み禁止区間は極力短くしてください。
よくある落とし穴
WaitEventの後にClearEventを忘れる
イベントフラグはクリアしない限り残り続けます。次回の WaitEvent() が即座に返ってきてしまうので、必ずクリアしてください。
GetResourceとReleaseResourceをネストする
複数のリソースをネストして取得する場合、解放順序を誤るとデッドロックになります。必ず取得の逆順に解放してください。
基本タスクでWaitEventを呼ぶ
WaitEvent() は拡張タスクのみが呼べます。基本タスクから呼ぶとエラーになります。
リソースを保持したままWaitEventを呼ぶ
OSEK仕様では、GetResource() でリソースを取得したまま WaitEvent() を呼ぶことは禁止されています。必ず ReleaseResource() でリソースを解放してから WaitEvent() を呼んでください。
まとめ
-
ActivateTask()で休止状態のタスクを起動する -
TerminateTask()で自タスクを終了する。基本タスクは必ず呼ぶ -
ChainTask()でタスクの終了と別タスクの起動をアトミックに行う -
SetEvent()でイベントを設定、WaitEvent()で待つ、ClearEvent()で必ずクリアする -
GetResource()とReleaseResource()は必ずペアで、逆順に解放する
後編では時間管理API(アラーム)とOIL設定ファイルを取り上げます。