FreeRTOS 22 · 設計指針と他の OS との比較

Chapter 22

設計指針と他の OS との比較

この章がなぜ必要なのか——ここまでの知識を「使える形」にまとめるため.

各章で個別に述べた指針を、設計・実装・レビューの順に並べ直す。 そして最後に、FreeRTOS で学んだ考え方が 他の OS にどこまで通用するのかを確認する。

OS の芯は共通である。 それを確かめて、このシリーズを終える。

この章で使う既出の用語(定義は各リンク先). コンテキスト(01 章 6 節)、TCB(02 章 2 節)、タスク(02 章 2 節)、ビジーウェイト(02 章 3 節)、ハードウェア(03 章 2 節)、Blocked(04 章 1 節)、Ready(04 章 1 節)、Running(04 章 1 節)、Suspended(04 章 1 節)、CFS(06 章 1 節)、FreeRTOS(06 章 4 節)、RAM(06 章 4 節)、固定優先度(06 章 1 節)、バイナリセマフォ(12 章 5 節)、ブロック(13 章 1 節)、メッセージ(14 章 7 節)、ソフトウェアタイマ(15 章 1 節)、ティック(15 章 1 節)、不可(16 章 4 節)、MPU(17 章 5 節)、正確(17 章 5 節)、循環待ち(19 章 4 節)、デッドライン(20 章 2 節)、周期(20 章 2 節)、最高(20 章 4 節)、スタックオーバーフロー(21 章 9 節)、デッドロック(21 章 8 節)

1. 設計段階で決めること

タスク分割

指針理由章
「独立して待つもの」を 1 タスクに待ちの単位がタスクの単位02
10〜15 個以内に収める1 タスクあたり数百バイト〜数 KB02
分ける理由がないなら分けないタスク間通信がバグを生む02
タスクを動的に生成・削除しないリソースが自動回収されない04

優先度設計

指針理由章
周期が短いタスクほど高優先度(RM)固定優先度では最適20
実行時間で優先度を決めない重い処理を上げると短周期が破綻する20
優先度は 5〜8 段で足りる段数が多いと迷う06
同じ優先度に複数タスクは正常むしろ推奨される06
優先度 0 を使わないアイドルタスク専用09
configTIMER_TASK_PRIORITY は高めただしコールバックを軽くする15

通信路の選択

やりたいこと道具章
ISR → タスクの起床タスク通知14
構造化データの受け渡しキュー11
大きな可変長データメッセージバッファ14
UART の連続受信ストリームバッファ14
共有資源の保護ミューテックス12
複数条件の AND 待ちイベントグループ14
資源の個数管理カウンティングセマフォ12

メモリ方針

指針理由章
静的確保を既定にする失敗しない・ビルド時に分かる16
動的が要るなら heap_4結合するので断片化に強い16
スタックはヒープの中にあることを意識するconfigTOTAL_HEAP_SIZE の見積り16
printf はログタスクに集約単独で 1〜2 KB 使う17

ネットワーク/ハードウェアの選択

指針理由章
正確なタイミングはハードウェアタイマソフトウェアタイマは精度が粗い15
キャッシュがあると WCET が難しくなる予測可能性のために無効化も検討20
共有ペリフェラルはミューテックスで守る切り替えでレジスタは保存されない05

2. 実装段階の掟

タスク関数

void vTask( void *pv )
{
    /* 初期化(ここは 1 回だけ) */

    for( ;; )
    {
        /* ★ 必ずブロックする API を含める */
        xQueueReceive( q, &item, portMAX_DELAY );

        /* 処理 */
    }

    /* ★ ここには到達しない。到達するなら vTaskDelete(NULL) を書く */
}
掟理由章
関数から return しない戻る先がない02
必ずブロックする API を入れるビジーウェイトは低優先度を飢えさせる02, 06
大きな配列をローカルに置かないスタックオーバーフロー17
再帰・VLA・alloca を使わないスタック深さが計算できなくなる17
周期処理は vTaskDelayUntilvTaskDelay は誤差が累積する08

排他制御

