例外 (Exceptions)
Overview Table
| 主題 | 核心內容 | 書頁 |
|---|---|---|
| ECF 定義 | control flow 的突然改變,反應「非程式變數所能表達」的系統狀態變化 | p.758 |
| Exception 定義 | 對 processor state 變化(event)所做的突然控制轉移,由硬體+OS 共同實作 | p.759 |
| Exception handling | 透過 exception table(jump table)間接呼叫 exception handler | p.760-761 |
| 四大類例外 | Interrupt / Trap / Fault / Abort(非同步 1 類 + 同步 3 類) | p.762-764 |
| Linux/x86-64 例外 | 最多 256 種;0-31 由 Intel 定義,32-255 由 OS 定義 | p.765 |
| System call | 以 syscall 指令實作的 trap;參數走暫存器不走 stack |
p.765-767 |
例外控制流 (Exceptional Control Flow, ECF) 總覽 (p.758-759)
處理器從開機到關機,PC 依序取值
ECF 存在於系統的每一層:
- 硬體層:硬體偵測到 event → 跳到 exception handler。
- OS 層:kernel 以 context switch 在 process 間轉移控制權(見 08-Exceptional-Control-Flow/02-Processes-and-Context-Switches)。
- 應用層:process 對 process 送 signal、程式內部以 nonlocal jump(
setjmp/longjmp)跳脫 call/return stack discipline。
理解 ECF 的五個理由 (p.758-759):理解 OS 如何實作 I/O、process、virtual memory;理解應用程式如何透過 trap / system call 向 OS 要求服務;能寫 shell、Web server 等程式;是理解 concurrency 的第一步;理解 C++/Java 軟體例外(try/catch/throw)的底層原理。
例外的解剖 (Anatomy of an Exception) (p.759-760)
Exception = 對 processor state 中某個變化(event)的回應而發生的控制流突然改變。state 編碼在處理器內部的位元與訊號中。event 可能與當前指令直接相關(page fault、算術溢位、除以零),也可能無關(system timer 到期、I/O 完成)。
Application program Exception handler
─────────────────── ─────────────────
│
Event │ I_curr ── Exception ──────►┐
occurs │ I_next ◄──────┐ │ Exception
here │ │ │ processing
│ Exception return ────┘
▼ (optional:回 I_curr / 回 I_next / abort)
處理器偵測到 event 後,透過稱為 exception table 的 jump table 做一次間接程序呼叫(即 exception),進入 OS 的 exception handler。handler 處理完後三選一:
- 把控制權還給 I_curr(event 發生時正在執行的指令)。
- 把控制權還給 I_next(若無例外本來要執行的下一條指令)。
- abort 被中斷的程式。
try/catch/throw 也叫 exception,那是應用層軟體例外;本章的 exception 指硬體+OS 層機制。通常由上下文即可分辨。8.1.1 Exception Handling (p.760-762)
例外處理是硬體與軟體的緊密分工。每種例外被指派唯一的非負整數 exception number:一部分由處理器設計者指定(divide by zero、page fault、memory access violation、breakpoint、arithmetic overflow),另一部分由 OS kernel 設計者指定(system call、外部 I/O 裝置 signal)。
- 開機時 (boot time):OS 配置並初始化 exception table,entry k 存放 exception k 的 handler 位址。
- 執行時 (run time):處理器偵測 event、判定 exception number k,透過 exception table entry k 做間接程序呼叫進入 handler。
Exception number k
(× 8)
│ Exception table
▼ ┌──────────────┐
Exception table ──►( + )────────► │ 0: handler 0 │──► Code for handler 0
base register │ 1: handler 1 │──► Code for handler 1
(special CPU reg) │ 2: handler 2 │──► Code for handler 2
│ ... │
│ n-1 │──► Code for handler n-1
└──────────────┘
Handler 位址 = exception table base register + k × 8(exception number 為表的索引;表的起始位址存在特殊 CPU 暫存器 exception table base register)。
例外 vs. 一般程序呼叫 (p.761-762)
| 面向 | 一般 procedure call | Exception |
|---|---|---|
| 返回位址 | 下一條指令 | 依例外類別,是 I_curr 或 I_next |
| 額外壓入的狀態 | 無 | 壓入額外 processor state(如 x86-64 的 EFLAGS,含 condition codes),供 handler 返回後重啟程式 |
| 使用的 stack | 呼叫者的 user stack | 若從 user program 轉移到 kernel,一律壓入 kernel stack |
| 執行模式 | user mode(受限) | handler 在 kernel mode 執行,可存取所有系統資源 |
硬體觸發例外之後,其餘工作由軟體(exception handler)完成。handler 處理完 event 後,可選擇性執行特殊的「return from interrupt」指令返回:把狀態 pop 回處理器的控制與資料暫存器、若中斷的是 user program 則恢復 user mode,再把控制權交還被中斷的程式。
8.1.2 Classes of Exceptions(四大類)(p.762-764)
| Class | 成因 | Async/Sync | 返回行為 |
|---|---|---|---|
| Interrupt | 來自 I/O 裝置的 signal | Async | 一律返回 I_next |
| Trap | 蓄意(intentional)的例外 | Sync | 一律返回 I_next |
| Fault | 可能可恢復的錯誤 | Sync | 可能返回 I_curr(重新執行),否則 abort |
| Abort | 不可恢復的致命錯誤 | Sync | 永不返回 |
Interrupts (p.762-763)
I/O 裝置(網卡、磁碟控制器、timer chip)藉由拉高處理器晶片上的 interrupt pin 並把識別裝置的 exception number 放上 system bus 來觸發中斷。其 handler 常稱 interrupt handler。
(1) Interrupt pin goes high (2) Control passes to handler
during I_curr after I_curr FINISHES
I_curr ─────────────────────────►┐
I_next ◄────────┐ │ (3) Interrupt
│ │ handler runs
(4) Handler returns ───────────┘
to I_next
- 處理器等當前指令執行完才察覺 pin 拉高、從 system bus 讀取 exception number、呼叫對應 handler。
- Handler 返回 I_next,效果是程式彷彿中斷從未發生。
Traps 與 System Calls (p.763-764)
Trap 是蓄意的例外——執行某指令的預期結果;handler 一律返回 I_next。最重要的用途:在 user program 與 kernel 之間提供類似程序呼叫的介面,即 system call。user program 需要 kernel 服務時(讀檔 read、建立 process fork、載入程式 execve、終止 process exit),執行處理器提供的特殊指令 syscall n 要求服務 n;trap 進入 handler,由 handler 解碼參數並呼叫適當的 kernel routine。
(1) Application makes a (2) Control passes to handler
system call
syscall ─────────────────────────►┐
I_next ◄────────┐ │ (3) Trap
│ │ handler runs
(4) Handler returns ────────────┘
to I_next (instruction following the syscall)
從程式設計師觀點,system call 與一般函式呼叫看起來相同,但實作大不同:
| 面向 | 一般函式呼叫 | System call |
|---|---|---|
| 執行模式 | user mode,可執行的指令種類受限 | kernel mode,可執行 privileged instructions |
| Stack | 與呼叫者共用同一 stack | 使用 kernel 定義的 stack |
Faults (p.764)
Fault 源自 handler 可能修正的錯誤條件。handler 若能修正錯誤 → 返回 faulting instruction 重新執行;若不能 → 返回 kernel 的 abort routine 終止造成 fault 的程式。
(1) I_curr causes a fault (2) Control passes to handler
I_curr ─────────────────────────►┐
▲ │ (3) Fault
│ │ handler runs ──► abort
└── (4) Handler re-executes ───┘ (若無法修正)
current instruction (I_curr)
經典例子:page fault——指令參照的 virtual address 對應的 page(典型 4 KB 的連續 virtual memory 區塊)不在記憶體,須自磁碟載入。page fault handler 載入該頁後返回 faulting instruction;重新執行時該頁已在記憶體,指令得以完成而不再 fault(詳見 09-Virtual-Memory/01-Address-Spaces-and-VM-Caching)。
Aborts (p.764)
Abort 源自不可恢復的致命錯誤,典型如 DRAM/SRAM 位元損毀造成的 parity error 等硬體錯誤。abort handler 永不把控制權還給應用程式,而是轉給 kernel 的 abort routine 終止該程式。
(1) Fatal hardware error (2) Control passes to handler
I_curr ─────────────────────────►┐
│ (3) Abort handler runs
│
(4) Handler returns ───────────┴──► abort routine
to abort routine (terminates program)
8.1.3 Exceptions in Linux/x86-64 Systems (p.765-767)
x86-64 最多 256 種例外類型:
- 編號 0-31:由 Intel 架構師定義,所有 x86-64 系統相同。
- 編號 32-255:由 OS 定義的 interrupts 與 traps。
| Exception number | 描述 | 類別 |
|---|---|---|
| 0 | Divide error | Fault |
| 13 | General protection fault | Fault |
| 14 | Page fault | Fault |
| 18 | Machine check | Abort |
| 32-255 | OS-defined exceptions | Interrupt 或 trap |
Linux/x86-64 Faults 與 Aborts (p.765)
- Divide error(#0):除以零、或除法結果對目的運算元太大。Unix 不嘗試恢復,直接 abort;shell 通常回報 "Floating exception"。
- General protection fault(#13):成因很多,通常是參照未定義的 virtual memory 區域或寫入 read-only text segment。Linux 不嘗試恢復;shell 回報 "Segmentation fault"。
- Page fault(#14):會重啟 faulting instruction 的例外;handler 把磁碟上的 virtual memory page 映射進 physical memory 後重啟該指令。
- Machine check(#18):執行 faulting instruction 期間偵測到的致命硬體錯誤;handler 永不返回應用程式。
Linux/x86-64 System Calls (p.765-767)
Linux 提供數百個 system call,每個有唯一整數編號,對應 kernel 中另一張 jump table 的 offset(此表不是 exception table)。常見範例 (Figure 8.10):
| 編號 | 名稱 | 描述 | 編號 | 名稱 | 描述 |
|---|---|---|---|---|---|
| 0 | read |
讀檔 | 37 | alarm |
排程 alarm signal |
| 1 | write |
寫檔 | 39 | getpid |
取得 process ID |
| 2 | open |
開檔 | 57 | fork |
建立 process |
| 3 | close |
關檔 | 59 | execve |
執行程式 |
| 4 | stat |
取得檔案資訊 | 60 | _exit |
終止 process |
| 9 | mmap |
映射 memory page 到檔案 | 61 | wait4 |
等待 process 終止 |
| 12 | brk |
重設 heap 頂端 | 62 | kill |
送 signal 給 process |
| 32 | dup2 |
複製 file descriptor | 33 | pause |
暫停直到 signal 抵達 |
C 程式可用 syscall 函式直接呼叫任何 system call,但實務上很少需要:C 標準函式庫為多數 system call 提供 wrapper functions(打包參數 → 以適當指令 trap 進 kernel → 回傳狀態給呼叫者)。本書把 system call 與其 wrapper 統稱 system-level functions。
x86-64 syscall 指令的暫存器慣例 (p.766):
- 所有參數走 general-purpose registers,不走 stack。
%rax= system call 編號;最多六個參數依序放%rdi,%rsi,%rdx,%r10,%r8,%r9。- 返回時
%rcx與%r11被破壞 (destroyed);%rax存回傳值。 - 回傳值介於 -4,095 到 -1 之間表示錯誤,對應
-errno。
%rcx,但 syscall 慣例第 4 個參數改用 %r10(%rcx 會被 syscall 指令破壞)。一般呼叫慣例見 03-Machine-Level-Programs/04-Procedures-and-the-Stack。範例:用 write system-level function 取代 printf 的 hello (p.766):
int main()
{
write(1, "hello, world\n", 13); /* arg1: stdout; arg2: 位元組序列; arg3: 位元組數 */
_exit(0);
}
直接以 syscall 指令實作 (Figure 8.11, code/ecf/hello-asm64.sa):
.section .data
string:
.ascii "hello, world\n"
string_end:
.equ len, string_end - string
.section .text
.globl main
main:
# First, call write(1, "hello, world\n", 13)
movq $1, %rax # write is system call 1
movq $1, %rdi # Arg1: stdout has descriptor 1
movq $string, %rsi # Arg2: hello world string
movq $len, %rdx # Arg3: string length
syscall # Make the system call
# Next, call _exit(0)
movq $60, %rax # _exit is system call 60
movq $0, %rdi # Arg1: exit status is 0
syscall # Make the system call
Exam/Test Patterns
| 情境 / 關鍵字 | 答案 |
|---|---|
| 唯一「非同步」的例外類別? | Interrupt(來自外部 I/O 裝置,與特定指令無關);trap/fault/abort 皆同步 |
| 哪些類別「一律返回 I_next」? | Interrupt 與 Trap |
| 哪個類別「可能返回 I_curr(重新執行)」? | Fault(修正成功則重執行 faulting instruction,否則 abort) |
| 哪個類別「永不返回」? | Abort(致命硬體錯誤,如 parity error) |
| 「蓄意的例外」是哪類?最重要用途? | Trap;實作 system call(user program 進入 kernel 的受控入口) |
| exception table 是誰、何時建立? | OS 在系統開機 (boot) 時配置並初始化;entry k 存 handler k 的位址 |
| handler 位址如何形成? | exception number 當索引:base register + k × 8(exception table base register) |
| exception 壓 stack 壓在哪? | user → kernel 轉移時,返回位址與 processor state(如 EFLAGS)壓在 kernel stack |
| exception handler 跑在什麼模式? | kernel mode,可存取所有系統資源、執行 privileged instructions |
| system call 參數傳遞方式? | 全走暫存器:%rax=編號,參數依序 %rdi,%rsi,%rdx,%r10,%r8,%r9;%rax 回傳;%rcx,%r11 被破壞 |
| syscall 回傳值在 -4095 到 -1 之間? | 表示錯誤,值為 negative errno |
| 程式除以零,shell 印什麼? | "Floating exception"(divide error, 例外 #0, Fault,Unix 直接 abort) |
| 程式寫入 read-only text segment,shell 印什麼? | "Segmentation fault"(general protection fault, 例外 #13) |
| page fault 屬哪類?返回行為? | Fault(#14);handler 從磁碟載入 page 後重新執行 faulting instruction |
| x86-64 例外編號 0-31 vs. 32-255? | 0-31 由 Intel 定義(所有 x86-64 相同);32-255 由 OS 定義(interrupt/trap) |
| system call 編號查哪張表? | kernel 中的另一張 jump table(offset = syscall 編號),不是 exception table |
| 常考 syscall 編號 | read=0, write=1, fork=57, execve=59, _exit=60 |
Related Notes
- 08-Exceptional-Control-Flow/02-Processes-and-Context-Switches — exception 是 kernel 提供 process 抽象與 context switch 的基石;user/kernel mode 詳見 8.2.4
- 08-Exceptional-Control-Flow/04-Process-Control —
fork/execve/wait4等 system call 的程式設計介面 - 08-Exceptional-Control-Flow/05-Signals — 應用層 ECF:signal 建立在 OS-defined 例外與 kernel 遞送機制之上
- 09-Virtual-Memory/01-Address-Spaces-and-VM-Caching — page fault 的完整機制(VM paging)
- 03-Machine-Level-Programs/04-Procedures-and-the-Stack — 一般 procedure call 慣例,與 exception/syscall 慣例對照
- 10-System-IO/01-Unix-IO-and-Files —
read/write/open/close等 system-level functions 的使用