4章 割り込みと例外

  • 割り込み:プロセッサが実行する命令列を変更する事象
  • 同期割り込み(例外)
    • CPUが命令を実行中にCPUの制御回路が生成
    • 各命令の実行終了時のみ発生するため同期的と呼ばれる
    • プログラミングエラーやカーネルが処理しなければならない状態の場合に発生
  • 非同期割り込み(割り込み)
    • CPU以外のハードウェアデバイスが任意の時点で(非同期的に)生成
    • インターバルタイマやI/Oデバイスが生成

4.1 割り込み信号の役割

  • 通常の制御の流れからCPUを逸脱させる手段
  • PCの現在値をカーネルモードスタックに退避し、割り込みの種類に応じたアドレスをPCに代入する事で割り込みを行う
    • プロセス切り替え(3章)と異なるのは、割り込みはカーネル実行パスの一つだということ
  • 割り込み処理はきわめて慎重に行う必要がある
    • いつ発生するかわからないので、可能な限り早く割り込みへの対応を済ませ、割り込み処理の多くの部分を可能な限り遅延させなければならない
    • 割り込み発生中に別の割り込みが発生する事があるので、入れ子になっても支障無く実行されなければならない
    • 割り込みを禁止しなければならないクリティカル区間は最小限でなければならない

4.2 割り込みと例外

割り込みの種類
  • マスク可能割り込み
    • I/Oデバイスが生成
    • 制御回路はマスク状態の割り込みをマスクが解除されるまで無視する
  • マスク不可割り込み
    • クリティカル事象(ハードウェア故障など)が生成
    • どんなときでもCPUに通知される

例外の種類
  • プロセッサが検出する例外
    • フォルト:一般的に修復可能な例外、例外要因を除去できたら同じEIPから処理が再開される
    • トラップ:トラップ命令で発生する例外、ブレークポイントなどで利用され次のEIPから処理が再開される
    • アボート:致命的エラーが発生した場合の例外、プロセスは終了される
  • ソフトウェアが生成する例外
    • プログラマが発生させる例外
    • ソフトウェア割り込みと呼ばれることもある
  • それぞれの割り込みと例外は、ベクタと呼ばれる8bit符号なし整数で識別

4.2.1 IRQと割り込み

  • ハードウェアデバイスコントローラはIRQラインと呼ばれる1本の出力ラインを持つ
  • 全てのIRQラインはプログラマブル割り込みコントローラ(PIC)の入力ピンに接続
  1. IRQラインを監視し信号の発生を見る
  2. 信号が発生した場合は
    1. 発生した信号を対応するベクタに変換
    2. 信号をプロセッサのINTRピンに送る
    3. CPUが割り込み信号をアサートしたら、特別なバスサイクルでベクタを通知
    4. CPUからのACKを待ち、応答が返ればINTRラインをクリア
  • PICはIRQラインを個別に禁止することができる
    • 割り込みが失われるわけではない
    • cli/stiでeflagsレジスタのIFフラグを落とす/設定する
4.2.1.1 拡張プログラマブル割り込みコントローラ(APIC)
  • 複数プロセッサの為の拡張
  • 24本のIRQラインと24エントリのIRT(Interrupt Redirection Table)を持つ
  • 各プロセッサはローカルAPICを内蔵し、それぞれを外部のI/O APICに接続
  • I/O APICは外部のハードウェアデバイスから来る割り込み要求をCPUへと分配
    • 静的な分配:割り込み転送テーブルに従ってCPUに分配
    • 動的な分配:タスク優先度レジスタ(Task Priority Register/TPR)を利用して、最も優先度の低いプロセスを実行しているプロセッサに分配
  • CPUがプロセッサ間割り込み(InterProcessor Interrupt/IPI)を生成する事ができる

4.2.2 例外

20種類の例外があり(資料148−150)、20-31まではIntelにより予約

4.2.3 割り込みディスクリプタテーブル

  • 割り込みや例外のベクタとハンドラの対応を登録するテーブル(IDT)
    • 形式はGDTやLDTとほぼ同じ(8バイト x 256エントリ = 最大2048バイト)
    • メモリ上の任意の位置に置く事ができる(lidtコマンド)
    • IDTは3種類のディスクリプタを提供
      • タスクゲート:プロセスのTSSセレクタを置き、割り込み信号が発生したときに実行していたプロセスのTSSと置き換える
      • 割り込みゲート 割り込みや例外ハンドラがあるセグメントの、セグメントセレクタとセグメント内のオフセットを置く。
    このセグメントを制御する間は、プロセッサの割り込み許可フラグ(IFフラグ)を落とし、マスク可能割り込みを禁止する
      • トラップゲート:IFフラグを変更しない点以外は割り込みゲートと同様
    • 割り込みの処理には割り込みゲートを使用し、例外の処理にはトラップゲートを使用する
    • タスクゲートはダブルフォルト例外のみ使用

