次のページ 前のページ 目次へ

8. 割り込みと割り込み処理

この章では、割り込みが Linux によってどのように処理されるかを見る。カーネル は、割り込み処理のための汎用的な仕組みとインターフェイスを持っているが、 割り込み処理の詳細の大部分はアーキテクチャに固有のものである。

図表(7.1) 割り込みのルーティングに関する論理的なダイアグラム

Linux は、多種多様なハードウェアを使用して多くの異なるタスクを実行してい る。ビデオデバイスはモニタをドライブし、IDE デバイスはディスクをドライブする 等である。それらの複数のデバイスを同期させて使用することも可能であり、その際 は、ある操作についてのリクエストを送り(たとえば、あるメモリブロックをディスクに 書き出す等)、処理の完了を待つことになる。この方法は、たしかに実行はされ るのだが、非常に効率が悪く、オペレーティングシステムは個々の操作が完了するまで 待ち状態になるため、「何もしないビジー」状態で多くの時間を消費することになる。 もっと効率のよい方法は、リクエストを送った後、より有益な他の仕事をしながら、 リクエストが完了した時点でデバイスから割り込みを受けるという方法である。この 方法ならば、デバイスに対する多くのリクエストを未処理の状態で同時にシステム上に 併存させることが可能になる。

CPU がその時にどのような処理を行っていようともデバイスが割り込みをかけること ができるためには、なんらかのハードウェアによるサポートが必要である。すべてでは ないとしても、Alpha AXP のような大部分の汎用プロセッサは、そのために類似した 方法を使っている。 CPU の物理ピンのいくつかは、そのピンに電圧の変化(たとえば、+5V から -5V への 変化)が生じると、CPU の現在の処理が中断され、割り込みを処理をする特別なコード である割り込み処理コード(interrupt handling code)の実行が開始されるように設計 されている。 それらのピンのひとつはインターバルタイマーに接続され、千分の一秒ごとに割り 込みを受けられるようになっていて、それ以外のピンが SCSI コントローラのような システム上の他のデバイスに接続されている。

しばしばシステムは、割り込みコントローラ(interrupt controller)を使用して、 デバイスの複数の割り込みをグループ化し、その上で CPU 上の単一の割り込みピンに シグナルを送っている。 これは CPU の割り込みピンの数を節約し、システムをデザインする際に柔軟性をも もたらす。割り込みコントローラは、マスクレジスタ(mask register)とステータス レジスタ(status register)を持つことで割り込みを制御している。 マスクレジスタは、ビットの設定によって割り込みの可不可を決定するものであり、 ステータスレジスタはシステム上の現在アクティブな割り込みを示すものである。

システム上の割り込みのいくつかは、物理的に配線が固定されている場合がある。 たとえば、リアルタイムクロックのインターバルタイマーは、割り込みコントローラの ピン 3 に固定的に接続されていることがある。 しかし、ピンが何に接続されているかは、特定の PCI か ISA スロットにどのような コントローラカードが差されているかによって決まる場合もある。たとえば、割り込み コントローラのピン 4 は、PCI スロットの 0 番に接続されているかもしれず、その スロットには、あるときはイーサネットが差されていて、別のときは SCSI コント ローラが差されているかもしれない。 結局、基本的に、割り込みを伝達する仕組みはシステムごとに異なるので、 オペレーティングシステムにはそれに対処するだけの柔軟性がなければならない。

現在の汎用マイクロプロセッサの大部分は、同じ方法で割り込みを処理している。 ハードウェア割り込みが発生すると、CPU は、現在実行中の命令の実行を停止し、 割り込み処理コードそのものか、あるいは割り込み処理コードへと分岐する命令の どちらかを含んだメモリ内のある場所へとジャンプする。 通常このコードは、割り込みモードと言われる CPU の特別なモードで実行されるので、 一般に、このモードにあるときは、他の割り込みは起こらない。 ただし例外があり、ある CPU では割り込みに優先順位を付けているので 優先順位の高い割り込みは起こる場合がある。 すなわち、最高の優先度を持つ割り込み処理コードは非常に入念に書かれていなけれ ばならず、しばしば独自のスタックを備えているため、その割り込み処理コード は、起動されて割り込みを処理する前に、そのスタックを使って CPU の実行状態(CPU の通常のレジスタとコンテキストのすべて)を保存する。 CPU のなかには、割り込みモード専用の特別なレジスタセットを持っているものが あるため、割り込み処理コードは、それらのレジスタを使って必要なコンテキスト保存 操作の大部分を実行する。

