こんにちは!「Zephyr OS入門」連載の第4回です。
[第3回:マルチスレッド徹底解剖] では、Zephyr OSのマルチスレッドの基本やラウンドロビン(タイムスライス)の動き、一般的なRTOSとの違いについて解説しました。
マルチスレッドプログラミングで必ず直面するのが、「スレッド同士でどうやって安全にデータをやり取り(同期・排他制御)するか?」 という課題です。
特にFreeRTOSやµITRONなどの一般的なRTOSに慣れた開発者ほど、Zephyr OSのスレッド間通信に触れると「良い意味で設計思想が全然違う!」と驚くはずです。
今回は、Zephyr OSが提供する代表的な通信メカニズムである「ミューテックス」「セマフォ」「メッセージキュー」に加えて、Linux/POSIX経験者にはお馴染みのselect / poll / k_poll による複数イベント同期待機まで一気に解説します!
1. 従来のRTOS経験者が知っておくべき「設計思考の切り替え」
まず押さえておきたいのが、Zephyr OSはLinux Foundation主導ということもあり、「マイコンの上で動く極小のPOSIX/Linux環境」に近い思想で作られているという点です。
| 設計の局面 | 従来のRTOS (例: FreeRTOS / µITRON) | Zephyr OS (POSIX / Linux的アプローチ) |
| 設計スタイル | ベアメタル(直叩き)の延長 割り込みとタスクを泥臭く組み、自前でバッファやセマフォを組合わせる | 小型Linuxアプリ開発 OSが用意した同期オブジェクトに処理を任せ、イベント駆動でスマートに組む |
| 同期・通信 | キューやセマフォの組み合わせが基本 | メッセージキュー単体で「データ受け渡し+イベント同期待機」まで完結 |
| 複数イベント監視 | 異種オブジェクトを束ねて待つのは苦手 | k_poll() や POSIXの select() / poll() で一括同期待機 |
「ベアメタルのノウハウで泥臭く作り込む」のではなく、「POSIXライクなOSの機能を信用して任せる」という頭の切り替えが、Zephyr OSを使いこなす最大のカギになります。
2. 3つのオブジェクトの役割と決定的な違い
スレッド間通信の基本となる「ミューテックス」「セマフォ」「メッセージキュー」の役割分担を整理しましょう。
| オブジェクト | 英語表記 | 本質・イメージ | 主な用途 |
| ミューテックス | k_mutex | 1個だけの所有権付きの鍵 | 共有変数や通信バスの完全排他制御(優先度逆転防止つき) |
| セマフォ | k_sem | N個まで持てる「カウンター付きの鍵」 | 複数リソースの数量管理、タスク間の同期・イベント通知 |
| メッセージキュー | k_msgq | FIFOバッファ+自動同期待機 | スレッド間でのデータ構造体そのものの送受信&同期イベント通知 |
① ミューテックス(k_mutex):共有リソースの完全保護
ミューテックスは「同時に使える鍵が世界に1つだけ」のオブジェクトです。
k_mutex_lock() で鍵を借りたスレッド自身しか k_mutex_unlock() で鍵を返すことができません。さらに、バイナリセマフォにはない優先度継承機能(優先度逆転防止)が内蔵されているため、共有変数や通信バスの保護には必ずミューテックスを使用します。

② セマフォ(k_sem):数量管理とタスク間同期
セマフォは本質的に「内部でカウント(数字)を管理する仕組み」(複数持てるMutex)です。
- 数量管理: 初期カウントを
3などにして、「同時に3スレッドまでアクセスOK」というリソース管理を行う。 - タスク間同期: 初期カウントを
0にしておき、「処理Aが終わったらgiveして、待っていた処理Bを起動させる」というシグナル通知を行う。

3. Zephyr特有の強力な同期スタイル:メッセージキューと select / poll
ここからがZephyr OSの本領発揮です。
① メッセージキュー(k_msgq)は「データ添付付きの自動同期待機」
k_msgq_get(&sensor_msgq, &rx_data, K_FOREVER) を呼ぶと、データが無い場合は受信スレッドが完全スリープ(CPU使用率0%)のブロック同期待機状態に入ります。
送信側がデータを投入した瞬間にOSが自動でイベントを発行し、スレッドを叩き起こしてくれます。自前で「データ保存」と「セマフォ通知」を組み合わせる必要はありません。

② 複数イベントを1つのスレッドで待つ k_poll() と POSIX select() / poll()
一般的なRTOSでは「メッセージキュー」と「セマフォ」を1つのタスクで同時に待つのは困難ですが、ZephyrではPOSIXの poll() と同じ感覚で扱える k_poll() API が使えます。
#include <zephyr/kernel.h>
K_SEM_DEFINE(my_sem, 0, 1);
K_MSGQ_DEFINE(my_msgq, sizeof(int), 10, 4);
/* poll イベント構造体の配列 */
struct k_poll_event events[2] = {
K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_SEM_AVAILABLE, K_POLL_MODE_NOTIFY_ONLY, &my_sem, 0),
K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_MSGQ_DATA_AVAILABLE, K_POLL_MODE_NOTIFY_ONLY, &my_msgq, 0),
};
void main_thread(void)
{
while (1) {
/* セマフォまたはメッセージキューのどちらかにイベントが来るまでスリープ同期待機 */
k_poll(events, 2, K_FOREVER);
if (events[0].state == K_POLL_STATE_SEM_AVAILABLE) {
k_sem_take(&my_sem, K_NO_WAIT);
printk("セマフォイベント発生!\n");
}
if (events[1].state == K_POLL_STATE_MSGQ_DATA_AVAILABLE) {
int val;
k_msgq_get(&my_msgq, &val, K_NO_WAIT);
printk("メッセージキューにデータ到着: %d\n", val);
}
/* 次回監視用にステートリセット */
events[0].state = K_POLL_STATE_NOT_READY;
events[1].state = K_POLL_STATE_NOT_READY;
}
}さらに、ソケット通信やシリアル入力に対しては、POSIX標準の select() や poll() 関数もそのまま使用可能です!
4. まとめ
今回は、Zephyr OSにおけるスレッド間通信と、POSIXライクな同期待機モデルについて解説しました!
- ミューテックス(
k_mutex): 1個だけの鍵。共有リソースの完全保護(優先度逆転防止つき) - セマフォ(
k_sem): カウンター付きの鍵。数量管理やタスク間同期 - メッセージキュー(
k_msgq): データ受け渡し+イベント同期待機の一体型バッファ k_poll()/select(): 複数オブジェクトやソケットのイベントを一括同期待機
従来のRTOSのやり方に固執せず、「Linuxアプリを書くような感覚」でOSのAPIを活用することで、コード量が劇的に減り、バグの少ない高度な組み込みシステムが開発できます。

コメント