進程控制是 Linux 系統程式設計的核心技能。本文深度解析 waitpid、fork、execlp 三大核心系統呼叫,提供完整參數說明、返回值分析與可編譯執行的實戰程式碼。
#include <sys/types.h> #include <sys/wait.h> pid_t waitpid(pid_t pid, int *status, int options);
| 參數 | 說明 |
|---|---|
pid | >0:等待指定 PID;=0:等待同進程組任一子進程;=-1:等待任一子進程(等同 wait());<-1:等待指定進程組 ID(絕對值) |
status | 輸出參數,儲存子進程退出狀態,可為 NULL |
options | WNOHANG:無子進程結束立即返回不阻塞;WUNTRACED:子進程暫停也返回 |
WIFEXITED(status) // 正常結束 WEXITSTATUS(status) // 取得 exit code(需先判斷 WIFEXITED) WIFSIGNALED(status) // 因信號終止 WTERMSIG(status) // 取得終止信號編號 WIFSTOPPED(status) // 處於暫停狀態 WSTOPSIG(status) // 取得導致暫停的信號編號
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
int main(void) {
pid_t childpid;
int status;
childpid = fork();
if (childpid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
else if (childpid == 0) { // 子進程
puts("In child process");
sleep(3);
printf("\tchild pid = %d\n", getpid());
printf("\tchild ppid = %d\n", getppid());
exit(EXIT_SUCCESS);
}
else { // 父進程
waitpid(childpid, &status, 0); // 阻塞等待
puts("in parent");
printf("\tparent pid = %d\n", getpid());
printf("\tparent ppid = %d\n", getppid());
printf("\tchild process exited with status %d\n", status);
}
return 0;
}
關鍵觀察:若註解掉 waitpid,父進程會先於子進程結束,子進程成為「孤兒進程」被 init(PID 1)收養,ppid 變為 1。
#include <unistd.h> pid_t fork(void);
pid_t pid = fork();
if (pid < 0) { /* 錯誤處理 */ }
else if (pid == 0) { /* 子進程邏輯 */ }
else { /* 父進程邏輯,pid 為子進程 PID */ }
#include <unistd.h> int execlp(const char *file, const char *arg, ... /* (char *)NULL */);
PATH 環境變數目錄搜尋 filearg0(通常為程式名)、arg1...,必須以 (char *)NULL 結尾ls -al /etc/passwdexeclp("ls", "ls", "-al", "/etc/passwd", (char *)0);
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_ARGS 64
int main(void) {
char line[1024];
char *argv[MAX_ARGS];
while (1) {
printf("mysh> ");
if (!fgets(line, sizeof(line), stdin)) break;
line[strcspn(line, "\n")] = 0; // 移除換行
if (strcmp(line, "exit") == 0) break;
if (strlen(line) == 0) continue;
// 簡易參數解析
int argc = 0;
char *token = strtok(line, " ");
while (token && argc < MAX_ARGS - 1) {
argv[argc++] = token;
token = strtok(NULL, " ");
}
argv[argc] = NULL;
pid_t pid = fork();
if (pid == 0) {
execvp(argv[0], argv); // 使用 execvp 自動搜尋 PATH
perror("execvp");
exit(EXIT_FAILURE);
}
else if (pid > 0) {
int status;
waitpid(pid, &status, 0);
printf("[退出碼: %d]\n", WEXITSTATUS(status));
}
else {
perror("fork");
}
}
return 0;
}
| 陷阱 | 解決方案 |
|---|---|
| 忘記檢查 fork 返回值 | 必須處理 -1 錯誤情況 |
| 父進程未 wait 導致僵屍進程 | 使用 waitpid 或設置 SIGCHLD 處理器 |
| execlp 參數未以 NULL 結尾 | 務必加 (char *)NULL |
| exec 失誤後未退出子進程 | exec 失敗時必須 exit() 或 _exit() |
| 混淆 fork/vfork | vfork 共享位址空間,極度危險,現代建議避免使用 |
掌握 fork → exec → waitpid 經典三部曲,是編寫健壯 Linux 系統程式、Shell、服務管理器的基石。