例外控制流練習題 (Practice - Exceptional Control Flow)


Question 1 - 四大類例外 [recall]

請列出 CSAPP 定義的四大類例外 (exception),並針對每一類說明:(1) 同步或非同步;(2) handler 處理完後的返回行為。


Question 2 - Exception Handler 位址計算 [application]

某 x86-64 系統的 exception table base register 值為 0x100000。當處理器偵測到 page fault(exception number 14)時,它會到哪個位址取得 handler 的位址?這張 exception table 是由誰、在什麼時候建立的?


Question 3 - Exception vs. Procedure Call [recall]

Exception 造成的控制轉移與一般 procedure call 表面相似,實則有多處不同。請列出至少三個差異。


Question 4 - System Call 暫存器慣例 [recall]

x86-64 Linux 以 syscall 指令實作 system call。請說明:(1) system call 編號與參數放在哪些暫存器;(2) 回傳值放哪;(3) 哪兩個暫存器會被破壞;(4) 回傳值介於 -4095 到 -1 之間代表什麼。


Question 5 - Process 的定義與兩大抽象 [recall]

什麼是 process 的經典定義?它提供給應用程式哪兩個關鍵抽象,各自造成什麼「錯覺」?


Question 6 - 並行流判斷 [application]

單核系統上有三個行程,執行時間區間為:A (1, 4)、B (3, 6)、C (5, 8)。
請判斷 A 與 B、A 與 C、B 與 C 是否互為 concurrent,並說明它們是否可能是 parallel。


Question 7 - User Mode 與 Kernel Mode [recall]

處理器如何區分 user mode 與 kernel mode?user mode 的行程被禁止做哪些事?從 user mode 進入 kernel mode 的唯一途徑是什麼?


Question 8 - 三種錯誤處理風格 [recall]

Unix 系統層級函式有三種錯誤回報風格:Unix-style、Posix-style、GAI-style。請分別說明其成功/失敗的回傳慣例、錯誤碼位置,以及把錯誤碼轉成字串該用的函式。


Question 9 - fork 的輸出 [application]

考慮以下程式(假設所有呼叫都成功):

int main()
{
    int x = 1;
    if (fork() == 0)
        printf("child: x=%d\n", ++x);
    printf("parent view: x=%d\n", --x);
    exit(0);
}

這個程式總共印出幾行?子行程與父行程各印出的 x 值為何?為什麼父子的修改互不影響?


Question 10 - Process Graph 與可行輸出 [analysis]

某程式 fork 一個子行程:子行程依序印 "a" 再印 "b" 後 exit;父行程依序印 "c" 再印 "d" 後 exit(彼此無同步)。
請問輸出序列 acbdcdabbacd 是否可能?說明你的判斷方法。


Question 11 - Zombie 與 waitpid [recall]

什麼是 zombie 行程?若父行程先終止,孤兒行程由誰回收?另外請說明 waitpid(pid, &status, options) 中:pid = -1 與 pid > 0 的差別,以及 WNOHANG 選項的效果。


Question 12 - execve 的語意 [recall]

execvefork 常被合稱「fork + exec」。請說明:(1) execve 成功時的回傳行為;(2) 呼叫 execve 後 PID 是否改變;(3) 已開啟的 file descriptor 會如何;(4) 它與 fork 在「program vs. process」上的本質差異。


Question 13 - 訊號的 pending 與 blocked [recall]

請解釋 pending signal 與 blocked signal 的意義,kernel 用什麼資料結構追蹤它們?為什麼說「訊號不能用來計數事件」?kernel 又是在什麼時機、如何挑選要接收的訊號?


Question 14 - SIGCHLD Handler 的經典 Bug [analysis]

父行程建立三個幾乎同時終止的子行程,並安裝如下 SIGCHLD handler:

void handler1(int sig)
{
    pid_t pid = waitpid(-1, NULL, 0);  /* 只回收一次 */
    deletejob(pid);
}

為什麼這個 handler 可能留下 zombie?請描述導致漏收的事件序列,並給出正確寫法。


Question 15 - 訊號傳送與特殊訊號 [recall]

(1) 在鍵盤按 Ctrl+C 與 Ctrl+Z 各會送出什麼訊號?送給誰?(2) 哪兩個訊號既不能被 catch 也不能被 ignore?(3) kill(0, sig)kill(-15213, sig) 各把訊號送給誰?


Question 16 - pause 的 Race 與 sigsuspend [analysis]

shell 等待前景 job 時,handler 會把回收到的 PID 寫入 volatile sig_atomic_t pid。以下等待寫法有什麼問題?

while (!pid)
    pause();

為什麼 while (!pid) sleep(1); 也不理想?正解是什麼、它如何消除問題?


Question 17 - setjmp 與 longjmp 的回傳行為 [recall]

setjmplongjmp 的呼叫/回傳行為極不對稱。請說明:(1) setjmp 呼叫幾次、回傳幾次,各回傳什麼值;(2) longjmp 的回傳行為;(3) 在 signal handler 內做 nonlocal jump 該改用哪組函式、為什麼。


Question 18 - longjmp 錯誤處理輸出 [application]

執行以下程式,若 foo 內偵測到 error1 成立而呼叫 longjmp(buf, 1),程式會印出什麼?若把 longjmp 的第二個引數誤寫成其他未列舉的值(如 5)呢?

jmp_buf buf;
int main() {
    switch (setjmp(buf)) {
    case 0:  foo(); break;
    case 1:  printf("Detected an error1 condition in foo\n"); break;
    case 2:  printf("Detected an error2 condition in foo\n"); break;
    default: printf("Unknown error condition in foo\n");
    }
    exit(0);
}