レガシーガジェット研究所

気になったことのあれこれ。

Linux Kernel ~ 割り込みと例外 ハードウェア編 ~

概要

「詳解Linux Kernel」を参考にVersion 2.6.11のコードリーディングをしていく。CPUのアーキテクチャは書籍に沿ってIntelx86とする。

今回は割り込みと例外の概要と定義及び、それに関連するハードウェアについて見ていく。

割り込みと例外

一般的な割り込みの定義とは「プロセッサが実行する命令列を変更する事象」。CPU内部又はCPU外部のハードウェア回路が生成する電気的な信号により発生する。

割り込みには以下の2種類が存在する。

種類 概要
同期割り込み CPUが命令を実行中にCPUの制御回路が生成する。各命令の実行終了時にのみ制御回路が割り込みを発生させるため同期的と呼ばれる。
非同期割り込み CPUのクロック信号に合わせてCPU以外のハードウェアデバイスが任意のタイミングで発生させる。

Intelプロセッサでは、同期割り込みを「例外」、非同期割り込みを「割り込み」と呼んでいる。

種類 概要
例外(同期割り込み) プログラミングエラーやカーネルが処理する必要のある以上や特殊な状態の場合に発生する。例外としてシグナルを送信したり、ページフォルト例外、intsysenterシステムコールも例外にあたる
割り込み(非同期割り込み) インターバルタイマとI/Oデバイスが生成する。例えばキーボードを叩くと割り込みが発生する

割り込み信号の役割

割り込み信号は通常の制御の流れからCPUを逸脱させる手段である。割り込み信号を届くとCPUは現在行なっている処理を中断し別の処理に取り掛からなければならない。仕組みとしてはプログラムカウンタの現在の値をカーネルモードスタックに退避し、割り込みの種類に対応したアドレスをプログラムカウンタに代入する。

割り込みハンドラや例外ハンドラが実行するコードはプロセスではなく、割り込み発生時に実行中だったプロセスを犠牲にして行うカーネル実行パスの1つである。カーネル実行パスなので割り込みハンドラはプロセスよりも軽い処理となる。

割り込みはカーネルが実行する処理でも以下の理由から慎重に行う必要がある。

  • 割り込みはいつ発生するかわからないため、突然にCPUを奪われる可能性がある。そのためカーネルは出来るだけ早く割り込みへの対応を済ませること、加えてその処理を可能な限り遅延させることを目標にしている。例えばネットワークからデータが届いた場合を考える。ハードウェアがカーネルに割り込みをかけると、カーネルはデータが存在するというフラグだけを立てて一旦プロセッサを解放する。後になって送られてきたデータをバッファに移しそのデータを処理するという流れになる。このように割り込み処理にはすぐく行う緊急部と後で処理する遅延可能部の処理に分かれる。
  • 割り込みは何時も発生するため。割り込み実行中に別の割り込みが発生しネストすることもあり、カーネルはそれに対応している。最後のカーネル実行パスが終了するとカーネルは割り込みでインタラプトされたプロセスを再度実行したり、割り込み信号が再スケジューリング要求だった場合には他のプロセスに実行を切り替える。
  • カーネルは割り込み処理中に新しい割り込みを受け付けるが、中には割り込みを禁止する必要のあるクリティカル区間が多数存在する。そのようなクリティカル区間は最小限にすべきである。

割り込みと例外の定義

Intelのマニュアルでは割り込みと例外を以下のように定義している。

割り込み

種類 説明
マスク可能割り込み(マスカブル割り込み) I/Oデバイスが生成する全てのIRQはマスク可能割り込みを発生させる。マスク状態とマスク解除状態のどちらかの状態を取り、マスク状態の際には制御回路が割り込みを無視する。
マスク不可能割り込み(ノンマスカブル割り込み) 極少数のクリティカルな事象(ハードウェアの故障など)だけがマスク不可能割り込みを発生させる。マスク不可能割り込みはどんな時もCPUに通知される。

例外

プロセッサが検出する例外

