例外処理
Rev.19を表示中。最新版はこちら。
例外処理のメモ。対象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 |
+-------------+
| |
+-------------+
| |
:
+-------------+
| |
+-------------+
表1 IDTに格納されるGateの種類
(*1) Interrupt gateとTrap gateは同じ様なものだが、Interrupt
gate経由でハンドラに飛んだ場合は、EFLAGSレジスタのIFフラグをクリアして割り込みが抑止される点がことなる。
(*2) あえて複雑なTask gateを使うメリットとしては、TSSが切り替わるため割り込みハンドラ用に新しいスタックを使用できてシステムの安全性を高めることができることなどがある。ただし、Interrupt gateやTrap gateの方が軽いので通常はそちらを使う。Linuxではダブルフォルト例外の時にしか使っていない。
Descriptor種別 | 用途 |
---|---|
Interrupt gate | ハンドラへのポインタを保持しており、当該ベクタの例外/割り込みが発生すると指定したハンドラへ飛ぶ。 |
Trap gate | ハンドラへのポインタを保持しており、当該ベクタの例外/割り込みが発生すると指定したハンドラへ飛ぶ。(*1) |
Task gate | TSSセグメントセレクタでGDT内のTSSディスクリプタを選択し、該当TSSのタスクにタスクスイッチする。Task
gateはタスクスイッチのために使われるが、IDTの中で使用して例外/割り込み処理にタスクスイッチすることもできる。(*2) |
(*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()
set_trap_gate()
set_system_gate()
set_task_gate()
表2 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が設定されているため、最終的に本ハンドラに飛ぶ。
システムコールのエントリポイント
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へジャンプ