はじめに
前編ではRTOSの基本概念とタスクの状態遷移を整理しました。後編では、複数のタスクが協調して動くための仕組み(イベント・リソース・メッセージ)と、時間をトリガーに処理を実行する仕組み(アラーム・スケジュールテーブル)を整理します。
タスク間の連携 ― イベント・リソース・メッセージ
複数のタスクが動いているシステムでは、タスク同士が協調して動く仕組みが必要です。「センサーの値が更新されたら制御タスクに知らせる」「複数のタスクが同じ変数にアクセスするときに競合を防ぐ」といった場面です。
OSEK/ATK1では、タスク間の同期と通信のために主に3つの仕組みを提供しています。イベント・リソース・メッセージです。
イベント ― タスクへの通知
イベントは「何かが起きたことをタスクに知らせる」仕組みです。拡張タスクのみが使えます。
各拡張タスクは複数のイベントフラグを持ちます。他のタスクや割り込みハンドラが SetEvent() でイベントを設定すると、そのイベントを WaitEvent() で待っているタスクが実行可能状態に戻ります。
タスクA(送信側) タスクB(受信側・拡張タスク)
│ │
│ WaitEvent(event_x)
│ │ 待ち状態へ
│ ↓
SetEvent(タスクB, event_x) ─→ 実行可能状態へ
│ │
│ ClearEvent(event_x)
│ │
│ 処理実行
イベントはフラグなので、セットされたままにしておくと次回の WaitEvent() が即座に返ってきます。WaitEvent() の後に必ず ClearEvent() でクリアする習慣をつけてください。
リソース ― 共有データの排他制御
複数のタスクが同じデータや周辺機器にアクセスする場合、処理の途中でタスクが切り替わると一貫性が崩れる可能性があります。
タスクA: 共有変数を読む → (タスク切り替え) → タスクB: 共有変数を書く → タスクA: 古い値で処理を続ける
これを防ぐのがリソースです。リソースを取得(GetResource())している間は、PCPによりタスクの優先度がそのリソースの天井優先度まで引き上げられるため、優先度上限以下のタスクにプリエンプトされなくなります。処理が終わったらリソースを解放(ReleaseResource())します。
GetResource(res_shared_data); /* リソース取得 */
/* この区間は他のタスクに割り込まれない */
shared_data = new_value;
ReleaseResource(res_shared_data); /* リソース解放 */
OSEK/ATK1のリソース管理にはPCP(Priority Ceiling Protocol:優先度上限プロトコル)が使われており、デッドロックが発生しない設計になっています。詳しくはAPI編(同期API)で解説します。
メッセージ ― データの受け渡し
メッセージはタスク間でデータをやり取りする仕組みです。OSEK-COMという通信仕様で定義されており、送信側がメッセージを送ると受信側がそれを受け取れます。
イベントとの違いは「通知だけでなくデータも一緒に渡せる」点です。ただしATK1の実装によってはCOMのサポートが限定的な場合もあるので、プロジェクトの環境を確認してください。
3つの仕組みの使い分け
| 仕組み | 用途 | 使えるタスク |
|---|---|---|
| イベント | タスクへの通知 | 拡張タスクのみ |
| リソース | 共有データの排他制御 | 基本・拡張タスク両方 |
| メッセージ | データの受け渡し | 基本・拡張タスク両方 |
シンプルな通知にはイベント、共有データの保護にはリソース、データの受け渡しにはメッセージ、という使い分けが基本です。
時間管理 ― アラームとスケジュールテーブルの違い
組み込みシステムでは「10msごとにセンサーを読む」「100msごとに制御演算を実行する」といった周期処理が多くあります。アラームはOSEK/VDX仕様(ATK1)から使える時間管理の仕組みです。スケジュールテーブルはAUTOSAR OS仕様で追加された機能で、その仕様をベースに開発されたATK2から使えます。ATK1では使えません。本記事では概念の説明としてどちらも取り上げますが、実際に使える環境が異なる点に注意してください。
どちらも「時間をトリガーに何かを実行する」という点では同じですが、用途と特性が異なります。
カウンタ ― 時間の基準
アラームもスケジュールテーブルも、カウンタという仕組みの上に成り立っています。カウンタはハードウェアタイマーの割り込みなどを基準にインクリメントされる値で、時間の基準となります。
ハードウェアタイマー割り込み(例:1ms周期)
↓
カウンタのインクリメント
↓
アラーム・スケジュールテーブルの判定
カウンタの1ティックが何msに対応するかはシステムの設定によります。
アラーム ― 単純な周期・遅延処理
アラームは指定したカウンタ値に達したときに、タスクの起動やイベントの設定などのアクションを実行する仕組みです。
相対アラーム:現在のカウンタ値からの相対的な時間後に発火します。
SetRelAlarm(alarm_sensor, 10, 10);
/* 10ティック後に発火し、以降10ティックごとに繰り返す */
絶対アラーム:カウンタの絶対値が指定値に達したときに発火します。
SetAbsAlarm(alarm_control, 100, 50);
/* カウンタが100になったときに発火し、以降50ティックごとに繰り返す */
アラームはシンプルで使いやすいですが、複数の処理を決まった順序で実行したい場合には管理が煩雑になることがあります。
スケジュールテーブル ― 複数処理の時間的な整合
スケジュールテーブルは、1周期の中で複数の処理を決まったタイミングで実行するための仕組みです。
例えば「0msでタスクAを起動、10msでタスクBを起動、20msでタスクAとCを起動、50msで1周期終了して繰り返す」という処理を定義できます。
0ms 10ms 20ms 50ms(1周期)
│ │ │ │
タスクA タスクB タスクA ← 繰り返し
タスクC
アラームで同じことをしようとすると、複数のアラームを独立して管理する必要があり、処理間のタイミングの整合性を保つのが難しくなります。スケジュールテーブルなら1つの定義で管理できます。
アラームとスケジュールテーブルの使い分け
| アラーム | スケジュールテーブル | |
|---|---|---|
| 適した用途 | 単一処理の周期・遅延起動 | 複数処理の時間的な整合 |
| 設定のしやすさ | シンプル | やや複雑 |
| 動的な変更 | 可能 | 限定的 |
| 処理間のタイミング整合 | 難しい | 得意 |
単純な周期処理にはアラーム、複数の処理を決まった時間関係で動かしたい場合はスケジュールテーブルが向いています。
まとめ
- イベントはタスクへの通知に使う。拡張タスクのみが使える
- リソースは共有データの排他制御に使う。PCPによりデッドロックが防止される
- メッセージはデータの受け渡しに使う
- 時間管理の基準はカウンタ。ハードウェアタイマーをもとにインクリメントされる
- アラームは単純な周期・遅延処理に、スケジュールテーブルは複数処理の時間的な整合に向いている(スケジュールテーブルはATK2以降のみ)
次回(設計編①)は割り込み管理を取り上げます。ISR1とISR2の違いと使い分けを整理します。