Linux Kernel(2.6)の実装に関するメモ書き

例外処理


Rev.16を表示中。最新版はこちら

例外処理のメモ。対象CPUはi386。

例外処理

例外ベクタテーブル(IDT)

idt_table[256]にIDT(Interrupt Descriptor Table)が登録されている。IDTはtrap_init()で初期化する。IDTRレジスタはidt_tableを指すがこれは起動時にstartup_32_smpで初期化される(lidt idt_descr)。

Interrupt Descriptor Table
idt_table[256] <---- IDTR Register
+-------------+
| Descriptor |
+-------------+
| |
+-------------+
| |
:
+-------------+
| |
+-------------+

各Descriptorにはフォーマットによって以下の種類に分けられる。

Descriptor種別
用途
Interrupt gate割り込みハンドラへのポインタを保持しており、当該ベクタの例外/割り込みが発生すると指定したハンドラへ飛ぶ。
Trap gate
例外ハンドラへのポインタを保持しており、当該ベクタの例外/割り込みが発生すると指定したハンドラへ飛ぶ。(*1)
Task gate
TSSセグメントセレクタでGDT内のTSSディスクリプタを選択し、該当TSSのタスクにタスクスイッチする。Task gateはタスクスイッチのために使われるが、IDTの中で使用して例外/割り込み処理にタスクスイッチすることもできる。(*2)
(*1) Interrupt gateとTrap gateは同じ様なものだが、Interrupt gate経由でハンドラに飛んだ場合は、EFLAGSレジスタのIFフラグをクリアして割り込みが抑止される。

(*2) あえて複雑なTask gateを使うメリットとしては、TSSが切り替わるため割り込みハンドラ用に新しいスタックを使用できてシステムの安全性を高めることができることなどがある。ただし、Interrupt gateやTrap gateの方が軽いので通常はそちらを使う。Linuxではダブルフォルト例外の時にしか使っていない。


各ディスクリプタを設定するルーチンには以下のものがある。
set_intr_gate()
set_system_intr_gate()
set_trap_gate()
set_system_gate()
set_task_gate()


表1 idt_tableの設定内容(一部)
ベクタ番号種類
Descr.形式
エントリルーチン
0
0除算Trap
divide_error
8
ダブルフォルト
Task
doublefault_fn (*1)
14
ページフォルトInterrupt
page_fault
80h
システムコール(int80h)
Trap
system_call

(*1) このエントリはTask Gateなのでハンドラには直接ジャンプしない。このTask GateにはTSSセグメントセレクタにGDT_ENTRY_DOUBLEFAULT_TSS(31)が設定されており、GDT_ENTRY_DOUBLEFAULT_TSSで定義されるTSSに飛ぶ。GDT_ENTRY_DOUBLEFAULT_TSSのTSS実体はdoublefault_tssで.eipにdoublefault_fnが設定されているため、最終的に本ハンドラに飛ぶ。


割り込みのエントリポイント

(初期化)
IRQ0,1,...を例外ベクタ32以降にマッピングするように
割り込みコントローラを設定する。
(i8259.c::init_8259A())

例外ベクタ32以降のエントリを割り込みハンドラ用に設定する。

i8259.c::init_IRQ()
idt_table[]の例外ベクタ32〜のエントリを
set_intr_gate()で設定

例外ベクタ32〜のエントリがirq_entries_start[IRQ#]
を指すように設定される。


entrt.Sの関連テーブル(コンパイル時に構成される)

entry.S:inerrupt        entry.S::irq_entries_start
+------------+ +-----------------------+
| |--------->| pushl $vector-256 | vector = 0
+------------+ | jmp common_interrupt |
| |----+ | nop |
+------------+ | +-----------------------+
: +---->| | vector = 1
: | |
| |
+-----------------------+
:
:



(ハンドラ呼び出しの流れ)
1. 割り込み発生
(割り込みの例外ベクタは32以降に設定されている)
2. IDTの該当エントリに登録されているアドレスにジャンプ
(irq_entries_start[IRQ#]にジャンプ)
3. 一旦common_interrupt:に飛んでdo_IRQ()へ
4. irq_desc[]に登録されているハンドラへ
(request_irq()で登録される)


システムコールのエントリポイント

entry.S::system_call

例外ハンドラと同様にtrap_init()で設定

sys_call_tableに各種システムコールのエントリポイントが
並べられている。

[システムコールからの戻り]
1. システムコールからリターン
2. syscall_exit:
thread_info.flagsにbitが立っていれば
syscall_exit_workにジャンプ
3. restore_all:
レジスタを元に戻す。
iret

4. syscall_exit_work:
TIF_SYSCALL_TRACE|TIF_SYSCALL_AUDITが立っていなければwork_pendingへジャンプ

5. work_pending
TIF_NEED_RESCHEDが立っていればschedule()をコール
restore_allへジャンプ




[割り込みからの戻り]
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に戻す

5. resume_userspace:
thread_info.flagsにTIF_SYSCALL_TRACE|TIF_SYSCALL_AUDIT以外の
bitが立っていればwork_pendingへジャンプ
そうでなければrestore_allへジャンプ

6. restore_all:
レジスタを元に戻す。
iret

7. work_pending
TIF_NEED_RESCHEDが立っていればschedule()をコール
restore_allへジャンプ




最終更新 2006/06/24 16:07:16 - kztomita
(2006/03/27 12:49:42 作成)


リンク
最近更新したページ
検索