【第4回】Zephyr OSのスレッド間通信!従来のRTOSとどう違う?セマフォ・ミューテックス・メッセージキューをPOSIXライクに使いこなす

この記事は約6分で読めます。

こんにちは!「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_mutex1個だけの所有権付きの鍵共有変数や通信バスの完全排他制御(優先度逆転防止つき)
セマフォk_semN個まで持てる「カウンター付きの鍵」複数リソースの数量管理、タスク間の同期・イベント通知
メッセージキューk_msgqFIFOバッファ+自動同期待機スレッド間でのデータ構造体そのものの送受信&同期イベント通知

① ミューテックス(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を活用することで、コード量が劇的に減り、バグの少ない高度な組み込みシステムが開発できます。

コメント

タイトルとURLをコピーしました