はじめに
RTOSの概念を理解しても、「実際にどうタスクを設計すればいいか」という判断は難しいものです。タスクの分割粒度・優先度の決め方・スタックサイズの見積もりは、設計の質に直結します。
今回は設計編の締めくくりとして、タスク設計の勘所を整理します。
タスク分割の考え方
タスクを分割する基準は主に3つです。
処理の周期・応答性の要求が異なる
「1msごとに実行する制御演算」と「100msごとに実行する状態監視」は、要求される応答性が大きく異なります。同じタスクに混在させると、短周期の処理が長周期の処理に引きずられてしまいます。周期や応答性の要求が異なる処理は別タスクに分けるのが基本です。
独立して動作できる処理単位
「センサー入力タスク」「制御演算タスク」「アクチュエータ出力タスク」のように、機能的に独立した単位でタスクを分けると設計がわかりやすくなります。
共有リソースへのアクセスパターン
同じ共有データにアクセスする処理をまとめることで、リソース管理の複雑さを減らせる場合があります。
一方でタスクを細かく分けすぎると、コンテキストスイッチのオーバーヘッドが増え、タスク間の同期処理が複雑になります。「必要な数だけ、それ以上は増やさない」という判断が重要です。
優先度の決め方
OSEK/ATK1の優先度設計には、いくつかの指針があります。
応答性の要求が高いタスクほど優先度を高くする
ハードウェアの制御など、リアルタイム性が求められる処理は高優先度にします。バックグラウンドの管理処理や診断処理は低優先度にします。
周期が短いタスクほど優先度を高くする
一般的に周期が短い処理はそれだけ応答性の要求が高いため、優先度を高くします。これはRate Monotonic(RM)スケジューリングの考え方を参考にした指針です。
優先度逆転に注意する
低優先度タスクがリソースを保持しているとき、高優先度タスクがそのリソースを待たされる現象を優先度逆転と呼びます。OSEK/ATK1のPCP(優先度上限プロトコル)はこれを防ぐ仕組みです。PCPはリソース取得時にタスクの優先度を天井優先度まで引き上げることで優先度逆転を防ぎます。リソースを使う場合は優先度設定に注意が必要です。
優先度は静的に決まる
OSEK/ATK1の優先度はOILファイルで静的に定義されます。実行中に動的に変更することはできません。設計段階でしっかり決めておく必要があります。
スタックサイズの見積もり
各タスクはそれぞれ独立したスタックを持ちます。スタックサイズが不足するとスタックオーバーフローが発生し、デバッグが非常に困難なバグの原因になります。
スタックサイズの見積もりは以下の要素を考慮します。
関数呼び出しのネスト
タスク内で呼び出す関数のチェーンで使用されるローカル変数と戻り番地の合計がスタック消費量の基本です。
割り込みのネスト
実装によっては、タスクの実行中に割り込みが発生すると割り込みハンドラのスタックフレームがタスクスタックに積まれる場合があります(実装依存)。
コンテキストスイッチ時のレジスタ保存
コンテキストスイッチの際にCPUレジスタが保存されるため、その分のスタックも必要です。
実際には計算で正確に求めるのは難しいので、多めに確保してからスタック使用量を計測して調整するアプローチが現実的です。TOPPERS/ATK1にはスタック使用量を確認するデバッグ機能があります。
よくある設計ミス
タスク数を増やしすぎる
「機能ごとにタスクを作る」という発想で増やしすぎると、スタックのメモリ消費とコンテキストスイッチのオーバーヘッドが無視できなくなります。
全タスクを同じ優先度にする
「どれが重要かわからないから全部同じにする」という設計は、RTOSを使う意味を半減させます。応答性の要求を明確にして優先度を設計してください。
スタックサイズを小さくしすぎる
メモリ節約のためにスタックを削りすぎると、スタックオーバーフローのリスクが高まります。まず安全側に多めに確保し、計測して適正値に調整するのが無難です。
まとめ
- タスク分割は周期・機能・共有リソースのアクセスパターンを基準にする
- 優先度は応答性の要求と周期を基準に決める。優先度逆転に注意
- スタックサイズは多めに確保してから計測・調整する
- タスク数は必要最小限に抑える
次回からはAPI編に入ります。前編ではタスク管理APIと同期APIを取り上げます。