以下は、低レベルの処理の概要です。ここでは単純な典型的なアーキテクチャについて説明しています。実際のアーキテクチャは、このレベルの詳細では問題にならないほど複雑であったり、異なっていたりする可能性があります。
割り込みが発生すると、プロセッサは割り込みがマスクされているかどうかを調べます。そうである場合、マスクが解除されるまで何も起こりません。割り込みがマスク解除されると、保留中の割り込みがある場合、プロセッサは 1 つを選択します。
次に、プロセッサはメモリ内の特定のアドレスに分岐することにより、割り込みを実行します。そのアドレスのコードは割り込みハンドラと呼ばれます。プロセッサがそこで分岐すると、割り込みがマスクされ (割り込みハンドラが排他制御を持つようになります)、いくつかのレジスタの内容がどこか (通常は他のレジスタ) に保存されます。
割り込みハンドラーは、通常、割り込みをトリガーしたペリフェラルと通信してデータを送受信することにより、必要な処理を行います。割り込みがタイマーによって発生した場合、ハンドラーは OS スケジューラーをトリガーして、別のスレッドに切り替える可能性があります。ハンドラが実行を終了すると、保存されたレジスタを復元し、割り込みのマスクを解除する特別な割り込みからの復帰命令を実行します。
割り込みハンドラーは、他の割り込みの実行を妨げているため、迅速に実行する必要があります。 Linux カーネルでは、割り込み処理は 2 つの部分に分かれています:
- 「上半分」は割り込みハンドラです。通常はハードウェアと通信し、カーネル メモリのどこかにフラグを設定します。
- 「下半分」は、プロセス メモリへのデータのコピー、カーネル データ構造の更新など、その他の必要な処理を行います。割り込みで実行されるため、時間がかかり、システムの他の部分の待機をブロックすることさえあります。
このトピックではいつものように、詳細については、Linux デバイス ドライバーを参照してください。第 10 章は割り込みについてです。
Gilles は割り込みの一般的なケースについて既に説明しましたが、以下は特に Intel アーキテクチャ上の Linux 2.6 に適用されます (これの一部は Intel の仕様にも基づいています)。
割り込みは、プロセッサによって実行される命令のシーケンスを変更するイベントです。
割り込みには 2 種類あります:
- 同期割り込み (例外) 命令の処理中に CPU によって生成される
- 非同期割り込み (割り込み) 他のハードウェア デバイスによって発行された
例外は、プログラミング エラーによって発生します (例:除算エラー 、ページ フォールト 、オーバーフロー ) カーネルで処理する必要があります。彼はプログラムに信号を送り、エラーからの回復を試みます。
次の 2 つの例外が分類されます:
- プロセッサが検出した例外 異常な状態の検出中に CPU によって生成されます。 3 つのグループに分けられます:障害 通常、トラップを修正できます 実行を報告、中止 重大なエラーです。
- プログラムされた例外 プログラマーによって要求され、トラップのように処理されます。
割り込みは、I/O デバイス (キーボード、ネットワーク アダプタなど)、インターバル タイマー、および (マルチプロセッサ システムでは) 他の CPU によって発行できます。割り込みが発生すると、CPU は現在の命令を停止し、新しく到着した割り込みを実行する必要があります。彼は、割り込みが処理された後に (おそらく) 再開するために、中断された古いプロセス状態を保存する必要があります。
割り込みの処理はデリケートな作業です:
- 割り込みはいつでも発生する可能性があります。カーネルはできるだけ早く中断を回避しようとします
- 割り込みは別の割り込みによって中断される可能性があります
- カーネルには、中断してはならない領域があります
2 つの異なる割り込みレベルが定義されています:
- マスカブル割り込み I/O デバイスによって発行されます。マスクされているかマスクされていないかの 2 つの状態になります。マスクされていない割り込みのみが処理されます。
- マスク不可能な割り込み;重大な誤動作 (ハードウェア障害など);常に CPU によって処理されます。
すべてのハードウェア デバイスには、独自の割り込み要求 (IRQ) ラインがあります。 IRQ には 0 から番号が付けられます。すべての IRQ ラインはプログラマブル割り込みコントローラー (PIC) に接続されています。 PIC は IRQ をリッスンし、CPU に割り当てます。特定の IRQ ラインを無効にすることもできます。
最新のマルチプロセッシング Linux システムには、通常、CPU 間で IRQ 要求を均等に分散する新しい Advanced PIC (APIC) が含まれています。
割り込みまたは例外とその処理の間の中間ステップは、割り込み記述子テーブル (IDT) です。このテーブルは、各割り込みまたは例外ベクトル (数値) を指定されたハンドラー (例:除算エラー) に関連付けます。 関数 divide_error()
によって処理されます ).
IDT を通じて、カーネルは発生した割り込みまたは例外を処理する方法を正確に認識します。
では、割り込みが発生したときにカーネルは何をするのでしょうか?
- CPU は、(A)PIC からの IRQ があるかどうかを各命令の後にチェックします
- そうであれば、IDT に問い合わせて、受信したベクトルを関数にマッピングします
- 許可されたソースによって割り込みが発行されたかどうかを確認します
- 中断されたプロセスのレジスタを保存します
- 対応する関数を呼び出して割り込みを処理する
- 中断されたプロセスの最近保存されたレジスタをロードし、再開を試みます
まず、割り込み処理に関与するのは、周辺ハードウェア デバイス、割り込みコントローラー、CPU、オペレーティング システム カーネル、およびドライバーです。周辺ハードウェア デバイスは、割り込みの生成を担当します。オペレーティング システム カーネルからの注意が必要な場合は、割り込み要求ラインをアサートします。これらの信号は、割り込み信号の収集を担当する割り込みコントローラーによって多重化されます。また、割り込み信号が CPU に渡される順序の決定も担当します。割り込みコントローラーは、特定の割り込み要求ライン (IRQL) を一時的に無効にし、再度有効にすることができます (IRQL マスキング)。割り込みコントローラは、収集した割り込み要求を CPU に順次渡します。 CPU 各命令の実行が完了すると、CPU は割り込みコントローラからの待機中の割り込み要求があるかどうかをチェックします。 CPU が待機中の要求があることを検出し、内部 CPU 制御レジスタに割り込みイネーブル フラグが設定されている場合、CPU は割り込み処理を開始します。このように、CPU 内の割り込みフラグを操作し、割り込みコントローラと通信することで、Linux カーネルは割り込みの受け付けを制御することができます。たとえば、Linux は特定のデバイスからの割り込みの受け入れを無効にしたり、割り込みの受け入れをまったく無効にしたりできます。
プロセッサが割り込み要求を受け取るとどうなりますか?まず、CPU は割り込みフラグをリセットして割り込みを自動的に無効にします。割り込み処理が完了すると、それらは再び有効になります。同時に、CPU は、中断されたコードの実行を再開できるように、CPU をユーザー モードからカーネル モードに切り替えるために必要な最小限の作業を行います。 CPU は、Linux カーネルによって埋められた特別な CPU 制御構造を調べて、制御が渡されるコードのアドレスを見つけます。このアドレスは、Linux カーネルの一部である割り込みハンドラの最初の命令のアドレスです。
割り込み処理の最初のステップとして、カーネルは受信した割り込みのベクトルを識別して、システムで発生したイベントの種類を識別します。割り込みベクトルは、Linux がそれを処理するために実行するアクションを定義します。2 番目のステップとして、Linux は残りの CPU レジスタ (CPU によって自動的に保存されなかったもの) を保存し、中断されたプログラムが使用できる可能性があります。これは非常に重要なアクションです。これにより、Linux は中断されたプログラムを透過的に処理できるようになります。3 番目のステップとして、Linux はカーネル環境を設定し、それに必要な CPU 状態を設定することで、カーネル モードへの切り替えを完了します。従属割り込みハンドラが呼び出されます。 (arch\x86\kernel\entry_32.S の BUILD_INTERRUPT3 マクロを調べて、x86 アーキテクチャ関連の例の追加の詳細を取得できます) 周辺機器の場合、これは do_IRQ() ルーチンです。 (arch\x86\kernel\irq.c を調べてください)
ベクトル依存の割り込みハンドラは、通常、irq_enter() および irq_exit() への呼び出しによってラップされます。これらの関数のペアに囲まれたコード領域は、他のそのような領域に関してアトミックであり、cli/sti のペアに関してもアトミックです。 Irq_enter() および irq_exit() は、割り込み処理に関連するいくつかの統計も取得します。最後に、カーネルは vector_irq テーブルを調べて、受信した割り込みのベクトルに割り当てられた irq 番号を見つけ、handle_irq() を呼び出します (arch\x86\kernel から) \irq_32.c).
この時点で、Linux での割り込み処理の共通部分が終了します。これは、カーネルが、デバイス ドライバーによってインストールされたデバイス依存の割り込みハンドラー ルーチンを irq 記述子の一部として参照し、それを呼び出すためです。そのようなハンドラーがドライバーによってインストールされていない場合、カーネルは割り込みコントローラーで割り込みを認識し、一般的な割り込みハンドラーを終了します。
割り込み処理の終了後、カーネルは以前に中断されたプログラムの状態を復元し、このプログラムの実行を再開します。