Process 的家族關係#
Linux 上沒有「憑空建立 process」的 API。每一個 process 都來自於另一個 process,靠 fork(2) 複製,再用 exec(3) 替換成想跑的程式。整個系統開機後從 PID 1(init process)開始,所有後代都是它的子孫。
- 父子關係是 strict tree:每個 process 只有一個 parent,但可以有多個 children
- 親屬關係由 kernel 在 task_struct 中以 linked list 維護
- 觀察整棵樹最快的工具是
pstree
fork(2):複製出一個自己#
fork(2) 是 Linux 建立新 process 的核心系統呼叫。呼叫成功後會回傳兩次:一次在 parent、一次在 child。
- Parent 拿到 child 的 PID(> 0)
- Child 拿到 0
- 失敗回傳 -1
#include <unistd.h>
#include <stdio.h>
int main(void) {
pid_t pid = fork();
if (pid < 0) {
perror("fork");
return 1;
} else if (pid == 0) {
printf("child pid=%d ppid=%d\n", getpid(), getppid());
} else {
printf("parent pid=%d child=%d\n", getpid(), pid);
}
return 0;
}現代 Linux fork(2) 採用 copy-on-write,page table 共用直到任一方寫入才真正複製,所以 fork 本身很便宜。
exec(3):替換成另一個程式#
exec(3) 系列函式(execl、execv、execle、execve、…)會把當前 process 的程式碼、資料段整個替換成新的可執行檔,但 PID 不變。
- 成功時不會回傳,因為原本的程式已經被覆蓋
- 失敗才會 return -1,errno 標示原因
- File descriptor 預設保留(除非設了 FD_CLOEXEC)
典型 fork + exec 模式:
pid_t pid = fork();
if (pid == 0) {
execlp("ls", "ls", "-l", (char*)NULL);
perror("exec"); // 只有失敗才會跑到這
_exit(127);
}wait(2) 與 waitpid(2):回收 child#
當 child 結束時,kernel 會保留它的 exit status,直到 parent 透過 wait(2) 或 waitpid(2) 取走。這個動作稱為 reap。
- wait(2):阻塞直到任一 child 結束
- waitpid(2):可指定 PID,可加 WNOHANG 改成 non-blocking
- 若 parent 不 reap,child 會卡在 zombie 狀態
int status;
pid_t child = fork();
if (child == 0) {
execlp("sleep", "sleep", "1", (char*)NULL);
_exit(127);
}
waitpid(child, &status, 0);
if (WIFEXITED(status)) {
printf("exit code = %d\n", WEXITSTATUS(status));
}用 ps 觀察父子關係#
ps -ef # 完整列表,PPID 欄位是父行程 PID
ps -o pid,ppid,pgid,sid,cmd # 自訂欄位
ps --forest # 用縮排畫出樹狀關係想找出某個 process 的所有後代,可以遞迴比對 PPID,或直接用 pstree。
用 pstree 觀察 process tree#
pstree 會把整個系統的 process 以樹狀結構畫出,PID 1 在根部。
pstree # 從 PID 1 開始
pstree -p # 同時顯示 PID
pstree -p 1234 # 從特定 PID 出發
pstree -s 1234 # 顯示某個 PID 到 root 的祖先鏈在 container 內跑 pstree -p 1 會看到該 container 的整棵 process 樹,而且根部就是該應用,這正是 PID namespace 隔離的效果。
Shell 是怎麼執行命令的#
Shell(bash、dash、ash)跑外部命令的標準流程:
- fork(2) 出一個 child
- Child 中 exec(3) 成目標程式
- Parent shell 用 waitpid(2) 等 child 結束,拿到 exit code 寫進
$?
如果是內建命令(cd、export),shell 不會 fork,直接在自己 process 內執行。
延伸閱讀#
- man 2 fork
- man 3 exec
- man 2 wait
- man 1 pstree