こんにちは!「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 Group | K_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=y や CONFIG_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);ポイント解説
K_THREAD_DEFINE:main()関数の中でk_thread_create()を呼ばなくても、OS起動時に自動でスレッドが生成・開始されます。k_msleep(): 指定したミリ秒間スレッドを「Wait状態」にします。この間、CPUは別の「Ready状態」スレッドに切り替わるため、無駄なCPU消費が発生しません。
5. まとめ
今回は、Zephyr OSのマルチスレッド機能の基礎について解説しました!
- 静的生成(
K_THREAD_DEFINE) が標準で、実行時のメモリ安全性に優れている - 一般的なRTOSに比べて通信・同期オブジェクト(FIFO, Workqueue等)の標準ライブラリが非常に豊富
- ラウンドロビン(タイムスライス) により、同一優先度のタスク間でも均等にCPUリソースを分配可能
次回は、スレッド同士で安全にデータをやり取りするための 「タスク間通信(ミューテックス・セマフォ・メッセージキュー)」 について解説します!お楽しみに!

コメント