割り込み処理でやってはいけないこと5選|組み込み設計の落とし穴

目次

はじめに

初心者さん

割り込みって、“すぐ処理できる便利な場所”ですよね?

エンジニアくん

初心者の頃はみんなそう思う。でも実際は、一番事故が起きやすい場所なんだ

前回の記事では、割り込みの基本とポーリングとの違いを整理しました。
割り込みの基本についてはこちら

今回はさらに一歩踏み込みます。

割り込みは便利な仕組みですが、
扱いを間違えると、

  • 再現しないバグ
  • タイミング依存不具合
  • 謎のフリーズ

などの原因になります。

この記事では、
実務でよく見る

👉 「割り込み処理でやってはいけない設計」

を整理していきます。


割り込み処理解説シリーズ

本記事は「割り込み処理解説」シリーズの1つです。
こちらのまとめ記事で全体像を整理しています。

割り込み記事シリーズ一覧です。

  1. 割り込みとは何か?ポーリングとの違いから理解する【組み込み入門】
  2. 割り込み処理でやってはいけないこと5選|組み込み設計の落とし穴(この記事)
  3. 割り込みは「処理を書く場所」ではない|組み込み設計の本質
  4. 割り込み×状態遷移設計|イベント駆動型組み込みの基本パターン
  5. 割り込み優先度設計の考え方|リアルタイム性を壊さないために
  6. 割り込みとRTOSなし構成の違いとは?設計思想の本質を整理する

なぜ割り込み処理は危険なのか?

割り込み処理(ISR: Interrupt Service Routine)は、

👉 CPUの通常処理を“強制的に中断”して実行されます。

つまりISRは、

  • いつ実行されるか分からない
  • 他処理を止める
  • スタックを消費する
  • 並行実行状態になる

という非常に特殊な環境です。

通常の関数感覚で書くと、
後でかなり危険なことになります。


割り込み発生時にCPUで起きていること

割り込み発生時、
CPU内部では以下のような処理が行われます。

割り込み処理の流れ

つまりISRは、

👉 「CPUの実行を途中で切り替えている」

ということです。

そのため、

  • ISRが長い
  • ネストが深い
  • スタック不足

などが発生すると、
システム全体へ影響が出ます。

① 割り込み内で重い処理を書く

これは最も多いミスです。

例:

C
void UART_IRQHandler(void) {
    printf("received\n");
    heavy_calculation();
}

一見問題なさそうに見えます。

しかし実際には非常に危険です。


なぜ危険なのか?

ISR実行中は、
他の処理が止められている可能性があります。

そのためISRが長いと、

  • タイマ割り込み遅延
  • UART受信取りこぼし
  • センサ応答遅延
  • リアルタイム性崩壊

などにつながります。

特に組み込みでは、

👉 「ISR実行時間」

がシステム品質へ直結します。


原則

割り込みは、

👉 「処理を書く場所」ではなく、
👉 「イベント通知する場所」

です。

ISRでは、
最低限の処理だけ行うのが基本です。


② ISR内で printf() を使う

デバッグ中、
かなりやりがちです。

C
void UART_IRQHandler(void) {
    printf("IRQ\n");
}

しかしこれは、
組み込みではかなり危険です。


なぜ printf は危険なのか?

printf() は見た目以上に重い処理です。

内部では例えば:

  • UART送信待ち
  • バッファ処理
  • ロック処理
  • 文字列変換

などを行っています。

さらにライブラリによっては、
内部で割り込みを使うこともあります。

つまり、

👉 「軽そうに見えて、実はかなり重い」

処理です。


実務でよくあること

実際の現場では、

  • 「printfを消したら動いた」
  • 「デバッグ版だけ不安定」
  • 「ログ出力すると壊れる」

などはかなりよくあります。

特にISR内printfは、
真っ先に疑われます。


③ 共有変数を雑に扱う

例えば以下です。

C
int flag;

void ISR(void) {
    flag = 1;
}

int main(void) {
    while(1) {
        if(flag == 1) {
            flag = 0;
            process();
        }
    }
}

一見問題なさそうです。

しかし実際には危険です。


なぜ危険なのか?

コンパイラ最適化によって、

👉 flag を毎回読み直さない

可能性があります。

すると、

ISR側で flag = 1 しても、
メインループが永遠に終了しないことがあります。


必須になる知識

割り込みと共有変数では、

  • volatile
  • クリティカルセクション
  • アトミック性

などの理解が必要になります。


volatile の重要性

例えば:

C
volatile int flag = 0;

とすることで、

👉 「毎回メモリから読み直す」

ことをコンパイラへ指示できます。

割り込みと volatile は非常に重要な組み合わせです。

volatileについては、
こちらの記事でも詳しく解説しています。


