割り込み処理
ハードウェア割り込みに関するメモ。
irq_descの構造を図1に示す。
各IRQ毎のirq_desc構造体が並んでいる。actionリストに、request_irq()で登録した割り込みハンドラを管理する構造体(irq_action)がチェーンされる。基本的には1つのIRQに1つのハンドラだが、SA_SHIRQを付けて登録することで1つのIRQに複数のハンドラを登録することができる。この場合は、irq_actionがnextチェーンされる。もちろん、デバイスドライバなどの処理が割り込みを共有できる作りになっていないといけない。
irq_descのhandlerには割り込みコントローラ依存のハンドラが登録されている。ここに登録されたハンドラを使って、実際に割り込み禁止/解除やEOI(End of Interrupt)を行う。各IRQでどのようなハンドラが使われているは、/proc/interruptで確認できる。最近のPCであればだいたいIO-APIC-edgeとかIO-APIC-level。
これらのテーブルはentry.Sに定義されている。
例外ベクタの設定
割り込み発生時に上記の割り込みのエントリポイントへ処理を渡すため、例外ベクタ(IDT)を設定する。
i8259の場合、ハードウェア割り込みにはベクタ番号32〜を使用している(31まではプロセッサの予約領域)。
IDTの32番から順番にIRQ0〜15の例外ベクタをset_intr_gate()で設定している。(i8259.c::init_IRQ)
あわせてIRQを例外ベクタ32〜発生させるように割り込みコントローラを設定する。(i8259.c::init_8259A())
2. IDTの該当エントリに登録されているアドレスにジャンプ
4. irq_desc[]に登録されているハンドラへ
2. ret_from_intrへジャンプ
3. カーネル/ユーザモードへの復帰処理へ
RPL 0: 戻り先がカーネルモード → resume_kernelへジャンプ
(CONFIG_PREEMPTが未定義ならrestore_allへ)
RPL 0以外: 戻り先がユーザモード → resume_userspaceへジャンプ
4. resume_kernel: - 必要ならPreempt
5. resume_userspace:
6. restore_all:
7. work_pending
[関連関数]
request_irq()
irq_enter()
in_interrupt()
[関連ページ]
例外処理
割り込みハンドラの登録
割り込みハンドラはirq_desc[]に登録されている。デバイスドライバなどが割り込みハンドラを登録する場合はrequest_irq()で登録する。irq_descの構造を図1に示す。
各IRQ毎のirq_desc構造体が並んでいる。actionリストに、request_irq()で登録した割り込みハンドラを管理する構造体(irq_action)がチェーンされる。基本的には1つのIRQに1つのハンドラだが、SA_SHIRQを付けて登録することで1つのIRQに複数のハンドラを登録することができる。この場合は、irq_actionがnextチェーンされる。もちろん、デバイスドライバなどの処理が割り込みを共有できる作りになっていないといけない。
irq_descのhandlerには割り込みコントローラ依存のハンドラが登録されている。ここに登録されたハンドラを使って、実際に割り込み禁止/解除やEOI(End of Interrupt)を行う。各IRQでどのようなハンドラが使われているは、/proc/interruptで確認できる。最近のPCであればだいたいIO-APIC-edgeとかIO-APIC-level。
図1 irq_descの構造
割り込みのエントリポイント
irq_entries_start[]に各IRQの割り込み処理を呼び出すコードが格納されている。また、interrupt[]には各irq_entries_startへのアドレスが格納されており、例外ベクタにハンドラのアドレスを設定する際に利用する。これらのテーブルはentry.Sに定義されている。
割り込みのエントリポイント
entry.S:inerrupt entry.S::irq_entries_start
+------------+ +-----------------------+
| |--------->| pushl $vector-256 | vector = 0
+------------+ | jmp common_interrupt |
| |----+ | |
+------------+ | +-----------------------+
: +---->| | vector = 1
: | |
| |
+-----------------------+
:
:
例外ベクタの設定
割り込み発生時に上記の割り込みのエントリポイントへ処理を渡すため、例外ベクタ(IDT)を設定する。i8259の場合、ハードウェア割り込みにはベクタ番号32〜を使用している(31まではプロセッサの予約領域)。
IDTの32番から順番にIRQ0〜15の例外ベクタをset_intr_gate()で設定している。(i8259.c::init_IRQ)
あわせてIRQを例外ベクタ32〜発生させるように割り込みコントローラを設定する。(i8259.c::init_8259A())
例外ベクタ
idt_table[256]
+-------------+
| #0 |
+-------------+
| |
+-------------+
| |
:
+-------------+
| #32 IRQ0 | --> interrupt[0] (* 割り込みのエントリポイント参照)
+-------------+
| #33 IRQ1 | --> interrupt[1]
+-------------+
| #34 IRQ2 | --> interrupt[2]
+-------------+
:
割り込みハンドラ呼び出しの流れ
1. 割り込み発生割り込みコントローラによって例外ベクタ番号(32〜)が付加されて、CPUに通知される。
2. IDTの該当エントリに登録されているアドレスにジャンプ
例外ベクタ番号に対応するハンドラを呼び出す。
(irq_entries_start[IRQ#]にジャンプ)
3. 一旦common_interrupt:に飛んでdo_IRQ()へdo_IRQではirq_enter()を呼んで、割り込みコンテキストにする。
あとはCPU非依存の__do_IRQ()へ。
あとはCPU非依存の__do_IRQ()へ。
4. irq_desc[]に登録されているハンドラへ
割り込み処理の終了の流れ
1. do_IRQ()からリターン2. ret_from_intrへジャンプ
3. カーネル/ユーザモードへの復帰処理へ
スタック上のCS
Reg.のRPL(RequestPrevilegeLevel)から、割り込み発生時のPrevilegeLevelをチェックして、カーネルモード,ユーザーモードのどちらに戻るのか判定する。
RPL 0: 戻り先がカーネルモード → resume_kernelへジャンプ
(CONFIG_PREEMPTが未定義ならrestore_allへ)
RPL 0以外: 戻り先がユーザモード → resume_userspaceへジャンプ
4. resume_kernel: - 必要ならPreempt
preempt_count!=0ならrestore_allへジャンプ
TIF_NEED_RESCHEDがセットされていなければrestore_allへジャンプ
preempt_countにPREEMPT_ACTIVEを設定して
schedule()コール - Preempt
preempt_countを0に戻す
TIF_NEED_RESCHEDがセットされていなければrestore_allへジャンプ
preempt_countにPREEMPT_ACTIVEを設定して
schedule()コール - Preempt
preempt_countを0に戻す
5. resume_userspace:
thread_info.flagsにTIF_SYSCALL_TRACE|TIF_SYSCALL_AUDIT以外のbitが立っていればwork_pendingへジャンプ。そうでなければrestore_allへジャンプ。
6. restore_all:
レジスタを元に戻す。
iret
iret
7. work_pending
TIF_NEED_RESCHEDが立っていればschedule()をコール
restore_allへジャンプ
restore_allへジャンプ
[関連関数]
request_irq()
割り込みハンドラを登録する。
irq_enter()
ハードウェア割り込みを処理中(割り込みコンテキスト中)であることを示す状態にする。
具体的にはcurrent_thread_info()->preempt_countのHARDIRQ_MASKの部分をインクリメントしている。
preempt_countのビットフィールド
具体的にはcurrent_thread_info()->preempt_countのHARDIRQ_MASKの部分をインクリメントしている。
preempt_countのビットフィールド
PREEMPT_MASK: 0x000000ff
SOFTIRQ_MASK: 0x0000ff00 処理中のS/W割り込み数
HARDIRQ_MASK: 0x0fff0000 処理中のH/W割り込み数
SOFTIRQ_MASK: 0x0000ff00 処理中のS/W割り込み数
HARDIRQ_MASK: 0x0fff0000 処理中のH/W割り込み数
in_interrupt()
現在のコンテキストが割り込みコンテキストかどうかを取得できる。これが、0であればプロセスコンテキスト。
preempt_countのHARDIRQ_MASK | SOFTIRQ_MASK部分を取得している。
類似ルーチンでin_irq(),in_softirq()がある。これらはそれぞれHARDIRQ_MASK,SOFTIRQ_MASKの部分をチェックして、H/W割り込み処理中か、S/W割り込み処理中かを取得する。
[関連ページ]
例外処理