優先度逆転を「再現→検出→対策」まで

優先度逆転は、RTOSを使った組み込み開発で発生しうる代表的な不具合の要因の一つです。高優先度タスクが低優先度タスクに実行を阻害されるこの現象は、原因を把握していないと発見と対処が難しくなります。

症状がウォッチドッグタイムアウトやシステムリセットとして表れることが多く、優先度逆転が根本原因だと気付きにくいところが厄介な点です。この記事では、優先度逆転の仕組みから検出方法・対策・設計段階での予防策まで解説していきます。

RTOSの優先度逆転とは

低優先度タスクが共有リソースを保持中に中優先度タスクが割り込み、高優先度タスクが待機状態となる「優先度逆転」のメカニズムを示す図

優先度逆転とは、低優先度タスクが共有リソースを保持している間に中優先度タスクが割り込み、低優先度タスクがCPUを奪われてリソースを解放できなくなることで、高優先度タスクが間接的に実行を阻害される現象です。

名前の通り「優先度の高いタスクが優先度の低いタスクよりも後に実行される」という逆転した状態が発生します。適切な対策をとらなければ、デッドライン超過やシステムの応答停止につながります。

この現象はミューテックスによる排他制御を使う場面で起きやすく、3つのタスクが特定のタイミングで重なったときにだけ発生します。タイミング依存のバグであるため、テスト環境では再現しにくく、実機の稼働中に突然表れるケースも少なくありません。すなわち、再現と修正が難しいバグとなります。

RTOSで優先度逆転が起きるメカニズムと事例

優先度逆転がどのようなシナリオで発生するか、具体的なタスク構成で見ていきましょう。

3タスク構成での典型シナリオ

低(L)・中(M)・高(H)の3タスクとミューテックス1つがある構成を考えます。本来はH→M→Lの優先度順で実行されるべきですが、以下のシナリオで逆転が起きます。

  1. LがミューテックスMを取得して処理を開始する
  2. HがMを必要として取得を試みるが、Lが保持中のためブロックされる
  3. Lより優先度の高い Mが割り込み、LがCPUを奪われてミューテックスを解放できなくなる
  4. HはLの解放を待ち続けHよりも先にMが実行される

このとき「中優先度タスクMが高優先度タスクHより先に実行される」という優先度逆転が発生しています。

実例:Mars PathfinderでのRTOS障害

1997年のNASA火星探査機Mars Pathfinderでは、優先度逆転によりウォッチドッグタイムアウトが連発してシステムリセットを繰り返す問題が発生しました。使用していたVxWorksのミューテックスで優先度継承が有効化されていなかったことが原因でした。

飛行中のソフトウェアパラメータ変更で修正されたことで有名な事例です。修正自体は、問題となったミューテックスの優先度継承フラグを有効化することで完了しました。

しかし、原因特定には数日間の解析を要しており、優先度逆転の診断がいかに難しいかを示す事例でもあります。

RTOSの優先度逆転の検出方法

優先度逆転は通常のデバッグでは気付きにくいものです。以下の手段を組み合わせること等により、デバッグをしていけるようにします。

トレースツールでタスクタイムラインを可視化する

TracealyzerやSystemViewなどのRTOSトレースツールを使うと、高優先度タスクがブロックされている区間をタイムライン上から一目で特定できます。どのタスクが何のリソースを待っているかも可視化されます。

そのため、トレースツールの利用は優先度逆転の発見に最も効果的な手段の一つです。

FreeRTOSではTracealyzer(レコーダーライブラリ経由)や、SystemView(Segger RTT経由)など可視化ツールとの連携が可能です。いずれもRTOSのフック関数に計装を追加するだけで利用でき、コードへの変更量を少なくし、デバッグ用の動作が実動作と大きく変わることを防ぎます。

ログとタイムスタンプで手動解析する

トレースツールが使えない環境では、ミューテックス取得・解放のタイムスタンプをリン グバッファなどに記録し、ログを解析することで逆転区間を特定することが可能です。処理の前後にタイムスタンプを記録し、高優先度タスクのブロック時間が異常に長くなっていないか確認します。

リングバッファを使う場合、記録する情報はタスクID・リソースID・タイムスタンプの3点を最低限とし、バッファのオーバーフロー時に古いデータを上書きする方式にするとメモリ消費を抑えられます。ただし、ログ出力自体がリアルタイム性に影響することがあるため、出力処理はISRではなく専用のログタスクに委ねる設計が安全です。

RTOSの優先度逆転への対策

優先度逆転への対策は主に2つのプロトコルを利用します。それぞれの動作と注意点を理解した上で選択することが重要です。

優先度継承:ミューテックスを待つ間だけ低優先度タスクの優先度を引き上げる

高優先度タスクがミューテックスを待機すると、保持している低優先度タスクの優先度が一時的に高優先度タスクと同じ水準まで引き上げられます。FreeRTOSではxSemaphoreCreateMutexで作成したミューテックスがこの機能を持ちます。

ただし、優先度の動的な変更を伴うため、優先度管理が複雑になる点に注意です。設計・デバッグの難易度が上がります。適用する際は、優先度変更が他のタスクのスケジューリングに与える影響を十分に検討しましょう。

優先度上限プロトコル:ミューテックス取得と同時に優先度を引き上げる

アクセスする全タスクの中で最高の優先度を上限値として設定し、ミューテックスを取得した瞬間に保持タスクの優先度をその上限値まで引き上げることで優先度逆転を確実に防ぎます。ただし、FreeRTOSは本プロトコルを標準サポートしていません。

車載分野で広く使われるOSEK/AUTOSARではStack-based PCPが仕様として規定されており、これらに準拠したRTOSでの利用が前提となります。

設計段階での予防策

優先度逆転は検出・対処も大切ですが、設計段階で発生しにくい構造を作ることがより効果的です。

ミューテックス保持中にブロッキング処理を入れない

ミューテックスを保持したままブロックすると、ミューテックスが長時間解放されず他タスクが待たされます。保持区間は最短に保ち、ブロッキング処理はミューテックスの外に出す設計を徹底しましょう。

複数ミューテックスは取得順序を統一する

複数のミューテックスを異なる順序で取得するとデッドロックを引き起こします。全タスクでミューテックスの取得順序を統一し、設計ドキュメントに明記しておくことが重要です。

たとえば、Mutex_A→Mutex_Bの順を守るなど、取得順序を統一します。

ISR内ではミューテックスを使わない

ミューテックスは優先度継承機構がタスクコンテキストを必要とするため、ISRからは使用できません。ISRからタスクに通知する場合はバイナリセマフォやキューを使用します。

こうすることで、待機中のタスクをISR終了後に即座に起動でき、「割り込み検知はISR・実処理はタスク」という安全な分業が実現します。

まとめ

優先度逆転は「低優先度タスクが共有リソースを持ったまま中優先度タスクに割り込まれる」という3タスク構成で発生します。Tracealyzerなどのトレースツールやログによるタイムスタンプ解析で検出し、FreeRTOSであれば優先度継承ミューテックスを活用することが現実的な対策です。

また、設計段階でミューテックス保持時間の最小化・取得順序の統一・ISR内でのミューテックス不使用を徹底することで、そもそも発生しにくい構造を作ることが最善策です。

組み込みソフトの世界 トップへ戻る