スタックと割り込み ―― プログラムが動く仕組みを知ろう
● 割り込みと関数呼び出しの違い
割り込みは,以下の2点において,関数呼び出しと大きく異なっています.
(1) 戻り先アドレスに加え,CPUの内部状態をスタックに格納する必要がある
(2) 割り込み同士で優先順位が決まっている
まず(1)について説明します.関数呼び出しでは,戻り先アドレス(あれば引き数も)をスタックに格納し,関数フレームを確保しました.割り込みの場合も戻り先アドレスをスタックに格納するのですが,これだけでは足りません.CPU内部には,前の命令を実行した結果や処理モードを保持するレジスタがあります(例えば,算術演算におけるキャリの発生や比較演算の結果などを保持する).こうした内部状態を次の命令実行で必要とする場合があります.割り込みの発生タイミングは不定なので,CPUの内部状態を保存しないと,割り込みハンドラの終了後の命令実行で不都合が生じる可能性があります.
例えば図12には,変数aが0かどうかによってbに代入する値を変える3項演算があります.プログラム実行時,(1)のa != 0の判定中に割り込みが発生すると,3項演算の途中で実行が割り込みハンドラに移ります((2)).そして割り込みハンドラが終了して戻ったとき,CPUの内部状態が保存されていないと,b = 1 : b = 0の実行が正常に行えません.これは,a != 0 ?の部分が,CPUの命令レベルでは「aと0の比較」および「比較結果に基づく分岐」といった複数の形に展開されており,戻ってきたときにa != 0 ?の結果がなくなっているためです.このため,割り込みの発生タイミングが不定でも正しく命令実行が継続できるように,割り込みによって実行が切り替わる際には,戻り先アドレスだけでなくCPUの内部状態もスタックに格納する必要があるのです.
図12 3項演算の実行中に割り込みハンドラが発生する例
a != 0の判定時に割り込みが発生した場合,結果(CPU内部状態)をスタックに格納しておかないと,割り込みからの復帰後,正しく動作しない.
次に(2)について説明します.割り込みは,関数呼び出しと違ってどのタイミングで発生するか分からないため,複数の割り込みが同時に発生する可能性があります.同時に発生した割り込みに対して優先順位が決まっていないと,どちらの割り込みハンドラに実行を切り替えるべきかをCPUは決められません.このため,割り込み要因ごとに固有の優先順位を設定する必要があります注10.
(1)と(2)を考慮した,割り込みによる実行の切り替えの例を図13に示します.割り込み要因AとBがあり,同時に発生した場合((1)),割り込みの優先度によって割り込みAを選択します((2)).その後,次の命令アドレスとCPUの内部状態をスタックに保存し,割り込み要因Aに対する処理を行う割り込みハンドラAの先頭アドレス0x0800にプログラム・カウンタを更新します((3)).
図13 割り込みハンドラへの実行の切り替え
図11に比べて,割り込みの優先度を考慮している点と,CPUの内部状態もスタックに保存している点が異なる.
● 割り込みの呼び出し関係をイメージしよう
関数の呼び出し関係は,プログラム内に明示されています.それに対して割り込みハンドラの呼び出し関係は,プログラムの記述からは分かりません.このため,設計時にプログラム実行時のイメージを具体的に持っている必要があります.そうでないと,割り込みが頻発して通常の処理がほとんど実行されなかったり,多重割り込み(割り込みハンドラの処理中に別の割り込みが発生するという状況)によって割り込み同士の実行がままならなくなったりする可能性があるからです.
割り込みを使用する場合は,通常の関数の設計に加え,割り込み要因の発生頻度や,割り込みハンドラの処理の重さなどを加味した設計が必要です.
● 割り込みでエレガントなプログラムに挑戦しよう
割り込みは関数呼び出しと違い,呼び出し関係が見えない点,および優先順位を設定する点が特異です.このため敬遠しがちですが,基本は関数呼び出しと大差なく,設計さえ注意すれば,プログラムが見通しの良い自然なものになるはずです.必要十分に割り込みを利用して,エレガントなプログラムにトライしてみてください.
なお本稿では,一部ハードウェア機構にも目を向けて説明しました.本稿を読んで,C言語プログラミングの文法だけでなくコンピュータ・アーキテクチャにも興味を持っていただければ幸いです.
おおやま・まさしろ
NECエレクトロニクス(株)