CPUが命令を実行中に異常や特殊な状態を検出すると発生する。例外発生時のEIPのレジスタの値によって、さらに3つのグループに分類できる。例外発生時にCPUの制御回路が、EIPレジスタカーネルスタックに退避する。

種類 説明
フォルト 一般的に首服が可能な例外。修復に成功した場合プログラムは連続性を失うことなく実行を続行することができる。退避されるEIPはフォルトを発生させた命令で例外ハンドラ重量時も当該命令から再度実行される。
トラップ トラップ命令を実行すると直ちに発生する例外。カーネルが当該処理を終えて元のプログラムに戻るとプログラムは連続性を失うことなく処理を再開できる。退避されたEIPの値はトラップ命令の次の命令のアドレスとなる。トラップの主な用途はプログラムのデバッグで、この場合の信号の役割は特定の命令が実行されたこと(ブレークポイントなど)をデバッガに通知することである。
アボート 致命的なエラーが発生した場合の例外で制御回路が問題を抱えているため、例外が発生したEIPを退避できない可能性がある。ハードウェアエラーやシステム内の無効な値、矛盾した値が含まれるような深刻なエラーにより発生する。この場合はプロセスを終了させる以外の選択肢がない。制御回路から送られてくる割り込み信号はアボート例外ハンドラに制御を移すための緊急信号。

ソフトウェアが生成する例外

プログラマの指示により発生する例外で、int命令やint3命令で引き起こす。into命令(バッファオーバフロー)やbound命令(アドレス境界を調べる)でも結果が真でない場合に発生する。当該例外はトラップとして制御回路が処理するためソフトウェア割り込みとも呼ばれる。当該例外には2つの用途が存在し、1つはシステムコールでもう1つはデバッガに特定の事象を通知することである。

それぞれの割り込みと例外は0~255までの番号で管理し、Intelはそれをベクタ(Vector)と呼んでいる。マスク不可割り込みのベクタと例外のベクタは固定で、マスク可能割り込みのベクタは割り込みコントローラを操作することで変更が可能。

IRQとPIC

割り込み要求できるハードウェアデバイスコントローラは通常IRQラインと 呼ばれる1本の出力ラインも持っており、IRQラインはプログラマブル割り込みコントローラ(Programmable Interrupt Controller: PIC)の入力ピンに接続されている。IRQラインには0から順番に番号が付いており、先頭のIRQラインは通常IRQ0となる。IntelではIRQnに割り当てるベクタはn + 32となる。(以下の図ではoffsetが32)

PICは以下のように動作する。

  1. IRQラインを監視し信号の発生を調べる。2本以上のIRQラインに信号が発生していた場合には小さいピン番号を持っているIRQラインを選択する。
  2. IRQラインに信号が発生すると以下の処理が走る。
    • 発生した信号を対応するベクタ番号に変換。
    • 発生した信号をプロセッサのINTRピンに送る(割り込みの発生)
    • CPUが割り込み信号を受信したら、特別なバスサイクルでベクタを通知。
    • CPUからの応答を待つ。応答はCPUがPICのI/Oポートに書き込みを行う。
  3. ステップ1へ

CPUはPICに対してI/O命令を出すことでIRQとベクタのマッピングを変更することができる。またPICからの割り込みを禁止することも可能で、これはCPUのeflagsレジスタのIF(Interrupt Flag)フラグをcli(Clear Interrupt)命令でクリアすることで割り込みをマスクできる(実際には無視しているだけ)。再び割り込みを許可する際にはsti(Set Interrupt)命令で行う。禁止した割り込みは失われる訳ではなく。再び割り込みを許可するとPICは即座にCPUに割り込みをかける。

伝統的なPICは2つの8259A型の外部チップをカスケード接続している(マスタ、スレーブ)。それぞれ8個のIRQを持っており、スレーブ側のPICのINT出力はマスタ側のIRQに接続されており、実質利用可能なのは15個までに制限される。

拡張プログラマブル割り込みコントローラ(APIC)

