首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

APUE 筆記:進程控制核心三巨頭 fork、exec、wait 深度實戰

     編輯:關於C語言

Unix 進程控制核心:fork 創建、exec 替換、wait 回收

《APUE》第 8 章圍繞 fork()、exec()、wait() 三大系統調用展開。它們構成了 Unix 進程管理的基石:所有進程(除 init)皆由 fork() 誕生,執行新程序靠 exec(),父進程同步靠 wait()。

一、關鍵概念與 ps 觀察技巧

  • PID:進程唯一標識,pid_t 類型,非負整數,用完循環復用。
  • init (PID 1):所有進程祖先,內核啟動後創建,負責孤兒進程收養。

二、fork():一次調用,兩次返回

pid_t fork(void);
// 父進程返回子進程 PID > 0
// 子進程返回 0
// 出錯返回 -1,設置 errno

Copy-on-Write (COW) 機制:

  1. fork 瞬間:父子共享物理頁面,頁表標記只讀。
  2. 任意一方寫入:頁面缺失中斷 → 內核複製該頁 → 修改映射 → 繼續執行。
  3. 優勢:避免大量數據拷貝;若子進程緊接 exec() 則零拷貝。

父子差異五點:返回值、PID、PPID、資源使用量清零、未決信號/文件鎖不繼承。

緩衝區陷阱:fork 前必須 fflush(NULL) 刷新所有緩衝區,否則重定向輸出時父進程緩衝區數據會被子進程復制並重複輸出。

三、wait()/waitpid():僵屍進程收割者

pid_t wait(int *status);           // 阻塞等待任意子進程
pid_t waitpid(pid_t pid, int *status, int options); // 可指定進程、非阻塞

status 宏解析:

命令用途
ps axf樹狀顯示父子關係、PID、TTY、狀態
ps axj顯示 PPID、PID、PGID、SID、TTY
ps axm顯示線程(PID 下減號表示線程)

waitpid pid 參數四種模式:

  • < -1:進程組 ID = |pid| 的任意子進程
  • -1:任意子進程(等同 wait())
  • 0:同進程組的任意子進程
  • > 0:指定 PID 的子進程

WNOHANG:非阻塞模式,無子進程退出立即返回 0,輪詢檢查常用。

四、僵屍 vs 孤兒進程

宏含義
WIFEXITED(status)正常退出(非信號終止)
WEXITSTATUS(status)提取退出碼 (需正常退出)
WIFSIGNALED(status)被信號殺死
WTERMSIG(status)殺死進程的信號編號
WIFSTOPPED(status)被信號停止
WSTOPSIG(status)停止進程的信號編號

五、exec() 族:換殼不換魂

int execl(const char *path, const char *arg0, ..., (char *)NULL);
int execv(const char *path, char *const argv[]);
int execlp(const char *file, const char *arg0, ..., (char *)NULL); // 搜索 PATH
// l=列表參數,v=向量參數,p=搜索 PATH,e=指定環境變量
  • 本質:將調用進程的整個地址空間(代碼、數據、堆、棧)替換為新程序,但保留 PID、父子關係、打開的文件描述符(除非設定 FD_CLOEXEC)。
  • 不返回:成功則不返回,失敗返回 -1(如文件不存在、權限不足、非可執行文件)。
  • 緩衝區:exec 前必須 fflush(NULL),否則舊緩衝區數據隨地址空間丟失。

六、經典組合:fork + exec + wait = 簡易 Shell

pid_t pid = fork();
if (pid == 0) {           // 子進程
    execl("/bin/date", "date", "+%s", NULL);
    perror("execl");      // 只有失敗才執行到這裡
    _exit(1);             // 子進程失敗用 _exit 避免衝刷父緩衝區
}
wait(NULL);               // 父進程等待子進程
puts("End!");

七、system() 內部實現原理

int system(const char *cmd) {
    pid_t pid = fork();
    if (pid == 0) {
        execl("/bin/sh", "sh", "-c", cmd, NULL);
        _exit(127);
    }
    waitpid(pid, NULL, 0);
    return 0; // 簡化:實際需處理信號、返回值解析
}

八、進程憑證:Real/Effective/Saved UID/GID

  • Real:真實身份(登錄用戶)。
  • Effective:權限檢查時使用的身份。
  • Saved:保存執行文件所有者 ID,支持權限切換。

Set-UID 程序:如 passwd (-rwsr-xr-x),執行瞬間 Effective UID = 文件所有者,獲得 root 權限修改 /etc/shadow。

九、解釋器腳本與 #! 機制

  • #!/bin/bash:內核識別 #!,執行 /bin/bash script.sh。
  • 腳本權限需 +x;解釋器必須是二進制可執行文件。
  • 內核 execve 直接處理,不經過 Shell。

徹底掌握 fork/exec/wait 三部曲、僵屍孤兒機制、COW 原理、exec 族選擇、Set-UID 權限模型,是 Unix/Linux 系統編程的入門鑰匙。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
類型成因後果解決
僵屍子進程先退出,父進程未 wait佔用 PID、內核 task_struct,大量則耗盡 PID父進程調用 wait/waitpid,或父進程先死由 init 收割
孤兒父進程先退出,子進程還在運行被 init (PID 1) 收養,正常退出由 init wait通常無害,守護進程常利用此特性脫離終端