【第3回】Zephyr OSのマルチスレッド徹底解剖!一般的なRTOSとの違いとスケジューリングの仕

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

こんにちは!「Zephyr OS入門」連載の第3回です。

[第2回:開発環境構築とHello World] では、WSL2(Ubuntu)とQEMU仮想環境を使って、Zephyr OS上で最初のプログラムを動かす手順を解説しました。

今回は、RTOS(リアルタイムOS)の真骨頂である「マルチスレッド(マルチタスク)機能」を深掘りします。

「一般的なRTOS(FreeRTOSなど)と何が違うの?」

「優先度やラウンドロビンってどういう仕組みで動いているの?」

そんな疑問をクリアにしながら、図解を交えて分かりやすく解説していきます!

1. Zephyr OSにおける「スレッド」の基本概念

Zephyr OSでは、実行される処理の単位をスレッド(Thread)と呼びます。(他のRTOSでは「タスク」と呼ばれることが多いですが、意味合いはほぼ同じです。)

各スレッドには優先度(Priority)が割り振られており、スケジューラが優先度の高いスレッドを優先的に実行する「プリエンプティブ・スケジューリング」が基本となります。

時系列の流れで示すと、優先度の高いスレッド(Thread A)が起動した瞬間に、実行中だったスレッド(Thread B)の中断(割り込み)が発生します。

2. 一般的なRTOS(FreeRTOSなど)との決定的な違い

「RTOSならどれも似たようなものでは?」と思われがちですが、Zephyr OSには標準機能として組込まれた独自の設計思想が存在します。

機能・比較項目一般的なRTOS (例: FreeRTOS)Zephyr OS
スレッドの種類単一のスレッド種別
(Priorityのみ)
Cooperative Thread / Preemptive Thread を明確に分離
動的メモリ割り当てpvPortMalloc 等でヒープから動的確保が標準的静的割り当て(K_THREAD_DEFINE)が標準で極めて堅牢
同期・通信メカニズムQueue, Semaphore, Event GroupK_FIFO, K_LIFO, K_SEM, K_MSGQ, Workqueue 等が超充実
システム設定FreeRTOSConfig.h
(マクロヘッダー手作業編集)
Kconfig
(Linuxライクな視覚的・構造的設定)

大きな違い:静的定義(K_THREAD_DEFINE)の安全性

Zephyrでは、コンパイル時にスレッドのスタック領域やIDを固定化する K_THREAD_DEFINE マクロが提供されています。これにより、実行時のメモリ不足(ヒープ枯渇)による予期せぬクラッシュを回避できる設計になっています。

3. 同一優先度でどう動く?「ラウンドロビン(Round-Robin)」とは

複数のスレッドが同じ優先度を持っている場合、OSはどのように処理を分配するのでしょうか?

ここで登場するのが「ラウンドロビン(Round-Robin)」スケジューリングです。

💡 ラウンドロビンとは?

同じ優先度を持つスレッド同士に、一定の時間(タイムスライス)を順番に割り当て、円を描くようにくるくると実行権を交代していく仕組みです。特定のタスクが処理を占有するのを防ぎます。

タイムスライスの動きを時系列で見る

時間が上から下へ進むにつれて、タイムスライス(例: 10ms)ごとに Thread 1 と Thread 2 へ交互にCPU実行権が切り替わっていく様子がよく分かります。

Zephyr OSでは CONFIG_TIMESLICING=yCONFIG_TIMESLICE_SIZE=10(デフォルト10msなど)のKconfig設定を追加するだけで、簡単にラウンドロビンを有効化・カスタマイズできます。

4. 実際に動かしてみよう!マルチスレッドのコード例

QEMU上でもそのまま動かせる、2つのスレッド(LED点滅を模した処理と、シリアル出力処理)を静的に生成するシンプルなサンプルコードです。

#include <zephyr/kernel.h>

/* スタックサイズの定義 */
#define STACK_SIZE 1024

/* スレッド1の優先度(Preemptive: 正の整数) */
#define THREAD1_PRIORITY 7
#define THREAD2_PRIORITY 7

/* スレッド1の処理関数 */
void thread1_entry(void *p1, void *p2, void *p3)
{
    while (1) {
        printk("Thread 1 is running (High/Equal Priority)\n");
        k_msleep(1000); // 1秒スリープして他のスレッドへ実行権を譲る
    }
}

/* スレッド2の処理関数 */
void thread2_entry(void *p1, void *p2, void *p3)
{
    while (1) {
        printk("  --> Thread 2 is running\n");
        k_msleep(1000);
    }
}

/* スレッドの静的定義 (名前, スタックサイズ, エントリ関数, 引数1,2,3, 優先度, オプション, 起動遅延) */
K_THREAD_DEFINE(thread1_id, STACK_SIZE, thread1_entry, NULL, NULL, NULL,
                THREAD1_PRIORITY, 0, 0);

K_THREAD_DEFINE(thread2_id, STACK_SIZE, thread2_entry, NULL, NULL, NULL,
                THREAD2_PRIORITY, 0, 0);

ポイント解説

  1. K_THREAD_DEFINE: main() 関数の中で k_thread_create() を呼ばなくても、OS起動時に自動でスレッドが生成・開始されます。
  2. k_msleep(): 指定したミリ秒間スレッドを「Wait状態」にします。この間、CPUは別の「Ready状態」スレッドに切り替わるため、無駄なCPU消費が発生しません。

5. まとめ

今回は、Zephyr OSのマルチスレッド機能の基礎について解説しました!

  • 静的生成(K_THREAD_DEFINE が標準で、実行時のメモリ安全性に優れている
  • 一般的なRTOSに比べて通信・同期オブジェクト(FIFO, Workqueue等)の標準ライブラリが非常に豊富
  • ラウンドロビン(タイムスライス) により、同一優先度のタスク間でも均等にCPUリソースを分配可能

次回は、スレッド同士で安全にデータをやり取りするための 「タスク間通信(ミューテックス・セマフォ・メッセージキュー)」 について解説します!お楽しみに!

コメント

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