シングルプロセッサ環境では先ほどのようにマスタのPICからCPUのINTRピンに直接接続するが可能であったが、マルチプロセッサ環境では当該方式は取ることができない。

SMP(Symmetric Multi Processing)アーキテクチャの並列性を活かすためには割り込みの分配は重要となってくる。IntelPentium3からはI/O APIC(Advanced Programmable Interrupt Controller)を導入している。これはPICの拡張チップで最近のマザーボードは両方の形式に対応している。

さらに現在の80x86プロセッサはローカルAPICを内蔵しており、32ビットのレジスタ及びバイブクリック、ローカルタイマデバイス、ローカルAPICからの割り込みを受け取るためのピン(LINT0, LINT1)を持っている。全てのCPUのローカルAPICを外部のI/O APICに接続することでマルチAPICシステムを実現している。

以下にマルチAPICシステムの構成を示す。

APIC BusはI/O APICとローカルAPICを接続する。デバイスIRQラインはI/O APICに接続され、I/O APICはローカルAPICへのルータの役割を果たす。

I/O APICには24本のIRQライン、24エントリの割り込み転送テーブル、プログラマブルレジスタAPICバスを通じて他のAPICにメッセージを送受信するための回路から構成される。割り込みの優先度は割り込みテーブルで保持しており、ベクタに対応する優先度を設定することができる。I/O APICは割り込み転送テーブルの情報を用いて、外部からのIRQ信号をAPICバスを通じてローカルAPICへのメッセージに変換する。

I/O APICは外部のハードウェアデバイスから来る割り込みを要求を次の2通りの方法でCPUへと分配する。

  • 静的な分配(Static Distribution)

    IRQ信号を割り込み転送テーブルの対応するエントリに従ってローカルAPICに転送する。ユニキャストやマルチキャスト、ブロードキャストにも対応している。

  • 動的な分配(Dynamic Distribution)

    IRQ信号を最も優先度の低いプロセスを実行しているプロセッサのローカルAPICへと転送する。

    全てのローカルAPICプログラマブルなタスク優先度レジスタ(Task Priority Register: TPR)を保持しており、当該レジスタを利用してカレントプロセスの優先度を計算する。Intelはタスクスイッチが発生する毎にカーネルがTPRを更新することを期待している。

    複数のCPUが最も低い優先度を保持する場合には調停(arbitration)と呼ばれる技術を用いてCPUに割り込み要求を配布する。各CPUのローカルAPICには調整優先度レジスタ(Arbitration Priority Register: APR)が存在し、当該レジスタに0(最低)~15(最高)までの優先度を割り当てる。割り込みをCPUが受け取るとAPRは0になり、割り込みを受け取らなかったCPUのAPRはインクリメントされる。APRの値が15を超えると「割り込みを受けとったCPUが直前に保持していたAPRの値+1」が設定される。前述のように同じタスク優先度を保持しているCPU間ではラウンドロビンで割り込みを分配する。

マルチAPICシステムではCPUがプロセッサ間割り込み(InterProcesser Interrupt: IPI)を生成することができる。送り元CPUのローカルAPICの割り込みコマンドレジスタ(Interrupt Command Register: ICR)に、割り込みベクタと送り先CPUのローカルAPICの識別子を書き込むと、バスト通じて送り先CPUのローカルAPICへとメッセージが送られる。割り込みを受け取ったローカルAPICが管理するCPUに割り込みを発行する。IPIはSMPアーキテクチャの重要な機能でLinuxではCPU間でのメッセージ交換でよく用いられる。

多くの単一プロセッサシステムはI/O APICを持っており、以下のいずれかの方法で用いられている。

  • CPUに接続されている従来の8259A型の外部PICとして利用。ローカルAPICを無効にし、ローカルIEQラインのLINT0がINTRピン、LINT1がNMIピンとなるように設定する。
  • 標準の外部APICとして利用。ローカルAPICを有効にし、外部割り込みを全てのI/O APICを通じて受信する。

参考文献