fork() 建立子進程是 Unix 程式設計的核心。本文系統整理父子進程關係、信號處理、僵屍/孤兒進程成因與正確回收模式,附帶程式碼驗證。
fork() 效能。子進程終止後,核心保留其 task_struct (PID、退出狀態等) 直到父進程呼叫 wait()/waitpid() 讀取狀態。此期間子進程處於 Z (Zombie) 狀態。
// 僅等待特定子進程,若有多個子進程會遺漏
waitpid(child_pid, &status, 0);
#include <signal.h> #include <sys/wait.h> #include <stdio.h>
void sigchld_handler(int sig) {
int status; pid_t pid;
// WNOHANG 非阻塞,循環回收所有已終止子進程
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
if (WIFEXITED(status))
printf("Child %d exited normally, status=%d\n", pid, WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("Child %d killed by signal %d\n", pid, WTERMSIG(status));
}
}
int main() {
struct sigaction sa = { .sa_handler = sigchld_handler, .sa_flags = SA_RESTART | SA_NOCLDSTOP };
sigaction(SIGCHLD, &sa, NULL);
for (int i = 0; i < 3; i++) {
if (fork() == 0) {
printf("Child %d (PID=%d) sleeping...\n", i, getpid());
sleep(2 + i);
_exit(i + 10); // 子進程用 _exit 避免刷新標準 I/O 緩衝區
}
}
// 父進程繼續工作...
sleep(10);
return 0;
}
關鍵點:
SA_NOCLDSTOP:不因子進程停止/繼續 (SIGSTOP/SIGCONT) 產生 SIGCHLD,僅在終止時產生。SA_RESTART:被信號中斷的系統調用 (如 read) 自動重啟。waitpid(-1, ..., WNOHANG):回收任一已終止子進程,非阻塞。_exit() 而非 exit() 終止子進程,避免雙重 atexit、標準 I/O 緩衝區衝突。父進程先於子進程終止 → 子進程成為孤兒 → 核心將其 PPID 設為 1 (init / systemd) → init 定期 wait() 回收。
驗證程式:
if (fork() == 0) {
printf("Child PID=%d, PPID=%d\n", getpid(), getppid());
sleep(5); // 父進程已退出
printf("Child PID=%d, new PPID=%d (adopted by init)\n", getpid(), getppid());
_exit(0);
} else {
printf("Parent PID=%d exiting...\n", getpid());
_exit(0); // 父進程立即退出
}
fork 複製信號處置)。init 收養,預設處理動作生效。void sigint_handler(int sig) { printf("PID=%d caught SIGINT\n", getpid()); }
int main() {
signal(SIGINT, sigint_handler);
for (int i = 0; i < 2; i++) {
if (fork() == 0) {
printf("Child %d (PID=%d) sleeping...\n", i, getpid());
sleep(3); // 此時父進程仍在,收到 SIGINT 會執行 handler
printf("Child %d after 1st sleep\n", i);
sleep(5); // 父進程已退出,子進程成孤兒,再收到 SIGINT 接預設動作(終止)
printf("Child %d after 2nd sleep\n", i);
_exit(0);
}
}
sleep(2); // 父進程較早退出
return 0;
}
因 fork 後記憶體獨立 (COW),父子無法直接共享變數。常用 IPC:
| 機制 | 適用場景 |
|---|---|
| 管道 | 親緣進程單向/雙向資料流 |
| Socketpair | 親緣進程雙向通訊 (全雙工) |
| 共享記憶體 (shm) | 高效大量資料共享,需同步 (信號量/mutex) |
| 訊號 | 簡單事件通知 (不可攜帶資料,除非 real-time signal) |
| Unix Domain Socket | 本機任意進程通訊,支援傳遞檔案描述符 |
fork() 複製資源,waitpid() 回收狀態,SIGCHLD 非阻塞回收是標準模式。init 收養自動回收,但失去原父進程協調能力。fork 後共享,孤兒後行為改變,設計時需考慮生命週期。