概要
タスク(Task)またはスレッド(Thread)は、CPUが独立して実行できる処理の最小単位です。複数のタスクがスケジューラによって管理され、時分割または優先度ベースで実行されることで、一つのCPUコアが複数の処理を「並行して」動かしているように見えます。
組み込み分野では、RTOSの文脈でよく「タスク」と呼ばれます。LinuxなどのOS文脈では「スレッド」や「プロセス」が使われますが、組み込みRTOSでは保護メモリ空間を持たないことが多く、全タスクが同一のアドレス空間を共有します。
各タスクは独自のスタック(ローカル変数・関数呼び出し履歴の格納域)、プログラムカウンタ(現在の実行位置)、レジスタの値を持ちます。タスクが切り替わるとき、これらの情報が保存・復元されます(コンテキストスイッチ)。
タスク設計は組み込みシステムの応答性・安定性に直結します。各タスクの役割を明確に分離し、優先度を適切に設定することがリアルタイム性確保の基本です。
歴史・背景
マルチタスクという概念はメインフレーム時代(1960年代)に生まれましたが、組み込みシステムへの応用は1980年代以降です。リソース制約の厳しいマイコンでは、シングルタスクのスーパーループ(無限ループで各処理を順次実行)が長らく主流でした。
1980年代にVxWorks(Wind River社)やpSOS(Software Components社)などの商用RTOSが登場し、優先度ベースのマルチタスク機能を組み込み分野にもたらしました。
1993年に策定されたTOPPERS/ITRON(日本発のRTOS仕様)は国産家電・自動車電子機器への普及に貢献。同年のPOSIX.1b(リアルタイム拡張)ではスレッドAPI(pthreads)が標準化されました。
2000年代以降はFreeRTOS(2003年)がオープンソースで公開され、コスト・ライセンス面から急速に普及。2017年にAmazonがFreeRTOSを取得しAWS IoTとの統合を進め、現在はIoT組み込みの事実上の標準となっています。
技術仕様
タスクの状態遷移
RTOSのタスクは以下の状態を遷移します。
+-----------+
作成 | Ready | スケジューラが選択
------->| (実行可能) |---------------+
+-----------+ |
^ v
| +-----------+
| | Running |
| |(実行中) |
| +-----------+
| |
+-----------+ [ブロック条件]
| Blocked |<---------(待機・スリープ)
|(待機中) |
+-----------+
| [条件成立:キュー受信・タイムアウト]
+----> Ready へ
※Suspended(一時停止)状態を持つRTOSもある
FreeRTOSでのタスク仕様
| 項目 | 詳細 |
|---|---|
| 優先度 | 0(最低)〜configMAX_PRIORITIES-1(最高) |
| スタックサイズ | ワード単位で指定(例:512ワード = 2048バイト on 32bit) |
| スタック割り当て | 静的(xTaskCreateStatic)または動的(xTaskCreate) |
| タスク識別子 | TaskHandle_t |
| スケジューリング | 優先度ベース・プリエンプティブ(デフォルト) |
スタックサイズの設計
タスクのスタックサイズが不足するとスタックオーバーフローが発生し、メモリ破壊や不正動作を引き起こします。
スタックサイズの決定に影響する要因:
- ローカル変数のサイズ(配列・大きな構造体に注意)
- 関数呼び出しの深さ(再帰は特に危険)
- ISRからの通知による追加スタック
printf()等のライブラリ関数の内部スタック使用量(数百バイト)
FreeRTOSではuxTaskGetStackHighWaterMark()でスタックの使用済みピーク値を確認できます。
/* スタック使用量の確認 */
UBaseType_t remaining = uxTaskGetStackHighWaterMark(NULL);
/* remaining ワードが残り空き容量。小さすぎると危険 */
動作原理
タスクの作成と管理(FreeRTOS)
#include "FreeRTOS.h"
#include "task.h"
/* タスク関数の定義 */
void sensorTask(void *pvParameters) {
/* 初期化処理 */
TickType_t xLastWakeTime = xTaskGetTickCount();
for (;;) { /* 無限ループ(タスクは終了しないこと) */
float temp = readTemperature();
xQueueSend(tempQueue, &temp, 0);
/* 100ms周期で実行(ジッタを抑制) */
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(100));
}
}
void controlTask(void *pvParameters) {
float temp;
for (;;) {
if (xQueueReceive(tempQueue, &temp, pdMS_TO_TICKS(200)) == pdTRUE) {
adjustHeater(temp);
}
}
}
int main(void) {
SystemInit();
tempQueue = xQueueCreate(10, sizeof(float));
/* タスク作成:関数, 名前, スタック(ワード), 引数, 優先度, ハンドル */
xTaskCreate(sensorTask, "Sensor", 256, NULL, 2, NULL);
xTaskCreate(controlTask, "Control", 512, NULL, 3, NULL);
vTaskStartScheduler(); /* スケジューラ起動(戻らない) */
for (;;);
}
タスク間通信
タスク同士はキュー・セマフォ・イベントグループなどのRTOSオブジェクトで通信します。
/* キューによるタスク間データ転送 */
QueueHandle_t dataQueue;
void producerTask(void *pvParameters) {
uint32_t data = 0;
for (;;) {
data++;
/* キューへ送信(満杯なら10ms待機後に諦める) */
xQueueSend(dataQueue, &data, pdMS_TO_TICKS(10));
vTaskDelay(pdMS_TO_TICKS(50));
}
}
void consumerTask(void *pvParameters) {
uint32_t received;
for (;;) {
/* キューからデータを受け取るまで無限ブロック */
if (xQueueReceive(dataQueue, &received, portMAX_DELAY) == pdTRUE) {
processData(received);
}
}
}
タスク優先度の設計指針
優先度は処理のタイムクリティカル度に基づいて割り当てます。
高優先度(優先度:高)
├ モーター制御タスク(1ms周期、制御ループ)
├ UART受信処理タスク(ISRから起床)
├ センサ読み取りタスク(10ms周期)
│
中優先度
├ データ処理・フィルタリングタスク
├ 通信プロトコル処理タスク
│
低優先度(優先度:低)
├ ログ記録タスク
├ 自己診断タスク
└ アイドルタスク(スタック監視・省電力移行)
用途・ユースケース
産業機器
- モーション制御: 軸制御タスク(最高優先度・1ms)、軌跡補間タスク(中)、HMI更新タスク(低)
- PLCエミュレーション: IOポーリングタスク、ラダーロジック実行タスク、Modbus通信タスク
IoT機器
- センサ収集: センサ読み取りタスク(1秒周期)、データ圧縮タスク、クラウド送信タスク(MQTT)
- BLEデバイス: BLEスタックタスク(最高優先度)、アプリケーションタスク、省電力管理タスク
自動車ECU(AUTOSARタスク構造)
AUTOSARではOSタスクをRunnable(実行可能エンティティ)の集合として定義し、静的に周期・優先度を割り付けます。
HighFrequencyTask(1ms周期、優先度高)
└ ComRead_Runnable, PIDControl_Runnable, ComWrite_Runnable
MediumFrequencyTask(10ms周期)
└ SignalProcessing_Runnable, Diagnostic_Runnable
BackgroundTask(100ms周期、優先度低)
└ NvWrite_Runnable, PowerManagement_Runnable
実装・開発のポイント
タスクのアンチパターン
-
タスク数の過多 タスクが多すぎるとコンテキストスイッチの頻度が増え、スケジューラのオーバーヘッドが増大します。機能をまとめられるタスクは統合を検討します。
-
グローバル変数での直接共有 タスク間をグローバル変数で直接通信すると、競合状態(Race Condition)が発生します。必ずキュー・セマフォ・ミューテックスを経由します。
-
スタックサイズの過小設定 スタックオーバーフローは最も発見しにくいバグの一つです。FreeRTOSのスタックオーバーフロー検出機能(
configCHECK_FOR_STACK_OVERFLOW)を有効化します。 -
vTaskDelay()による固定スリープvTaskDelay(100ms)では、処理時間のばらつきで実効周期がずれます。vTaskDelayUntil()を使って周期を絶対時刻基準にします。
デバッグ支援
/* FreeRTOS デバッグ情報の表示 */
void printTaskInfo(void) {
char buf[512];
vTaskList(buf); /* タスク一覧(名前・状態・優先度・スタック空き) */
printf("%s", buf);
vTaskGetRunTimeStats(buf); /* タスクごとのCPU使用時間 */
printf("%s", buf);
}
他技術との比較
| 観点 | RTOSタスク | Linuxスレッド(pthread) | コルーチン |
|---|---|---|---|
| メモリ保護 | なし(同一空間共有) | あり(プロセス間は分離) | なし |
| コンテキストスイッチコスト | 数μs | 数μs〜数十μs | 数ns〜数百ns |
| 優先度の粒度 | 粗(32〜256段階) | 細(99段階,SCHED_FIFO) | ユーザ定義 |
| スタック管理 | 手動(固定サイズ) | 自動拡張(mmap) | 小スタック可 |
| タイミング保証 | あり(RTOS保証) | PREEMPT_RTで改善 | なし |
| 向いている用途 | 組み込みリアルタイム | エッジAI・GUI・ネット | 高並行・省メモリ |
タスク設計はリアルタイム性を達成するための根幹です。スケジューラ・プリエンプション・タスク間通信を正しく組み合わせることで、複雑な組み込みシステムを安全かつ保守性高く構築できます。