変更要約: 初版
5.4割込みハンドラとリソース管理
ISR(割込みサービスルーチン)を短く保ち後続処理へ委譲する設計原則、リエントラントな関数設計とクリティカルセクションによる排他制御、そして限られたスタック/ヒープを守るメモリ枯渇・メモリリークへの対処を学びます。
割込みは組込みシステムがリアルタイムに外部事象へ反応するための中核機構ですが、割込みハンドラ(ISR)の中に処理を詰め込みすぎると、その間は他の割込みや通常処理が止まり、システム全体の応答性を損ないます。さらに、ISRと通常処理が同じ変数を共有すると、タイミング次第でデータが壊れる危険もあります。この節では、ISRをどう設計すべきか、そして共有資源をどう守るかという、組込みソフトウェアの信頼性を左右する判断を学びます。
5.4.1ISR設計原則(短く保ち遅延処理へ委譲)
- ISR(割込みサービスルーチン)=割込みが発生した際にCPUが実行する処理。ISRの実行中は同レベル以下の割込みがマスクされ受け付けられなくなるため、ISRを長く保つほど他の割込み対応が遅れ、システム全体のリアルタイム性が悪化する。原則は「ISRの中では必要最小限のこと(割込み要因のクリア・重要なデータの退避等)だけを行い、時間のかかる処理は通常処理側に委譲する」こと。
- 委譲の実装パターンとして、ISRは受信データをバッファに格納しフラグやセマフォを立てるだけにとどめ、実際のデータ加工・通信処理は通常のタスク側(メインループやRTOSタスク)で行う、という設計が一般的。これにより割込み応答の遅延(割込みレイテンシ)を最小化しつつ、時間のかかる処理も実行できる。
5.4.2リエントラントとクリティカルセクション
- リエントラント(再入可能)=ある関数の実行中に割込みが発生し、その割込みハンドラ(や別のタスク)から同じ関数が呼び出されても、互いの実行結果に影響を与えず正しく動作する性質。静的変数やグローバル変数への依存を避け、ローカル変数(スタック上)だけで完結させることでリエントラント性を確保するのが基本。ISRから呼び出す可能性のある共通関数は必ずリエントラントに設計する必要がある。
- ISRと通常処理(あるいは複数のタスク)が同じグローバル変数や共有バッファを読み書きする場合、片方の処理が中断された不完全な状態でもう片方がその変数にアクセスするとデータが破壊される(競合状態)。これを防ぐため、共有変数へのアクセス中はクリティカルセクション(割込み禁止区間や排他制御用のロック)で囲み、他の処理からの割り込みを一時的に防ぐ。ただしクリティカルセクションを長く取りすぎると、その間は割込みに応答できずリアルタイム性が悪化するため、必要最小限の範囲に絞るのが設計の要諦。
「ISRは短く保ち時間のかかる処理は通常処理に委譲」「リエントラント=静的/グローバル変数に依存せず割込み中の再呼び出しでも正しく動く」「クリティカルセクション=共有変数保護のため必要最小限の範囲に絞る」が最頻出です。共有変数へのISR/通常処理間の競合状態(レースコンディション)の見抜き方も押さえましょう。
あるファームウェア開発者が、複数のセンサから届くデータをタイマ割込みで定期的に集計し、通常のメインループがその集計結果を読み取って上位システムへ送信する、という設計のシステムでデータ化けの不具合に直面したとします。まず調査すると、タイマ割込みのISR内で32ビットのグローバル変数(集計値)を読み書きしており、メインループも同じグローバル変数を直接読み取っていたことが分かりました。使用しているCPUが8ビットMCUで、32ビット変数へのアクセスが複数の命令(複数バイトの読み書き)に分割される場合、メインループがその変数を読み取っている途中でタイマ割込みが発生し、上位バイトは新しい値・下位バイトは古い値のまま、という不整合な値を読んでしまう競合状態が発生しうると判断しました。この診断に基づき、メインループが集計値を読み取る直前後をクリティカルセクション(割込み一時禁止)で囲み、読み取りの間はタイマ割込みが集計値を書き換えないようにしました。ただし、クリティカルセクションを集計処理全体に広げるとタイマ割込み自体が長時間ブロックされ他のリアルタイム処理に支障が出るため、「32ビット変数を読み取る最小限の数命令分だけ」に範囲を絞りました。さらに、この集計処理を呼び出す共通ユーティリティ関数が静的変数を使っていたため、ISRとメインループの両方から呼ばれた場合に相互汚染するリスクがあると判断し、静的変数をローカル変数(スタック上)に置き換えてリエントラントな実装に修正しました。このように、ISRと通常処理が共有する資源を特定し、競合状態が起きうる箇所だけをクリティカルセクションで最小限に保護し、共通関数はリエントラントに設計することが、割込みハンドラ周りの信頼性設計の核になります。
| 概念 | 目的 | やりすぎた場合の弊害 |
|---|---|---|
| ISRを短く保つ | 他の割込み応答の遅延を防ぐ | (不足すると)割込みレイテンシ増大・リアルタイム性悪化 |
| リエントラント設計 | 割込み中の再呼び出しでも正しく動作させる | (不足すると)静的/グローバル変数の相互汚染 |
| クリティカルセクション | 共有変数へのアクセスを排他制御する | 範囲が広すぎると割込み応答が長時間ブロックされる |
ひっかけ: 「ISRに全ての処理を書き込んでおけば通常処理との連携ミスが起きず安全」は誤りです——ISRを長くするほど他の割込みがマスクされリアルタイム性が悪化するため、時間のかかる処理は通常処理へ委譲すべきです。また「クリティカルセクションは長く取るほど安全」も誤り=範囲が広いほどその間割込みに応答できずリアルタイム性が悪化するため、保護すべき共有変数へのアクセスの最小範囲に絞るのが正しい設計です。
5.4.3スタック/ヒープ制約とメモリ枯渇・漏れ
- 組込み機器のスタック(関数呼び出し・ローカル変数用)とヒープ(動的確保用)は、PCと異なり数KB〜数十KB程度と極めて限られる。関数の再帰呼び出しの深さや、ISRのネスト(多重割込み)が想定以上に深くなるとスタックオーバーフロー(スタック領域が他の変数領域を侵食)を起こし、原因特定の難しい不定動作や暴走につながる。
- 動的メモリ確保(
malloc等)を繰り返し使う設計では、確保したメモリを解放し忘れるメモリリークが蓄積し、長時間稼働する組込み機器では最終的にメモリ枯渇に至り新規確保が失敗して機能停止する。長時間無人稼働が前提の組込み機器では、動的確保を最小限にし静的にメモリを確保する設計や、確保・解放のペアを厳密に監査する設計が重視される。
5.4.4この節のまとめ
- ISRは必要最小限にとどめ時間のかかる処理は通常処理へ委譲する。長く保つほど他の割込みがマスクされリアルタイム性が悪化する
- リエントラント(静的/グローバル変数に非依存)な共通関数設計と、共有変数保護のためのクリティカルセクション(必要最小限の範囲)が排他制御の核
- 限られたスタック/ヒープでは、ネストの深さによるスタックオーバーフロー、解放忘れによるメモリリークからのメモリ枯渇に注意する
進捗の記録にはログインが必要です。
理解度チェック
(軽い確認用)Q1. タイマ割込みのISR内で受信データの加工や上位システムへの送信処理まで全て実行する設計にしたところ、他の重要な割込みへの応答が遅れるようになった。最も適切な是正策はどれか。
Q2. 8ビットMCUで、タイマ割込みのISRと通常のメインループの両方が同じ32ビットのグローバル変数を読み書きしており、メインループが読み取りの途中で割込みが発生すると上位バイトと下位バイトの新旧が混在した値を読んでしまう不具合が起きている。最も適切な対処はどれか。
Q3. ISRとメインループの両方から呼び出される共通のデータ集計ユーティリティ関数が、内部で静的変数を使って前回値を保持していた。この関数を安全に両方から呼び出せるようにするための最も適切な修正はどれか。