4.2.4 ハードウェアの割り込みと例外の処理

  1. 1つの命令の実行が終了したとき、制御回路は割り込みか例外が発生していないか調べる
  2. 割り込みまたは例外のベクタやIDT、GDTから、割り込みまたは例外のハンドラがあるセグメントのベースアドレスを指す、セグメントディスクリプタを読み取る
  3. 現行の特権レベルが適切かどうか調べる
  4. スタックにレジスタの内容を退避
  5. 割り込みや例外ハンドラのセグメントディスクリプタ等をレジスタに読み込む
  6. 処理が終了したら、退避していたレジスタを戻す
  7. 適切な特権レベルで動作していたか調べる
  8. カーネル空間にアクセスできないようにセグメントレジスタを適切に設定

4.3 例外および割り込み処理のネスト

  • 割り込み処理中に別の割り込みが発生する事がある
    • その場合、割り込みが入れ子構造になる
    • 入れ子状態を許可する条件は、割り込みハンドラが絶対に実行を中断しないこと
      • 割り込み実行中はプロセスの切り替えができない
    • 例外によるカーネル実行パスは2つまでしか重ならない
      • カーネルモードで発生する例外が「ページフォルト例外」しかないため
    • 例外ハンドラが割り込みハンドラに割り込む事は無い
      • 割り込みハンドラはページ処理をしないため

カーネル実行パスに別のカーネル実行パスを割り込ませる理由は
  • PICやデバイスコントローラのスループットを向上させる
  • 優先度レベルが無い割り込みモデルを実現させる

4.4 割り込みディスクリプタテーブルの初期化

  • システムの初期化時にIDTを初期化
  • 不正な割り込みの発生を防ぐ為に、特定の割り込みやトラップのゲートディスクリプタをカーネルモードに設定
  • ユーザーモードプロセスが生成する信号の為に、一部の割り込みやトラップのゲートディスクリプタをユーザーモードに設定

4.4.1 割り込み、トラップ、システムゲート

Linuxは5つの割り込みディスクリプタを提供
  • 割り込みゲート:Intelの割り込みゲートと同じで、カーネルモード、割り込みハンドラは全て割り込みゲートで実装されている
  • システムゲート:Intelのトラップゲートと同じで、ユーザーモード、一部の例外ハンドラが実装されている
  • システム割り込みゲート:Intelの割り込みゲートのうちユーザーモードで動作するもの
  • トラップゲート:Intelのトラップゲートと同じで、カーネルモード、殆どの例外ハンドラがトラップゲートで実装
  • タスクゲート:ダブルフォルト例外ハンドラが実装されている

4.4.2 IDTの予備初期化

  • コンピュータの起動直後はBIOSがIDTを初期化する
    • Linuxが起動すると2度目の初期化を行う
  • 1回目の初期化では、IDTが置かれているエントリを全て何もしない割り込みゲートに割り当てる
  • 2回目の初期化で本来使用する割り込みや例外ハンドラに置き換える

4.5 例外処理

  • 例外が発生したとき、カーネルはプロセスに異常状態を知らせるシグナルを送る
    • プロセスはシグナルを受け取り、復旧処理やアボートを行う

例外の基本処理
  1. 大半のレジスタの内容をカーネルモードスタックに退避(アセンブリ言語で記述)
  2. 例外処理を行う(C言語で記述)
  3. ハンドラを終了

4.5.1 例外ハンドラ用のレジスタ退避処理


省略

4.5.2 例外ハンドラの開始と終了


省略

4.6 割り込み処理

  • 割り込み発生時は、カレントプロセスに対しシグナルを発生させない
  • 割り込み処理は3つに分類
    • I/O割り込み
    • タイマ割り込み(6章で説明)
    • プロセッサ間割り込み

4.6.1 I/O割り込み処理

  • I/O割り込みハンドラは、複数のデバイスを同時に扱う事が要求される
  • 割り込みハンドラの柔軟性は2つのうちいずれかで実現
    • IRQ共有
      • 割り込みサービスルーチン(ISR)を使用し、どのデバイスを実行すべきか調べる
    • IRQの動的割り当て
      • デバイスドライバへのIRQラインの割り当てをできる限り遅延させる
  • 割り込みの緊急性に応じて3つに分類
    • 緊急
      • 可能な限り早く実行されなければならないものは、マスク可能割り込みを禁止したまま即座に実行する
    • 非緊急
      • 割り込み許可の状態で実行される
    • 非緊急で遅延可能
      • 可能な限り遅延させる

