RTOSのIPC(セマフォ・ミューテックス・キュー)
セマフォとミューテックスの違いを即答できる。同期・相互排除・通信を切り分け、優先度継承やキューの動作、ISRでの禁じ手を原理で押さえる。
- 1.同じ待ち合わせでも目的が違う。セマフォは事象の同期(誰が返してもよい)、ミューテックスは資源の相互排除(取った者だけが返す・所有権あり)で、この所有権の有無が優先度継承の可否を分ける。
- 2.ミューテックスは優先度継承つきを使い、優先度逆転によるブロック時間を有界化する。キューはコピー渡しでデータ競合そのものを消し、生産者と消費者を疎結合にする。
- 3.ISRからは待てない(ブロックできない)。ISR文脈では give/send の非ブロッキング版だけを使い、専用APIでコンテキストスイッチを遅延させて末尾で一括で切り替える。
同期・相互排除・通信を切り分ける
RTOS(リアルタイムOS)上では複数のタスクが並行して走り、割り込み(ISR)とも協調します。このとき必要になるのが、タスク間の待ち合わせと受け渡しの仕組み——IPC(Inter-Process Communication、プロセス間通信) です。組込みで使う語彙はセマフォ・ミューテックス・メッセージキュー・イベントフラグと少数ですが、混同すると優先度逆転やデータ破壊を招きます。まず整理すべきは、それぞれが解く問題が違うことです。
| プリミティブ | 解く問題 | 所有権 | 典型用途 |
|---|---|---|---|
| バイナリセマフォ | 同期(事象の通知) | なし | ISR→タスクの起床、割り込み待ち |
| カウンティングセマフォ | 資源数の管理・イベント計数 | なし | 空きバッファ数、到着イベントの取りこぼし防止 |
| ミューテックス | 相互排除(クリティカルセクション) | あり | 共有変数・周辺デバイスの排他アクセス |
| メッセージキュー | 通信(データ受け渡し+同期) | - | 生産者・消費者、コマンド配送 |
| イベントフラグ | 複数条件の待ち合わせ | - | 複数イベントのAND/OR待ち |
RTOSの内部では、これらはいずれも待ち行列(ブロックされたタスクのリスト)とスケジューラの連携で実装されます。資源が得られないタスクをレディキューから外してブロック状態にし、条件が満たされたら待ち行列の先頭(多くは待機タスク中の最高優先度)を起こす。土台となる優先度ベースのプリエンプティブスケジューリングは /embedded/rtos-scheduling-rms-edf/ で扱った通りです。
セマフォ:カウンタによる同期
セマフォはダイクストラが定式化した、内部にカウンタを持つ整数です。操作は2つだけ——take(P操作、待って減算)と give(V操作、加算して起床)です。
take(): give():
もし count > 0: count = count + 1
count = count - 1 もし待ち行列にタスクあり:
続行 先頭タスクを起床(レディへ)
そうでなければ:
呼び出しタスクをブロック
(count が正になるまで待つ)
バイナリセマフォはカウンタが0か1のみで、事象が起きたか否かのフラグとして働きます。典型は割り込みとの同期です。ISRがデータを用意して give し、待っていた処理タスクが take で起きて後半処理を行う——/embedded/interrupt-handling-isr/ で述べた前半/後半分離を、セマフォで実現する形です。
カウンティングセマフォはカウンタが上限まで増えます。ここが重要な設計点で、give された回数を落とさずに数えられます。DMA完了やパケット到着が連続で起きても、バイナリセマフォなら2回目以降の give は飽和して失われますが、カウンティングセマフォなら回数ぶん take で回収でき、イベントの取りこぼしを防げます。空きバッファ数のような「資源の在庫数」を表すのにも使います。
セマフォの give は誰が呼んでもかまいません。ISRが give してタスクが take する、という非対称な使い方が正当なのはこのためです。裏を返すと、セマフォは「今この資源を誰が握っているか」を知りません。だから相互排除にセマフォを流用すると、後述の優先度継承が働かず、優先度逆転を有界化できません。相互排除には必ずミューテックスを使います。
ミューテックスと優先度継承
ミューテックス(相互排除) は、共有資源を一度に1タスクだけが使うためのロックです。カウンタが常に1のバイナリセマフォと構造は似ていますが、決定的な違いが所有権です。ミューテックスは lock したタスクだけが unlock でき、「今どのタスクが握っているか」を保持します。この所有者情報があるからこそ、優先度継承が実装できます。
なぜ所有権が要るのか。共有資源が絡むと優先度逆転(priority inversion) が起きるからです。低優先度タスク L がミューテックスを保持中に高優先度タスク H が同じロックを要求してブロックされる。ここで中間優先度のタスク M が現れると、M は L をプリエンプトして走り続け、L はロックを解放できず、結果 H は M の都合で青天井に待たされます。優先度の序列が事実上逆転するのです。この現象が1997年の火星探査機 Mars Pathfinder のリセット多発を引き起こしたのは有名な事例です。
対策は、ロックを保持する L の優先度を、待っている H の優先度まで一時的に引き上げることです。L が「H の優先度を継承」すれば、中間タスク M は L をプリエンプトできなくなり、L は速やかにクリティカルセクションを抜けてロックを返せます。L が unlock した瞬間、L の優先度は元に戻り、H が資源を得ます。これによりブロック時間が「L のクリティカルセクション実行時間」で有界化され、応答時間解析に組み込めるようになります。所有者を知らないセマフォでは、誰の優先度を上げるべきか特定できず、この仕組みが原理的に成り立ちません。
ミューテックスにはもう一つ再帰(recursive)版があり、同じタスクが同じロックを入れ子で複数回取得できます。取得回数をカウントし、同数の unlock で初めて解放します。ただしISRからは絶対に使えません。ISRには「呼び出しタスク」という概念がなく、所有権を持てないためです。
| 観点 | バイナリセマフォ | ミューテックス |
|---|---|---|
| 目的 | 同期(事象通知) | 相互排除(資源保護) |
| 所有権 | なし(誰でも give 可) | あり(lock した者のみ unlock) |
| 優先度継承 | 不可 | 可(優先度逆転を有界化) |
| ISRからの操作 | give は可 | 不可(所有者を持てない) |
| 再帰取得 | 非対応 | 再帰ミューテックスなら可 |
メッセージキュー:コピー渡しで競合を消す
同期だけでなくデータそのものを渡したいならメッセージキューです。キューは固定長のリングバッファで、send(末尾へ追加)と receive(先頭から取り出し)を提供します。満杯での send と空での receive は、それぞれ空き/データができるまでブロックできます。つまりキューは通信と同期を同時に担います。
キューの本質的な利点は、多くのRTOS実装が値コピー(by copy) でメッセージを受け渡す点にあります。送信側のデータはキュー内の領域へ丸ごとコピーされ、受信側は自分のバッファへコピーして取り出します。送受信が同じメモリを同時に触ることがないため、ミューテックスで別途保護しなくてもデータ競合が原理的に起きません。生産者と消費者は、相手の実行タイミングもポインタの寿命も気にせず疎結合になれます。
[生産者・消費者をキューで疎結合にする]
生産者タスク:
msg = センサ値をパック
queue_send(q, &msg) # 満杯ならブロック。中身はコピーされる
消費者タスク:
queue_receive(q, &out) # 空ならブロックして待つ
処理(out) # 自分のコピーを安全に扱える
大きなデータをコピーしたくない場合はポインタだけを送る設計も可能ですが、その瞬間に「指した先の所有権をいつ誰が解放するか」という問題が復活し、コピー渡しの安全性は失われます。速度と安全性のトレードオフとして意識的に選ぶべき箇所です。
イベントフラグ:複数条件の待ち合わせ
イベントフラグ(イベントグループ) は、1つのワード内の各ビットをそれぞれ独立したイベントとして扱う仕組みです。タスクは複数ビットの組み合わせを待てます。ここでセマフォやキューと決定的に違うのは、AND待ちとOR待ちを表現できることです。
- OR待ち:指定ビットのいずれか1つが立てば起床(例
AまたはBのどちらか到着で進む)。 - AND待ち:指定ビットが全部揃って初めて起床(例
AとBの両方が揃うまで待つ)。
「センサXとセンサYの両方のデータが揃ったら計算を始める」のように、複数の前提条件を1回の待ち合わせにまとめられるのが強みです。セマフォを条件の数だけ並べて逐次 take する実装に比べ、待ち合わせのロジックが単純になり、途中で片方だけ取得して中途半端にブロックする事故も避けられます。
「起きたか/起きてないか」を待つだけならバイナリセマフォ。「何回起きたか・在庫はいくつか」を数えるならカウンティングセマフォ。「資源を排他で使う」ならミューテックス(優先度継承つき)。「データを渡す」ならキュー。「複数条件のAND/OR」ならイベントフラグ。目的から逆引きすると迷いません。
ISRからの利用:待てないという制約
最後に、すべてのプリミティブに共通する落とし穴がISR(割り込み文脈)からの利用です。原則は単純で、ISRの中では絶対にブロックしてはいけません。ISRには対応するタスク制御ブロックがなく、ブロックして待ち行列に入れる「呼び出しタスク」が存在しないからです。空のキューで receive、ロック済みミューテックスで lock といった「待つ可能性のある操作」は、ISRから呼ぶとシステムが破綻します。
したがってISRから使えるのは、待たずに済む片方向の操作だけです。バイナリ/カウンティングセマフォの give、キューの send(満杯なら失敗を返すだけでブロックしない)、イベントフラグのセット——いずれもタスクを起こす向きの操作です。ミューテックスは所有権が必要なため、ISRからは一切使えません。
多くのRTOSはISR用に別系統のAPI(FreeRTOSなら末尾に FromISR が付く関数群)を用意します。これらは内部でスケジューラのロック方法が異なり、通常APIと混同するとクリティカルセクションが壊れます。さらに重要なのがコンテキストスイッチの扱いです。ISR内で高優先度タスクを起こしても、その場で切り替えず「切り替えが必要か」のフラグだけを受け取り、ISRを抜ける直前にまとめてスケジューラへ渡します(FromISR 系が返す higherPriorityTaskWoken を末尾で yield に渡す作法)。こうしてISR内での不用意なスイッチを避け、割り込み応答(レイテンシ)と最悪実行時間の見積もりを崩さないようにします。
この「ISRは give/send して即抜け、重い処理と待ち合わせはタスク側」という構図は、割り込みの前半/後半分離そのものです。ブロック時間をこうして有界に保つことが、締切を保証する最悪実行時間解析——/embedded/real-time-wcet/ の前提になります。
最頻出は「セマフォとミューテックスの違い」で、答えは『セマフォは所有権のない同期用(誰でも give 可・ISRから使える)、ミューテックスは所有権のある相互排除用(lock した者のみ unlock・優先度継承が効く)』。「カウンティングセマフォの利点」は『イベントを計数して取りこぼしを防ぐ』。「なぜミューテックスに優先度継承が実装できるか」は『所有者を保持しているから、誰の優先度を上げるべきか特定できる』。「ISRでやってはいけないこと」は『ブロックする操作(空キューのreceive等)と、専用のFromISR系を使わないこと』を押さえます。
まとめ
RTOSのIPCは、同期・相互排除・通信という別々の目的に、それぞれ最適化されたプリミティブを当てる体系です。セマフォは所有権を持たないカウンタで事象を同期し(バイナリは有無、カウンティングは計数)、ミューテックスは所有権ゆえに優先度継承でブロック時間を有界化し、キューはコピー渡しでデータ競合を根絶して生産者・消費者を疎結合にし、イベントフラグは複数条件のAND/OR待ちをまとめます。そしてISRからは待てないという制約が全体を貫き、片方向の非ブロッキング操作と遅延コンテキストスイッチで応答時間を守ります。この使い分けを目的から逆引きできれば、優先度逆転もデータ破壊も設計段階で潰せます。より低レイヤーの割り込み機構は /embedded/interrupt-handling-isr/、OSを持たない場合の同期設計は /embedded/bare-metal-firmware/ と併せて理解すると立体的になります。
組込み・IoT Article
RTOSのIPC(セマフォ・ミューテックス・キュー)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
RTOS
比較で見る軸
難易度: advanced / カテゴリ: 組込み・IoT / タグ数: 6
導入後に効く点
ミューテックスは優先度継承つきを使い、優先度逆転によるブロック時間を有界化する。キューはコピー渡しでデータ競合そのものを消し、生産者と消費者を疎結合にする。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- 組込み・IoT
- タグ数
- 6
判断チェックリスト
- 自社の用途が「RTOS / 組込み」に近いか確認する。
- 強みである「同じ待ち合わせでも目的が違う。セマフォは事象の同期(誰が返してもよい)、ミューテックスは資源の相互排除(取った者だけが返す・所有権あり)で、この所有権の有無が優先度継承の可否を分ける。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。