Chapter 20
リアルタイム性の解析 — 「間に合う」を計算で保証する
この章がなぜ必要なのか——「動いているから大丈夫」は保証ではない.
RTOS の「RT」は Real-Time——リアルタイムである。 だがリアルタイムとは「速い」ことではない。
「決められた時刻までに、必ず終わることが保証されている」ことである。
テストで動いても、最悪のタイミングが重なったときに間に合うとは限らない。 それを計算で確かめる方法が確立している。この章はその入門である。
この章で使う既出の用語(定義は各リンク先). コンテキスト(01 章 6 節)、タスク(02 章 2 節)、固定優先度(06 章 1 節)、追加(07 章 2 節)、不可(16 章 4 節)、正確(17 章 5 節)
1. リアルタイムとは何か
| 種類 | 期限を破ったときの結果 | 例 |
|---|---|---|
| ハードリアルタイム | システムの失敗。人命や重大な損害 | エアバッグ、ブレーキ、モータ転流 |
| ファームリアルタイム | その結果が無価値になる | 動画のフレーム、通信のタイムスロット |
| ソフトリアルタイム | 品質が下がるが動く | UI の応答、ログの記録 |
「速い」と「リアルタイム」は別物である.
平均 1 µs で応答するが、最悪 100 ms かかることがあるシステムは、 リアルタイムではない。
平均 10 ms だが、絶対に 20 ms を超えないシステムは、リアルタイムである。
リアルタイム性とは、最悪値の保証である。 平均値には意味がない。
2. 用語
| 記号 | 名前 | 意味 |
|---|---|---|
| \(T_i\) | 周期 | タスク \(i\) が起動される間隔 |
| \(C_i\) | 最悪実行時間 (WCET) | 1 回の実行にかかる最大時間 |
| \(D_i\) | デッドライン | 起動から、終わっていなければならない時刻まで |
| \(R_i\) | 最悪応答時間 | 起動から実際に終わるまでの最大時間 |
| \(U\) | CPU 利用率 | \(\sum C_i / T_i\) |
保証すべき条件は、すべてのタスクについて \(R_i \le D_i\) である。
多くの場合、\(D_i = T_i\) とする(次の起動までに終わっていること)。これを暗黙のデッドラインと呼ぶ。
3. CPU 利用率
| タスク | \(C_i\) | \(T_i\) | \(C_i / T_i\) |
|---|---|---|---|
| 制御 | 2 ms | 10 ms | 0.20 |
| 通信 | 5 ms | 50 ms | 0.10 |
| 表示 | 20 ms | 200 ms | 0.10 |
| 合計 | \(U = 0.40\) |
\(U > 1\) なら、どんなスケジューラでも間に合わない(物理的に不可能)。 だが \(U \le 1\) でも間に合うとは限らない。優先度の付け方が問題になる。
4. レートモノトニック (RM) — 優先度の付け方
周期が短いタスクほど、優先度を高くする。
Liu と Layland(1973)が証明した重要な結果:
固定優先度スケジューリングにおいて、 レートモノトニックは「最適」である。 RM で間に合わないなら、他のどんな固定優先度の付け方でも間に合わない。
| タスク | 周期 | RM での優先度 |
|---|---|---|
| 制御 | 10 ms | 最高 |
| 通信 | 50 ms | 中 |
| 表示 | 200 ms | 最低 |
これは直感に反することがある.
「表示は 20 ms もかかる重い処理だから優先度を上げよう」——これは間違いである。 実行時間ではなく、周期の短さで優先度を決める。
重い処理を高優先度にすると、短周期のタスクが待たされて期限を破る。
例外: デッドラインが周期と違う場合
\(D_i \ne T_i\) のときは、デッドラインモノトニック (DM) を使う。
デッドラインが早いタスクほど、優先度を高くする。
これも最適であることが証明されている。
5. 利用率境界による判定
Liu と Layland は、簡単な十分条件も示した。
これを満たせば、RM で必ず全タスクが期限を守る。
| \(n\)(タスク数) | 境界 |
|---|---|
| 1 | 1.000 |
| 2 | 0.828 |
| 3 | 0.780 |
| 4 | 0.757 |
| 5 | 0.743 |
| 10 | 0.718 |
| \(\infty\) | \(\ln 2 \approx 0.693\) |
\(n \to \infty\) での極限は \(\ln 2 \approx 69.3\%\) である。
「CPU 使用率を 70 % 以下に抑える」という経験則の出どころがこれである.
ただし——これは十分条件であって、必要条件ではない。 \(U = 0.9\) でも間に合う場合はいくらでもある。
境界を超えたら「間に合わないかもしれない」だけであって、 「間に合わない」ではない。もっと正確な判定が必要になる。
利用率境界を確かめる
タスクの周期と実行時間を変えて、利用率と境界の関係を確かめられる。
6. 応答時間解析 — 正確な判定
利用率境界より厳密な方法が応答時間解析 (Response Time Analysis) である。
タスク \(i\) の最悪応答時間 \(R_i\) は、次の再帰式の不動点として求まる。
ここで \(hp(i)\) は「\(i\) より優先度の高いタスクの集合」である。
式の意味
| 項 | 意味 |
|---|---|
| \(C_i\) | 自分の実行時間 |
| \(\lceil R_i / T_j \rceil\) | 自分が走っている間に、タスク \(j\) が起動される回数 |
| \(\times C_j\) | その分だけ CPU を奪われる |
天井関数 \(\lceil \cdot \rceil\) が重要である。 「\(R_i\) の間に \(j\) が何回来るか」は、切り上げで数える必要がある (起動のタイミングが最悪の場合を考えるため)。
解き方 — 反復計算
\(R_i\) が両辺にあるので、反復で解く。
\(R_i^{(k+1)} = R_i^{(k)}\) になったら収束。それが \(R_i\) である。 \(R_i > D_i\) になったら、その時点で「間に合わない」と判定できる(発散する)。
計算例
| タスク | \(C_i\) | \(T_i = D_i\) | 優先度(RM) |
|---|---|---|---|
| A | 1 ms | 4 ms | 高 |
| B | 2 ms | 6 ms | 中 |
| C | 2 ms | 12 ms | 低 |
\(U = 1/4 + 2/6 + 2/12 = 0.25 + 0.333 + 0.167 = 0.75\)
\(n = 3\) の利用率境界は \(0.780\) なので、\(0.75 < 0.780\) で十分条件を満たす。 念のため応答時間解析でも確かめる。
タスク A(最高優先度):
タスク B:
\(R_B = 3\ \text{ms} \le 6\ \text{ms}\) ✓
タスク C:
\(R_C = 6\ \text{ms} \le 12\ \text{ms}\) ✓
全タスクが期限を守る。
応答時間解析を実行する
タスクセットを編集して、応答時間の反復計算と判定を確かめられる。
7. 現実の補正項
上の式は理想化されている。実際には次の項を足す必要がある。
| 項 | 名前 | 内容 |
|---|---|---|
| \(B_i\) | ブロッキング時間 | 低優先度タスクが持つ資源を待つ時間(13 章) |
| \(J_j\) | リリースジッタ | 起動タイミングのばらつき |
| \(n_{sw}^i \cdot C_{sw}\) | 切り替えコスト | コンテキストスイッチの合計(05 章) |
| \(I_{\text{isr}}\) | 割り込み負荷 | ISR が奪う CPU 時間(18 章) |
\(B_i\) の求め方(優先度継承あり)
優先度継承を使っている場合、\(B_i\) は 「\(i\) より低優先度のタスクが持つ、\(i\) が使う可能性のあるミューテックスの、 最長クリティカルセクション」の和で上から抑えられる。
これが 13 章で「有界な逆転」と呼んだものの正体である.
優先度継承がないと、\(B_i\) に中優先度タスクの実行時間が全部入る。 つまり計算不能になる。
優先度継承は「\(B_i\) を計算可能にするための装置」である。 性能の話ではなく、保証可能性の話である。
\(I_{\text{isr}}\) の扱い
割り込みは、どんなタスクよりも優先度が高い。 だから ISR は「周期 \(T_{\text{isr}}\)、実行時間 \(C_{\text{isr}}\) の最高優先度タスク」として、 \(hp(i)\) に含めて計算する。
ここで「ISR は 10 µs 以下」の意味が分かる(18 章).
1 ms 周期の割り込みが 100 µs かかると、\(10\%\) の CPU を無条件に奪う。 そしてそれは全タスクの応答時間に上乗せされる。
ISR を軽くすることは、全システムの応答時間を改善する。
8. WCET(最悪実行時間)をどう求めるか
これが実務では最も難しい。
| 方法 | 精度 | コスト |
|---|---|---|
| 実測(多数回) | 「観測された最大値」であって最悪値ではない | 低 |
| 実測 + マージン | 実用的。1.5〜2 倍のマージンを取る | 低 |
| 静的解析ツール | 上界を保証できる | 高(商用ツールが必要) |
| コードの手計算 | 小さい関数なら可能 | 中 |
実測の方法
/* 高分解能タイマで測る */
uint32_t t0 = DWT->CYCCNT; /* Cortex-M のサイクルカウンタ */
do_work();
uint32_t dt = DWT->CYCCNT - t0;
if( dt > worst_case ) { worst_case = dt; }WCET を大きくする要因
| 要因 | 影響 |
|---|---|
| キャッシュミス | 数倍〜数十倍 |
| 分岐予測ミス | 数サイクル〜数十サイクル |
| フラッシュのウェイトステート | 数倍 |
| DMA によるバス競合 | 数十 % |
| データ依存の分岐 | 経路によって大きく違う |
キャッシュのあるマイコンでは、WCET の見積りが極端に難しくなる.
Cortex-M7 や Cortex-A では、キャッシュヒットとミスで 実行時間が 10 倍以上変わることがある。
ハードリアルタイム性が要るなら——
- キャッシュを無効にする(遅くなるが予測可能になる)
- 重要な関数を TCM(密結合メモリ)に置く
- キャッシュをロックする(対応していれば)
「予測可能性のために性能を捨てる」——リアルタイム設計ではよくある判断である。
9. 実務的な指針
| 指針 | 理由 |
|---|---|
| CPU 利用率を 70 % 以下に保つ | 利用率境界。将来の機能追加の余地も残る |
| 周期の短いタスクを高優先度に | レートモノトニック |
| ISR を 10 µs 以下に | 全タスクの応答時間に効く |
| クリティカルセクションを短く | \(B_i\) に直結 |
| ミューテックスをネストしない | \(B_i\) の計算が可能になる |
| 重い処理は低優先度タスクへ | 実行時間ではなく周期で優先度を決める |
| WCET を実測し、記録する | 変更のたびに劣化していないか確認 |
| 最悪応答時間を実測で検証する | 計算と実測の両方をやる |
実測で検証する方法
/* 周期タスクの応答時間を測る */
void vControlTask( void *pv )
{
TickType_t xLastWake = xTaskGetTickCount();
for (;;) {
if( xTaskDelayUntil( &xLastWake, pdMS_TO_TICKS( 10 ) ) == pdFALSE ) {
deadline_miss_count++; /* ★ 周期に間に合っていない */
}
uint32_t t0 = DWT->CYCCNT;
do_control();
uint32_t dt = DWT->CYCCNT - t0;
if( dt > wcet_observed ) { wcet_observed = dt; }
}
}
xTaskDelayUntil()の戻り値でデッドラインミスが検出できる(08 章)。 これを本番でもカウントしておくと、 フィールドで期限を破っているかどうかが分かる。非常に有用である。
10. この章のまとめ
| ポイント | 内容 |
|---|---|
| リアルタイム | 「速い」ではなく「最悪値が保証されている」 |
| CPU 利用率 | \(U = \sum C_i / T_i\)。\(U > 1\) なら物理的に不可能 |
| レートモノトニック | 周期が短いほど高優先度。固定優先度では最適 |
| デッドラインモノトニック | \(D_i \ne T_i\) のとき。デッドラインが早いほど高優先度 |
| 利用率境界 | \(U \le n(2^{1/n}-1)\)、極限は \(\ln 2 \approx 69.3\%\)。十分条件のみ |
| 応答時間解析 | 反復で不動点を求める。正確な判定 |
| 現実の補正 | ブロッキング・ジッタ・切り替え・割り込み負荷 |
| 優先度継承の意義 | \(B_i\) を計算可能にすること |
| WCET | 実測 + マージン。キャッシュがあると極端に難しい |
| 実務の目安 | CPU 70 % 以下・ISR 10 µs 以下 |
次章では、ここまでの解析に必要な「見る」道具—— デバッグとトレースを扱う。