割り込み処理が終了すると、CPU の状態は元に戻され、割り込みは解除される。 CPU は、割り込みが起こる前に実行していた処理を継続する。重要なのは、割り込み処 理コードは出来る限り効率の良いものであることであり、オペレーティングシステムが 頻繁に、あるいは長時間、他の割り込みをブロックすることがないようにすることであ る。

8.1 プログラム可能な割り込みコントローラ

システム設計者は、自分の好きな割り込みの仕組みを自由に使うことができるが、 IBM PC では、Intel 82C59A-2 という CMOS プログラム可能な割り込みコントローラ (Programable Interrupt Controller)か、その派生コントローラが使用されている。 このコントローラは、PC の黎明期からあるもので、ISA アドレス空間の決まった場所 (well known location)に存在するレジスタを使ってプログラムすることができる。 最新の補助ロジックチップでさえ、同様のレジスタを ISA メモリの同じ場所に置いて いる。Alpha AXP のような非 Intel ベースの PC では、そうしたハードウェア上の 制約から解放されており、たいていの場合、それとは異なる割り込みコントローラを 使用している。

図表(7.1)では、ふたつの 8 ビットのプログラム可能 な割り込みコントローラ(Programable Interrupt Controller, PIC)が鎖状に繋がれてい て、PIC 1 と PIC 2 の個々のコントローラが、それぞれマスクレジスタ(mask register)と割り込みステータスレジスタ(interrupt status register)を持ってい る様子が示されている。 マスクレジスタは、アドレス 0x21 と 0xA1 にあり、ステータスレジスタは、 アドレス 0x20 と 0xA0 にある。 マスクレジスタの特定のビットに 1 を書き込むと割り込みが可能になり、0 を書き込む と不可になる。そして、ビット 3 に 1 を書き込むと、割り込み 3 が使用可能になり、 0 を書き込むと不可となる。残念なことに(そして、困ったことに)、割り込みマスク レジスタは書き込み専用であり、書き込んだ値を読み出すことはできない。すなわち、 Linux は、マスクレジスタに設定した値のコピーを自分で保存しなければならない ことを意味する。Linux はいつも、割り込みの可不可を設定するルーチンに 保存した、それらのマスク値を修正してから、レジスタにマスク値全部を上書きして いる。

割り込みが発生したとき、割り込み処理コードは、ふたつの割り込みステータス レジスタ(Interrupt status register, ISR)の値を読み出す。 割り込み処理コードは、16 ビットある割り込みステータスレジスタのうち、0x20 に ある ISR を下位 8 ビットとして扱い、0xA0 にある ISR を上位 8 ビットとして 扱う。 それゆえ、0xA0 にある ISR のビット 1 は、システム割り込み 9 として扱われる。 プログラム可能な割り込みコントローラ PIC 1 のビット 2 は利用できないが、 それは、PIC 2 からの割り込みを繋げるために使用されているからであり、PIC 2 上の 割り込みは、PIC 1 のビット 2 としてセットされる。

8.2 割り込み処理のデータ構造の初期化