割り込みハンドラの基本動作は
  1. カーネルモードスタックにIRQの値とレジスタの内容を退避
  2. IRQラインをサービスしているPICに、さらに割り込みを発行できるようにACKを返す
  3. IRQを共有する全てのデバイスのISRを呼び出す
  4. 処理を終了する
4.6.1.1 割り込みベクタ
  • 物理IRQは32−238の範囲のどのベクタにも割り当てることができる
    • IBM PC互換機は幾つかのデバイスの接続IRQラインは決められている
  • IRQの選び方は
    • ハードウェアのジャンパ設定を利用する(古いデバイスカードのみ)
    • デバイス追加時に付属のユーティリティを利用する
    • システム起動時にハードウェアのプロトコルを利用して検出する方法
  • カーネルは割り込みを許可する前に、IRQ番号に対応するI/Oデバイスを知っていなければならない
    • 対応関係はデバイスドライバの初期化時に決定
4.6.1.2 IRQデータ構造
  • カーネルは予期しない割り込みを受け続ける場合、そのIRQラインを禁止する
    • IRQラインに対応するISRが無いか、そのハードウェアデバイスが発生する割り込みをISRが自身のものと認識しないような割り込み
  • 予期しない割り込みが一定の割合を超えた場合、そのラインを禁止する
  • Linuxは、複数種のPIC回路を一律の方法で利用できるように、PIC名と7つの標準メソッドからなるオブジェクトを利用する
    • ドライバがシステムに組み込まれているPICの種類を気にせず利用できる
  • 1つのIRQは複数のデバイスで共有できる
  • CPUの状態を把握する為に、1つのCPUに1対1で対応し、CPUの状態を記録する配列irq_statを持つ
4.6.1.3 マルチプロセッサシステムのIRQ分配処理
  • 割り込み信号は全てのCPUにラウンドロビン方式で分配
  1. ハードウェアデバイスがIRQ信号を作成
  2. マルチAPICシステムは1つのCPUを選び、信号をそのローカルAPICに送る
  3. 信号を受けたローカルAPICは、自身のCPUに対してのみ割り込みを発生させる
  • 分配はハードウェアが自動的に実行するが、均等な分配に失敗する事もある
    • カーネルはkirqdカーネルスレッドを使ってIRQ割り当てを調整できる
4.6.1.4 複数のカーネルモードスタック
  • thread_union 構造体(3章)の大きさが8KBのときは、カーネル実行パスは、カレントプロセスのカーネルモードスタックを使用
  • 大きさが4KBのとき、3種類のカーネルモードスタックを使用
    • 例外スタック:例外を処理
    • ハードIRQスタック:割り込みを処理
    • ソフトIRQスタック:遅延処理(4.7)
4.6.1.5 割り込みハンドラ用のレジスタ退避処理

省略
4.6.1.6 do_IRQ()関数
  1. thread_union構造体が4KBのときは、ハードIRQスタックを切り替える
  2. __do_irq()関数を呼び出す
  3. 変更したスタックを戻す
  4. 実行待ちの遅延処理が無いか調べる
4.6.1.7 __do_IRQ()関数
  1. 必要なスピンロックを取得
  2. 使用するIRQラインを禁止する
  3. 発生した割り込みに対して処理を行う必要があるかどうか調べる
  4. 割り込み処理を実行
  5. IRQラインの禁止を解除
  6. スピンロックを解放する
4.6.1.8 失われた割り込みの再生処理
  • IRQラインを禁止する前に発生した割り込みであるにも関わらず、割り込みが失われる可能性がある
    • CPUがACKを返すまでのタイムラグがある為
  • 割り込みが失われた場合、 enable_irq()関数は割り込みをハードウェアに強制発生させる
4.6.1.9 割り込みサービスルーチン
  • ISRは複数のIRQラインを扱う事ができる
  • 〃 同じ種類のデバイスを複数扱う事ができる
  • 〃 割り込んだカーネル実行パスの実行コンテキストに割り込む事ができる
  • が、殆どのISRはこの機能を利用していない
  • ISRの構造は、扱うデバイスの特性に依存
4.6.1.10 IRQラインの動的割り当て処理
  • 複数のデバイスで同じIRQラインを使用する
    • 1度に1つのデバイスしかIRQラインを使用できないように制御する
      • 既に使用されていて、IRQ共有が許可されていない場合はエラーコードを返す

4.6.2 プロセッサ間割り込み処理

  • プロセッサ間割り込み(IPI)を使うと、他のCPUに割り込み信号を送る事ができる
    • Linuxは3種類のIPIを実装
      • CPUを停止したり、メモリタイプレンジレジスタの値を変更できる
      • 指定したCPUに再スケジューリングを行わせる
      • CPUのアドレス変換バッファを無効にする

4.7 ソフト割り込みとタスクレット

  • 2種類の緊急性の無い割り込み可能なカーネル処理
    • 割り込みを全て許可した上で実行できる遅延可能な処理