掟理由章
共有変数を作らない。キューで渡す最も強力な対策19
ロックにはミューテックス(バイナリセマフォ不可)優先度継承12, 13
ミューテックスを持ったままブロックしない最重要13
ミューテックスをネストしないデッドロック・連鎖継承の限界13, 19
ネストするなら 順序を決めて文書化循環待ちを防ぐ19
クリティカルセクションは数 µs 以下割り込みレイテンシに直結18, 19
確認と実行を分けないTOCTOU19
_locked 命名規約を使う自己デッドロックを防ぐ19

割り込み

掟理由章
ISR は 10 µs 以下全タスクの応答時間に効く18, 20
ISR からは FromISR 版のみブロックできない18
portYIELD_FROM_ISR を最後に 1 回忘れると 1 ティック遅れる18
xHigherPriorityTaskWoken を pdFALSE で初期化未初期化だと誤動作11, 18
割り込み優先度を configMAX_SYSCALL 以下にAPI を呼ぶなら必須18
configPRIO_BITS をチップに合わせる設定ミスの温床18
本処理は遅延タスクへ割り込み禁止時間を短く18

戻り値

/* ★ すべての生成 API・送信 API の戻り値を確認する */
if( xTaskCreate( vTask, "T", 256, NULL, 2, &h ) != pdPASS )  { fail(); }
if( xQueueSend( q, &item, 0 )                  != pdPASS )  { handle_full(); }
if( xTimerStart( t, 0 )                        != pdPASS )  { fail(); }
if( ( q = xQueueCreate( 10, sizeof(int) ) )    == NULL   )  { fail(); }

「たぶん成功する」で書くと、ヒープ不足やキュー満杯が静かに無視される。

3. レビュー用チェックリスト

設計レビュー

コードレビュー

検証

設計レビュー用チェックリスト

上のチェックリストを、分野別にたどれる形で確認できる。

4. よくある症状と原因

症状疑うべき原因章
たまに再起動するスタックオーバーフロー17
数時間〜数日後にハングするヒープ断片化/ティック折り返し/割り込み優先度16, 10, 18
タスクを削除してもヒープが減ったままアイドルタスクの飢餓09
応答がたまに 1 ms 遅れるportYIELD_FROM_ISR の書き忘れ18
printf を足したら落ちたスタック不足17
高優先度タスクが遅い優先度逆転/ISR が重い/クリティカルセクションが長い13, 18
タイマが全部止まったタイマコールバックでブロックした15
低優先度タスクが動かない高優先度のビジーウェイト(正常な動作)06
vTaskStartScheduler() から戻ってくるヒープ不足03, 16
vTaskDelay が待たないpdMS_TO_TICKS が 0 になっている08
周期が少しずつずれるvTaskDelay を使っている08
デバッガを繋ぐと直るレースコンディション19

5. 他の OS との比較

用語の対応

FreeRTOSZephyrPOSIX / LinuxµITRON / TOPPERS
タスクスレッドスレッド (pthread)タスク
xTaskCreatek_thread_createpthread_createcre_tsk / act_tsk
vTaskDelayk_sleepnanosleepdly_tsk
キューk_msgqメッセージキュー (mq_*)データキュー
セマフォk_semsem_tセマフォ
ミューテックスk_mutexpthread_mutex_tミューテックス
イベントグループk_event(条件変数で代替)イベントフラグ
タスク通知(相当なし)(相当なし)(相当なし)
ソフトウェアタイマk_timertimer_create周期ハンドラ

設計思想の違い

観点FreeRTOSZephyrLinux (PREEMPT_RT)
規模約 1 万行約 100 万行約 3000 万行
優先度数値大が高数値小が高(負値は協調的)RT は大が高、nice は小が高
スケジューラ固定優先度固定優先度 + メタIRQCFS / SCHED_FIFO / SCHED_DEADLINE
メモリ保護なし(MPU 版はある)MPU / MMU 対応MMU で完全分離
プロセスなしなしあり
デバイスドライバなし(自分で書く)豊富(統一 API)非常に豊富
ネットワーク別製品(FreeRTOS+TCP)内蔵内蔵
ビルドソースを取り込むWest / CMake / KconfigKconfig / make
最小 RAM数 KB十数 KB数 MB〜
優先度継承簡易版ありあり(rt_mutex)
SMPv11 で対応対応完全対応

どれが優れているか、ではない.

規模が大きいほど機能が多く、RAM を食い、予測可能性が下がる。 トレードオフである。

共通している「OS の芯」

このシリーズで学んだことのうち、どの OS でも同じものを挙げる。

