wait()/waitpid() 與回收子行程
當父行程啟動一個子行程,子行程終究會結束——但父行程往往需要知道兩件事:它結束了,以及它怎麼結束的(成功了?當掉了?帶著錯誤碼結束了?)。wait 系列的呼叫,正是父行程得知這些的途徑。wait() 與 waitpid() 把父行程暫停,直到某個子行程終止,然後把那個子行程的結束狀態交回來。想像一位家長在校門口等:孩子沒出來他就不走,而孩子一到,他就知道這天過得如何。
這裡還有第二個更微妙的工作,叫做收割(reaping)。當一個行程終止時,核心還不能把它完全抹去——它得保留一筆極小的紀錄(PID 與結束狀態),好讓父行程能讀到子行程如何結束。一個已終止但尚未被收走的子行程,就是殭屍,它會一直賴著,直到父行程呼叫 wait。wait 呼叫身兼兩職:它把結束狀態回傳給父行程,並告訴核心「我收走了,你現在可以釋放子行程最後的殘跡了。」那次收走就是收割。waitpid() 是更靈活的版本——它讓你能用 PID 等待特定的子行程,或在不阻塞的情況下檢查(WNOHANG 選項),好讓父行程能繼續工作並輪詢。要解讀狀態,你用巨集:WIFEXITED(status) 在子行程正常結束時為真,接著 WEXITSTATUS(status) 給出它傳給 exit() 的碼;WIFSIGNALED(status) 在子行程被信號殺掉時為真,WTERMSIG(status) 告訴你是哪一個。
為何重要:收割不是可有可無的家務——一個從不等待子行程的父行程,會留下一串佔著 PID 表位置的殭屍,在長時間執行的伺服器上那是一種緩慢的資源洩漏。wait 也是 shell 得知指令結束、以及該不該印錯誤的方式。狀態巨集很重要,因為那個原始狀態整數並不是結束碼——你必須用 WIFEXITED/WEXITSTATUS 才能正確讀出它,這一步初學者經常跳過,然後就把數字讀錯了。
int status; pid_t done = waitpid(pid, &status, 0); if (WIFEXITED(status)) printf("exited with %d\n", WEXITSTATUS(status)); else if (WIFSIGNALED(status)) printf("killed by signal %d\n", WTERMSIG(status)); —— 父行程阻塞到那個子行程結束,然後用巨集解碼那個被打包的狀態整數。
waitpid 為子行程阻塞,再用 WIFEXITED/WEXITSTATUS 拆解它如何結束。
不要把原始狀態整數當成結束碼來讀——它同時打包了結束碼與信號資訊。務必先用 WIFEXITED 再用 WEXITSTATUS(以及 WIFSIGNALED/WTERMSIG)才能正確解讀它。