④ ISR内で malloc() する

これもかなり危険です。

例えば:

C
void ISR(void) {
    char* buf = malloc(128);
}

リアルタイム設計では、
基本的に避けます。


なぜ malloc が危険なのか?

mallocは内部で、

  • 空き領域探索
  • 管理テーブル更新
  • メモリ管理処理

などを行います。

つまり:

👉 実行時間が一定ではありません

リアルタイム制御では、
「いつ終わるか分からない処理」は危険です。

さらに、

  • フラグメンテーション
  • メモリ不足
  • ヒープ破壊

などの原因にもなります。


実務ではどうする?

組み込みでは一般的に、

  • 静的確保
  • 固定長バッファ
  • リングバッファ

などを使います。

組み込み開発でmallocが嫌われる理由については、こちらの記事でも解説しています。


⑤ 割り込みネストを考慮しない

割り込みには優先度があります。

例えば:

Timer ISR 実行中

UART ISR 割り込み

さらに別ISR

というように、
ISR中にさらにISRが発生することがあります。

これを「割り込みネスト」と呼びます。


ネストで起きる問題

ネストが深くなると、

  • スタック消費増大
  • RAM不足
  • 再入問題
  • HardFault

などにつながります。

特にスタック不足は、
再現性が低く非常に危険です。


スタック領域との関係

割り込み発生時には、
CPU状態がスタックへ退避されます。

つまりネストが増えるほど、
スタック使用量も増加します。

スタック領域については、
こちらの記事で詳しく解説しています。


このように割り込みが連続する状況では、割り込み優先度設計が重要です。
割り込み優先度設計はこちらの記事で解説しています。

理想的なISR設計

ISRの理想は、

👉 「イベント通知だけ行う」

ことです。

例えば:

C
volatile uint8_t uart_rx_event = 0;

void UART_IRQHandler(void) {
    uart_rx_event = 1;
}

ISRではフラグONだけ行います。


実処理はメインループ側

C
while(1) {
    if(uart_rx_event) {
        uart_rx_event = 0;
        process_uart();
    }
}

実際の重い処理は、
メインループ側で行います。


なぜこの設計が重要なのか?

この構造にすることで、

  • ISR短縮
  • リアルタイム性向上
  • デバッグ性向上
  • ネスト影響軽減

などのメリットがあります。


状態遷移設計との関係

実際の組み込み開発では、以下の「イベント駆動型設計」が良く使われます。

割り込みと状態遷移

このような「イベント駆動型設計」は、
組み込みソフトで非常によく使われる基本パターンです。

状態遷移設計については、
こちらの記事で詳しく解説しています。


なぜ割り込みは設計力が問われるのか?

割り込みは、

  • 実行タイミングが予測できない
  • 並行動作になる
  • スタックを消費する
  • 優先度管理が必要

など、かなり設計難易度が高い仕組みです。

つまり、

👉 「並行処理設計そのもの」

です。

ここを軽く扱うと、

  • 再現しないバグ
  • タイミング依存不具合
  • 長期間追えない障害

につながります。


まとめ

割り込み処理でやってはいけないこと:

  • 重い処理を書く
  • printfする
  • 共有変数を雑に扱う
  • mallocする
  • ネストを無視する

ISRは便利な処理場所ではありません。

👉 「最も慎重に扱うべき設計ポイント」

です。

そして実際の組み込みでは、

👉 ISRはイベント通知だけ行う

という設計が非常に重要になります。


次回予告

次回は、

「割り込みは“処理を書く場所ではない”」

という設計思想について、
さらに深掘りしていきます。

この記事が参考になった方へ

割り込み処理については、
こちらのまとめ記事で全体像を整理しています。

また当サイトでは、
割り込み理解に必要なC言語文法やメモリ構造についても実務目線で解説しています。

エンジニアとして技術を学ぶことは重要ですが、
キャリアや副業についても同時に考える必要があります。

副業の現実や市場価値、今後のキャリア戦略については、
こちらの記事でまとめています。

技術に関するご相談・開発・自動化ツール作成・記事執筆などのご依頼も承っています。

小さなご相談からでもお気軽にご連絡ください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

組み込みソフトエンジニアとして働きながら、
C言語・メモリ・ポインタなどの基礎から実務まで解説しています。

副業・キャリアについても実体験ベースで発信中です。

X・Qiita・noteでも発信しています。
X:更新情報・日常
Qiita:技術発信
note:キャリア・副業

▼まずはここから読むのがおすすめ
C言語文法シリーズ
メモリ領域解説シリーズ
割り込み処理解説シリーズ
ソフトウェア設計解説シリーズ
キャリアと副業ロードマップ

コメント

コメントする


目次