ソフト割り込み
  • 静的に割り当て
  • 同じ種類でも複数のCPU上で並列実行が可能

タスクレット
  • 動的に割り当て
  • 同じ種類のタスクレットは順次処理される

4種類の操作
  • 初期化処理
  • 起動要求:遅延処理を保留中にし、次のカーネルのスケジュールで実行されるようにする
  • マスク処理
  • 実行処理:保留中の遅延処理を実行:起動要求と同じCPU上で実行されなければならない

4.7.1 ソフト割り込み

  • Linuxの定義は6種類
  • ほとんどがタスクレットで十分
4.7.1.1 ソフト割り込みで使用するデータ構造

省略
4.7.1.2 ソフト割り込み処理
  1. ローカルCPUのIFフラグを保存し、割り込みを禁止する
  2. ソフト割り込みを保留中にする
  3. ソフト割り込みが禁止されているときはステップ5へ
  4. ローカルCPUのksoftirqdカーネルスレッドを起床する
  5. IFフラグの状態を戻す

定期的に保留中のソフト割り込みが無いか確認する
  • カーネルがローカルCPUのソフト割り込みを許可したとき
  • I/Oに対する割り込み処理が終了したとき
  • システムがI/O APICを使用している場合、ローカルタイマ割り込みの処理を終了したとき
  • プロセッサ間割り込みによって起動された関数の処理を終了したとき
  • ksoftirqd/nカーネルスレッドが起床したとき
4.7.1.3 do_softirq()関数
  • ソフト割り込みを確認した場合、この関数を呼び出して処理する
  1. 現在、ソフト割り込みが禁止されているならば実行を終了
  2. ローカルCPU上のIFフラグを退避し、割り込みを禁止する
  3. 必要ならばソフトIRQスタックに切り替える
  4. __do_softirq()関数を呼び出す
  5. スタックを戻し、IFフラグの状態を戻す
4.7.1.4 __do_softirq()関数
  • 保留中の全てのソフト割り込みが実行されるまで動作し続ける
    • ただし、一定時間がたつと強制終了し、残りの処理はksoftirqd カーネルスレッドが処理する
4.7.1.5 ksoftirqd カーネルスレッド
  • CPUごとにksoftirgd/n カーネルスレッドを所持(nはCPUの論理番号)
  • 起動したときに保留中のソフト割り込みがあればdo_softirq()関数を呼び出して処理
    • 処理が終わればプロセスを切り替える
  • 高い頻度で行われる同じソフト割り込みの起動要求の問題を解決
  • do_softirq()関数がとまらない場合、カーネルスレッドを起床することで、他の優先度の高いプログラムを実行できる
  • ksoftirqd カーネルスレッドを使用しない場合、ソフト割り込み中のソフト割り込みを完全無視するか、do_softirq()関数が保留中のソフト割り込みを絶え間なく監視するしかない

4.7.2 タスクレット

  • I/O ドライバが利用する遅延処理
  • ソフト割り込みの上に実装
  • 優先度の高低により、呼び出す関数が異なる
  1. タスクレットがスケジュールされていないか調べる
  2. IFフラグを退避し、割り込みを禁止する
  3. タスクレットディスクリプタをリストに登録
  4. 割り込みの状態を戻す
  5. リストに対して以下の処理を行う
    1. タスクレットが別のCPUで動作していないか調べる
    2. タスクレットの実行が禁止されていないか調べる
    3. タスクレットを実行する

4.8 ワークキュー

  • ワークキューは、カーネル関数の起動処理を行い、ワーカスレッドに後から実行させることが出来る
    • 遅延処理と異なるのは、ワークキューはプロセスコンテキストで動作する点
      • 実行を中断する可能性がある関数を実行できる
4.8.1.1 ワークキューのデータ構造

省略
4.8.1.2 ワークキュー関数
  • create_workqueue("foo")関数で、ワークキューとn個のワーカスレッドを作成することが出来る
  • 関数をワークキューに挿入する操作
  1. 挿入する関数がすでにワークキュー内にあるかどうか確認する
  2. ワークキューに関数を挿入する
  3. ワーカスレッドが停止している場合は起床する
  • ワーカスレッドは起床すると保留中の関数を実行する
  • ワーカスレッドを休止して、別のCPUに移して再開することも出来る
    • ワークキュー内の関数が処理を終了するまで待たなければならないこともある
4.8.1.3 あらかじめ用意してあるワークキュー
  • あらかじめ用意してあるワークキューを自由に利用できる
    • めったに呼び出されない関数を実行する場合に有効

4.9 割り込みおよび例外からの復帰


(Understanding the Linux Kernel Third Editionより引用)

メンバーのみ編集できます