| 概念 | 定義 | 比喻 |
| 程序 | 存儲在磁盤上的可執行文件(靜態代碼+數據) | 停在車庫的汽車 |
| 進程 | 程序加載入內存後的運行實例(動態、擁有資源) | 行駛在路上的汽車 |
關鍵點:同一程序可啟動多個進程,各自擁有獨立地址空間、文件描述符表、信號處理等。
進程標識 PID
- 類型:
pid_t(非負整數)。
- 獲取:
pid_t getpid(void) 返回調用進程 PID;pid_t getppid(void) 返回父進程 PID。
- 唯一性:系統保證同一時刻無兩個進程 PID 相同;進程結束後 PID 可復用。
C 代碼:打印當前 PID
#include
#include
#include
int main() {
pid_t pid = getpid();
printf("this process id is %d\n", pid);
return 0;
}
編譯運行:
$ gcc -o pid pid.c
$ ./pid
this process id is 24047
$ ./pid
this process id is 24048 # 每次運行 PID 不同
實戰:PID 文件防止重複啟動
守護進程常用手段:啟動時檢查 .pid 文件是否存在,存在則退出;不存在則創建並寫入當前 PID,退出時刪除。
完整代碼
#include
#include
#include
#include
#include
#include
int main() {
pid_t pid = getpid();
char *pidfile = "./freesir.pid";
char pids[16] = {0};
int fd;
// 1. 檢查 PID 文件是否存在
if (access(pidfile, F_OK) != -1) {
printf("this program is already run in system.\n");
return -1;
}
// 2. 創建並寫入 PID
fd = open(pidfile, O_TRUNC | O_CREAT | O_RDWR, 0644);
if (fd < 0) {
perror("open pid file error");
return -2;
}
sprintf(pids, "%d", pid);
if (write(fd, pids, strlen(pids)) != strlen(pids)) {
perror("write pid file error");
close(fd);
return -3;
}
// 3. 模擬業務邏輯
sleep(10);
// 4. 清理
unlink(pidfile);
close(fd);
printf("this process id is %d\n", pid);
return 0;
}
注意:生產環境需考慮原子性(open(O_EXCL))、進程崩潰殘留 PID 文件(啟動時驗證 PID 是否存活)、權限等問題,推薦使用 flock/fcntl 文件鎖或成熟庫(如 libbsd 的 pidfile_open)。
進程控制:fork / waitpid
fork() 核心語義
- 調用一次,返回兩次:父進程返回子進程 PID,子進程返回 0。
- 子進程複製父進程地址空間(Copy-on-Write)、文件描述符表、信號處理等。
- 失敗返回 -1,設置
errno。
waitpid() 等待子進程結束
pid_t waitpid(pid_t pid, int *status, int options);
// pid > 0: 等待指定 PID;pid = -1: 等待任意子進程
// status: 輸出子進程退出狀態,需用宏解析
// options: WNOHANG 非阻塞,WUNTRACED 報告停止狀態
三大狀態解析宏
| 宏 | 用途 |
WIFEXITED(status) | 子進程正常退出(調用 exit/_exit) |
WEXITSTATUS(status) | 取得正常退出碼(需先 WIFEXITED 為真) |
WIFSIGNALED(status) | 子進程被信號終止 |
WTERMSIG(status) | 取得終止信號編號 |
WIFSTOPPED(status) | 子進程被停止(如 SIGSTOP) |
WSTOPSIG(status) | 取得停止信號編號 |
父子進程協作示例
#include
#include
#include
#include
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork error");
return 1;
} else if (pid == 0) { // 子進程
printf("I'm child, pid=%d\n", getpid());
sleep(5);
exit(127); // 自定義退出碼
} else { // 父進程
printf("I'm parent, pid=%d, child_pid=%d\n", getpid(), pid);
int status;
pid_t w = waitpid(pid, &status, 0); // 阻塞等待
if (w < 0) perror("waitpid error");
else {
if (WIFEXITED(status))
printf("Child exited with code %d\n", WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("Child killed by signal %d\n", WTERMSIG(status));
}
}
return 0;
}
本章小結
- 程序 ≠ 進程:靜態文件 vs 動態實例,一對多關係。
- PID 是進程的身份證,
getpid() 獲取,可用於防重啟鎖。
- fork() 是 Unix 進程創建的核心,「調用一次,返回兩次」是理解父子邏輯的鑰匙。
- waitpid() + 宏 是父進程感知子進程命運的標準方式,必須處理所有退出情況。
下一章將深入信號處理、管道通信、exec 族函數,敬請期待。
- 上一篇文章: Tomcat + Java Web 應用 CPU 100% 故障排查實戰:top → ps → jstack 定位死循環代碼
- 下一篇文章: Java 接口方法深度解析:隱式 public abstract、默認方法、靜態方法與鑽石繼承
Copyright ©
程式碼之家 All Rights Reserved