カーネルの割り込み処理のためのデータ構造体は、デバイスドライバがシステムの 割り込みの制御を要求する際に、デバイスドライバによって設定される。 その場合、デバイスドライバは、Linux カーネルの一連のサービスを使用して、それに よって、割り込みを要求したり、あるいは割り込みの可不可を設定したりする。
[see: request_irq(), enable_irq(), disable_irq(), in arch/*/kernel/irq.c] ( i386, alpha)
個別のデバイスドライバは、そうしたルーチンを呼び出して、それぞれの割り込み処理 ルーチンのアドレスを登録する。

割り込みのなかには、PC アーキテクチャの慣習によって固定されているものがある ので、その場合、ドライバは、初期化される際、単にその割り込みを要求するだけで よい。 これは、フロッピーディスクドライバの動作そのものであり、フロッピーディスクドラ イバは、いつも IRQ 6 を要求する。 デバイスドライバは、そのデバイスがどの割り込みを使うかを知らない場合もある。 PCI デバイスドライバの場合は、割り込み番号は常に明らかなので、 そうした問題は起きない。残念ながら、ISA デバイスドライバが割り込み番号を知ろう とする場合は、簡単な方法というのはない。Linux は、この問題を解決するために、 デバイスドライバに割り込みを検出させる。

まず、デバイスドライバは、何らかの形でデバイスに働きかけて、デバイスに割り 込みを発生させる。次に、システム上でまだ割り当てられていないすべての割り込み を有効にする。これによって、そのデバイスが伝達できずに保留して いた割り込みが、プログラム可能な割り込みコントローラ経由で伝達される。 Linux は、割り込みステータスレジスタを読み出し、デバイスドライバにその内容を 返す。0 でない値が返った場合、それは、ひとつ、もしくはそれ以上の割り込みが、 割り込み検出中に発生したことを意味する。この時点で、ドライバは、検出を 終了し、割り当てられていない割り込みは、すべて無効にされる。
[see: irq_problem_*(), in arch/*/kernel/irq.c]( i386, alpha)
ISA デバイスドライバが、IRQ 番号の検出に成功した場合、ドライバは、通常通り その IRQ 番号の制御を要求する。

PCI ベースのシステムでは、ISA ベースのシステムよりももっと動的である。 ISA デバイスが使用する割り込みピン(IRQ)は、しばしばハードウェアデバイス上の ジャンパを使って設定されるので、デバイスドライバ内で固定されている。 反対に、PCI デバイスは、システムのブート時に PCI が初期化される際、 PCI BIOS か PCI サブシステムによって割り込みを割り当てられる。 個々の PCI デバイスは、A, B, C, D の 4 つの割り込みのうち、ひとつを使用する。 (訳注: 規格上は、ひとつから 4 つまで使用できるそうです。) これはデバイスの作成時に固定されており、大部分のデバイスは標準的なピン A を 割り込みに使用する。個々の PCI スロット上の A, B, C, D の 割り込みライン(interrupt line)は、割り込みコントローラに配線されている。 それゆえ、PCI スロット 4 のピン A は、割り込みコントローラのピン 6 に配線されて いて、PCI スロット 4 のピン B は、割り込みコントローラのピン 7 に配線されている 等々となっている。

割り込みがどのように伝達されるかは、完全にシステム固有のものであるので、 そのシステムの PCI 割り込みを伝達するトポロジーを理解しているなんらかの設定 コードが存在する必要がある。 Intel ベースの PC では、これは、起動時に実行されるシステム BIOS であるが、 BIOS のないシステム(たとえば、Alpha AXP ベースのシステム)では、Linux カーネルが そのセットアップを行う。
[see: arch/alpha/kernel/bios32.c]
PCI 設定コードは、割り込みコントローラのピン番号をデバイスごとの PCI コンフィグレーションヘッダに書き込む。 PCI 設定コードは、、PCI の割り込み伝達トポロジーやデバイスの PCI スロット番号、 およびそれが現在どの PCI 割り込みピンを使っているかという知識を利用して、割り込 みピン(もしくは IRQ)を決定する。 デバイスが使用する割り込みピンは固定されていて、そのデバイスの PCI コンフィグレーションヘッダのフィールド内に保存されている。 PCI 設定コードは、割り込みライン(Interrupt Line)フィールドに、その情報を書き込 む。割り込みラインフィールドはこのために予約されたフィールドである。 デバイスドライバの実行時には、ドライバはこの情報を読み出し、それを使って Linux カーネルに割り込み制御を渡すよう要求する。