概念どこまで共通か
コンテキストスイッチ完全に同じ。レジスタとスタックポインタの保存・復元
タスク/スレッドの状態機械ほぼ同じ。Running/Ready/Blocked/Suspended
Ready リストと優先度同じ発想。Linux も優先度別リスト+ビットマップ(O(1) 時代)
ブロックと起床同じ。待ちキューに繋いで、条件成立時に外す
偽の目覚めへの対処完全に同じ。while で条件を再確認する
優先度逆転と継承同じ問題、同じ解法
デッドロックの 4 条件完全に同じ。順序規約も同じ
割り込みの上半分/下半分同じ思想。名前が違うだけ
遅延実行(ワークキュー)同じ。FreeRTOS の xTimerPendFunctionCall = Linux のワークキュー
レートモノトニック・応答時間解析理論は共通。OS を問わない

これがこのシリーズの結論である.

FreeRTOS は小さいが、OS が OS であるための要素はすべて持っている。 そして、その要素は他の OS でも同じ形で存在している。

Linux の schedule() は 1000 行を超え、 CFS の赤黒木、CPU ごとのランキュー、負荷分散、 cgroup による制限——さまざまなものが載っている。

だがその芯にあるのは、「Ready なタスクから 1 つ選んで、コンテキストを差し替える」 という、FreeRTOS の vTaskSwitchContext() と同じ処理である。

小さいものを完全に理解することは、大きいものを理解する最短経路である。

6. FreeRTOS の SMP 対応(補足)

v11 でマルチコア(SMP)が本体に統合された。単一コアとの違いを簡単に述べる。

#define configNUMBER_OF_CORES  2
変化内容
pxCurrentTCB配列になる(pxCurrentTCBs[coreID])
「最高優先度が走る」「上位 N 個が走る」に変わる
並行 → 並列本当に同時に実行される(02 章の話が変わる)
クリティカルセクション割り込み禁止+スピンロックが必要
タスク固定vTaskCoreAffinitySet() で特定コアに固定できる

SMP になると、02 章で「単一コアでは並列は起きない」と述べた前提が崩れる.

単一コアから SMP への移行は、排他制御の全面的な見直しを要する。 安易に configNUMBER_OF_CORES を増やしてはいけない。

7. これから読むもの

対象内容
tasks.cこのシリーズの 04 章〜10 章の実物。まずこれを読む
queue.c11 章〜14 章の実物。xQueueGenericSend を追う
list.c250 行。30 分で読み切れる。最初に読んでもよい
port.c(自分のCPU用)このシリーズで省いた部分。契約の実装が見える
FreeRTOS 公式ドキュメントfreertos.org の API リファレンスとブック
*Hard Real-Time Computing Systems*(Buttazzo)20 章の理論的背景
*Making Embedded Systems*(White)組み込み設計全般
Zephyr の kernel/sched.c同じ問題を別の解き方で。比較すると理解が深まる

最後に、ソースを読むときの助言.

tasks.c は 5,500 行あるが、#if の中身の大半は使わない機能である。 自分の FreeRTOSConfig.h で有効になっているものだけを追えば、 実質的に読むべきは 1,500 行程度である。

そして、その 1,500 行の中身は——このシリーズで全部説明した。

地図は渡した。あとは歩くだけである。

8. このシリーズのまとめ

部学んだこと
I. なぜ OS が要るのかスーパーループの限界。「途中で待てる」ことが OS の価値。コンテキストという有限のデータ
II. スケジューラの中核状態=リストの所属。切り替え=SP の差し替え。規則は「最高優先度が必ず走る」の一行。O(1) の Ready リスト
III. 待つ・起こすソート済み遅延リストと 2 本交換。キュー 1 本でセマフォもミューテックスも実装。優先度継承
IV. 時間とメモリタイマタスクという専用スタッフ。5 つのヒープ。スタックが最大の事故原因
V. 正しく動かす割り込みとの境界の規則。排他設計の原則。応答時間解析。トレース

OS は魔法ではない。 レジスタをスタックに積み、ポインタを差し替え、リストの間でノードを動かす—— それだけのことを、正確に、漏れなく、速くやっているだけである。

その「それだけのこと」を最後まで追えたなら、 あなたはもう、どの OS のソースを開いても道に迷わない。