たとえば、PCI-PCI ブリッジが使用されているシステム上などでは、PCI 割り込みを 必要とするソースがたくさん存在する場合がある。割り込みソースの数が、システムの プログラム可能な割り込みコントローラ上にあるピンの数を超える場合もある。 その場合、PCI デバイスは割り込みを共有し、割り込みコントローラ上のひとつの ピンで、複数の PCI デバイスからの割り込みを受けることがある。 Linux がこれをサポートする場合、割り込みソースの中でその割り込みピンを最初に 要求したデバイスが、それを共有するかどうか明示できるようにするという方法 を取っている。割り込みが共有されるときは、いくつかの irqaction データ構造体が作成され、それらのデータ構造体が、 irq_action 配列の配列内にある各エントリ によってポイントされる。 共有割り込みが起こると、Linux は、そのソースに対するすべての割り込みハンドラを 呼び出す。割り込みの共有が可能なすべてのデバイスドライバ(これらはすべて PCI デバイスドライバでなければならない)は、割り込みがサービスされていないときは、 そのドライバの割り込みハンドラーがいつ呼び出されてもいいように準備していなけれ ばならない。

8.3 割り込み処理

図表(7.2) Linux 割り込み処理のデータ構造

Linux の割り込み処理サブシステムの主要なタスクのひとつが、割り込み要求を 正しい割り込み処理コードへと伝えることである。このコードは、システムの 割り込みトポロジーを理解していなければならない。たとえば、フロッピーコントロー ラが割り込みコントローラのピン 6 上で割り込む場合、それは、その割り込みが フロッピーからのものであることを認識して、フロッピーデバイスの割り込み処理 コードにそれを伝えなければならない。Linux は、一群のポインタを使って、システム の割り込み処理ルーチンのアドレスを持つ各データ構造体を参照する。これらの ルーチンは、システム上のデバイス用のデバイスドライバに属しており、ドライバが 初期化されるとき、必要な割り込みを要求するのは個々のデバイスドライバの責任で ある。図表(7.2)では、 irq_action は、 irqaction データ構造体 へのポインタの配列であることが示されている。個々の irqaction データ構造体は、割り込み処理ルーチンのアドレスが入っていると同時に、その 割り込みに対する処理ルーチンに関する情報が含まれている。割り込みの数と その処理方法は、アーキテクチャや、時にはシステムによって異なるので、Linux の 割り込み処理コードは、アーキテクチャ固有のものである。これは、irq_action 配列の配列が、存在する割り込みソースの数によって 異なることを意味している。

割り込みが起こると、Linux は、まず、システムのプログラム可能な割り込みコント ローラの割り込みステータスレジスタを読み出して、その割り込みソースを 判断する。そして、そのソースを irq_action 配列の配列に対するオフセット値に変換する。 それゆえ、たとえば、フロッピーコントローラからの割り込みコントローラのピン 6 上の割り込みは、割り込み処理ルーチンの配列内での 7 番目のポインタへと変換され る。発生した割り込みに対する割り込み処理ハンドラがない場合、Linux カーネルは エラーをログに記録するか、あるいは、その割り込みソースの全 irqaction データ構造体に対する(デフォルトの) 割り込み処理ルーチンを呼び出す。

デバイスドライバの割り込み処理ルーチンが Linux カーネルによって呼び出された とき、ルーチンは、割り込みが発生してそれに対処することになった理由を効率的に 調べなければならない。 割り込みの原因を見つけるため、デバイスドライバは、割り込みの発生した デバイスのステータスレジスタを読み出す。デバイスは、エラーを報告しているかも しれないし、要求された処理が完了したことを報告しているかもしれない。 たとえば、フロッピーコントローラが、フロッピーディスクの正しいセクタの上 への読みとりヘッドの位置移動を完了したことを報告しているのかもしれない。 割り込みの原因が判明すると、デバイスドライバは、さらに次の仕事をする必要が 生じる場合がある。その場合、Linux カーネルは、その仕事を後回しにすることができ る仕組みを持っている。すなわち、それによって、CPU が、割り込みモードに長時間 入ったままになるのを防止する。それについての詳細は、 「デバイスドライバ」の章を見てほしい。

REVIEW NOTE: 高速割り込みと低速割り込みというのは、Intel 系での実装なのか?

(脚注 1): 実際、フロッピーディスクコントローラは、慣習上、PC システムにおいて 決まった割り込みを使うデバイスのひとつである。フロッピーディスクコントローラ は、いつも割り込み 6 に接続されている。


次のページ 前のページ 目次へ