前言
在操作系統的世界里,進程就像一個個忙碌的 “工作單元”,從被創建到完成任務后終止,始終遵循著一套嚴謹的生命周期規則。理解進程的生命周期管理,是揭開操作系統多任務調度神秘面紗的關鍵 —— 而這其中,進程的創建、終止與等待機制,構成了整個生命周期的核心骨架。
本文將沿著 “創建→終止→等待” 的脈絡,系統解析進程管理的底層邏輯:從
fork
函數如何 “復制” 出一個新進程,到寫時拷貝技術如何優化內存效率;從進程正常退出與異常終止的不同場景,到退出碼背后的狀態傳遞邏輯;更會深入探討為何父進程必須等待子進程,以及wait
與waitpid
函數在阻塞 / 非阻塞模式下的實現細節。無論你是想搞懂 “父子進程為何能共享代碼卻互不干擾”,還是想理解 “如何安全回收子進程資源”,這些知識點都將為你構建起進程管理的完整知識體系。
目錄
1. 進程創建?
1.1 fork函數初始
1.2 fork函數返回值
1.3 寫時拷貝
1.4 fork常規用法
1.5 fork調用失敗原因
2. 進程終止
2.1 進程退出場景
2.2 進程常見退出方法
2.3 退出碼
3. 進程等待
3.1 進程等待必要性
3.2 進程等待函數
wait
waitpid
3.3 獲取子進程status
3.4 阻塞與非阻塞等待
1. 進程創建?
1.1 fork函數初始
在linux中fork函數是非常重要的函數,它從已存在進程中創建一個新進程。新進程為子進程,而原進程為父進程。
#include <unistd.h>
pid_t fork(void);
返回值:?進程中返回0,?進程返回?進程id,出錯返回-1
int main()
{
pid_t pid;
printf("Before: pid is %d\n", getpid());
if ( (pid=fork()) == -1 )perror("fork()"),exit(1);
printf("After:pid is %d, fork return %d\n", getpid(), pid);
sleep(1);
return 0;
}
?
?
1.2 fork函數返回值
父進程返回子進程的pid,子進程返回0
1.3 寫時拷貝

寫時拷貝減少創建時間? 減少內存浪費?
1.4 fork常規用法
1.5 fork調用失敗原因
2. 進程終止
2.1 進程退出場景
代碼運行完畢,結果正確
代碼運行完畢,結果不正確
代碼異常終止
在以前的語言代碼中,都有main函數,main函數的返回值,通常表明的程序的執行情況.
代碼運行完畢,結果正確,return 0;
代碼運行完畢,結果不正確,return !0;不同的非零值表示不同的出錯原因。
2.2 進程常見退出方法
#include <unistd.h>
void exit(int status);
任何地方調用exit,表示進程結束。并返回給父進程,子進程的退出碼。?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void func(){printf("I am func.\n");exit(1);
}
int main(){func();printf("main\n");return 0;
}
int main(){printf("main");sleep(2);exit(1);
}

?exit變成_exit后
所以也可以得出我們之前談論的緩沖區一定不是操作系統內部的緩沖區。?
?前者是庫函數,后者是系統調用,exit最后會調用_exit,
2.3 退出碼
?echo $?? 查看最近一個程序(進程)退出時的退出碼,進程的退出碼是寫到了task_struct內部的。
#include <stdio.h>
#include <stdlib.h>int main()
{// 注意:文件打開模式需要用雙引號括起來FILE *fp = fopen("Hello.txt", "r");// 檢查文件是否成功打開if (fp == NULL) {return 1;}// C語言中關閉文件使用fclose()函數,而不是C++的成員函數形式fclose(fp);return 0;
}
終端打印部分常見退出碼:
3. 進程等待
3.1 進程等待必要性
3.2 進程等待函數
wait
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status);
返回值:
成功返回被等待進程pid,失敗返回-1。
參數:
輸出型參數,獲取?進程退出狀態,不關?則可以設置成為NULL
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>int main()
{pid_t id = fork();if (id < 0) {perror("fork"); // 錯誤處理:打印fork失敗原因return 1;}else if (id == 0) { // 子進程邏輯int cnt = 3;while (cnt--) {// 增加換行符刷新緩沖區,避免輸出混亂printf("I am child, pid : %d\n", getpid());sleep(1); // 子進程每次打印后休眠1秒,方便觀察}// 子進程退出前顯式說明printf("Child process exit\n");exit(0); // 子進程正常退出}// 父進程邏輯pid_t ret = wait(NULL); // 回收子進程資源,不關心退出狀態if (ret > 0) { // 等待成功的判斷(ret為回收的子進程PID)printf("wait success, child pid=%d\n", ret);} else {perror("wait"); // 處理wait可能的錯誤return 1;}sleep(2);return 0;
}
?
如果等待子進程,子進程沒有退出,父進程會阻塞在wait處。?
當子進程結束若等待個幾秒觀察僵尸的情況。
觀察圖片明顯解決了僵尸的問題
waitpid
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
當正常返回的時候waitpid返回收集到的?進程的進程ID;
如果設置了選項WNOHANG,?調?中waitpid發現沒有已退出的?進程可收集,則返回0;
如果調?中出錯,則返回-1,這時errno會被設置成相應的值以指?錯誤所在;
參數:
pid:
Pid=-1,等待任?個?進程。與wait等效。
Pid>0.等待其進程ID與pid相等的?進程。
status: 輸出型參數
WIFEXITED(status): 若為正常終??進程返回的狀態,則為真。(查看進程是
否是正常退出)
WEXITSTATUS(status): 若WIFEXITED?零,提取?進程退出碼。(查看進程的
退出碼)
options:默認為0,表?阻塞等待
WNOHANG: 若pid指定的?進程沒有結束,則waitpid()函數返回0,不予以等
待。若正常結束,則返回該?進程的ID。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>int main()
{pid_t id = fork();if (id < 0) {perror("fork"); // 錯誤處理:打印fork失敗原因return 1;}else if (id == 0) { // 子進程邏輯int cnt = 3;while (cnt--) {// 增加換行符刷新緩沖區,避免輸出混亂printf("I am child, pid : %d\n", getpid());sleep(1); // 子進程每次打印后休眠1秒,方便觀察}// 子進程退出前顯式說明printf("Child process exit\n");exit(0); // 子進程正常退出}// 父進程邏輯pid_t ret = wait(id,NULL,0); // 回收子進程資源,不關心退出狀態if (ret > 0) { // 等待成功的判斷(ret為回收的子進程PID)printf("wait success, child pid=%d\n", ret);} else {perror("wait"); // 處理wait可能的錯誤return 1;}sleep(2);return 0;
}
?
3.3 獲取子進程status
?
如果代碼沒有異常,低7個比特位為0,一旦低7個比特位!=0,異常退出的,退出碼無意義。?
退出異常,低7位保存異常時對應的信號編號
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>int main() {pid_t id = fork();if (id < 0) { // 增加fork失敗的錯誤處理perror("fork failed");return 1;} else if (id == 0) { // 子進程邏輯int cnt = 3;while (cnt--) {printf("我是子進程,pid:%d, ppid:%d\n", getpid(), getppid());sleep(1);}exit(10); // 子進程退出,返回狀態碼10}// 父進程邏輯int status = 0;// 等待指定子進程(id),阻塞式等待(WNOHANG=0)pid_t rid = waitpid(id, &status, 0);if (rid > 0) {// 正確解析退出狀態:先判斷是否正常退出if (WIFEXITED(status)) { // 宏判斷是否正常退出printf("wait success, 回收的子進程pid:%d, 退出碼:%d\n", rid, WEXITSTATUS(status)); // 宏獲取退出碼} else if (WIFSIGNALED(status)) { // 宏判斷是否被信號終止printf("子進程被信號終止,信號編號:%d\n", WTERMSIG(status));}} else {printf("wait failed: %d:%s\n", errno, strerror(errno));}return 0;
}
#include <errno.h>
#include <string.h>
#include <unistd.h>
int main(){pid_t id=fork();if(id==0){int cnt=3;while(cnt--){printf("我是子進程,pid:%d,ppid:%d\n",getpid(),getppid());sleep(1);}exit(10);}int status=0;pid_t rid=waitpid(id,&status,0); if(rid>0){printf("wait success,rid:%d,exit code:%d,exit signal :%d\n",rid,(status>>8)&0xFF,status&0x7F);}else{printf("wait failed:%d:%s\n",errno,strerror(errno));}return 0;}
代碼中有除0操作 ,退出異常:
?子進程的退出信息只能放在task_struct.
3.4 阻塞與非阻塞等待
張三在寢室樓下打電話找李四吃飯,李四在復習,說等一會,過了一會張三又打了一次電話。李四說還有一點,于是張三隔一段時間就給李四打電話直到李四下樓。
張三是父進程,李四是子進程,打電話就是一次調用,以上就是非阻塞輪詢。
輪詢就是通過循環完成的。
第二次張三同樣找李四吃飯,李四同樣在復習,這次打電話張三叫李四不要掛電話,就一直開著,知道李四下樓,就打一次電話。
這就是阻塞調用。?
非阻塞調用,pid_t waitpid,返回值大于0,等待結束;等于0,調用結束,但是子進程沒有退出;小于0,等待失敗。
非阻塞調用可以讓等待方做做自己的事。
阻塞等待只有大于和小于。
非阻塞調用示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <vector> // C++容器,需用g++編譯
#include <errno.h>// 函數指針類型定義
typedef void(*handler)();
// 全局函數回調列表(C++標準庫容器,需加std::)
std::vector<handler> func;void Load(){printf("登錄!\n");
}
void Exit(){printf("退出!\n");
}// 初始化回調函數列表(只需要初始化一次)
void work(){// 避免重復添加(如果已經有函數則不再添加)if(func.empty()){func.push_back(Load);func.push_back(Exit);}
}// 執行所有回調函數
void handle(){if(func.empty()){work(); // 若未初始化則先初始化}for(auto e : func){ // auto是C++11特性,需用g++編譯e();}
}int main(){pid_t id = fork();if(id < 0){printf("fork error! errno:%d\n", errno);return 1;}else if(id == 0){ // 子進程邏輯printf("我是子進程, pid:%d\n", getpid());sleep(3); // 子進程休眠3秒后退出exit(1); // 退出碼為1}else{ // 父進程邏輯int status = 0;// 非阻塞等待:WNOHANG表示若子進程未結束則立即返回0pid_t ret = waitpid(id, &status, WNOHANG);// 循環等待子進程結束(每次檢查都需要重新調用waitpid)while(ret == 0){printf("child is running!\n");handle(); // 執行回調函數(登錄、退出)sleep(1); // 休眠1秒再檢查,避免CPU空轉ret = waitpid(id, &status, WNOHANG); // 重新獲取子進程狀態printf("本輪調用結束!\n");}// 檢查等待結果(ret應為子進程PID,即id)if(ret == id){// 判斷子進程是否正常退出if(WIFEXITED(status)){printf("wait child success, child return code is :%d.\n",WEXITSTATUS(status));}} else {printf("wait child failed, ret:%d, errno:%d\n", ret, errno);return 1;}}return 0;
}
結束語
進程的創建、終止與等待,看似是三個獨立的操作,實則是操作系統 “資源管理” 與 “程序協作” 理念的集中體現:
fork
通過寫時拷貝實現高效的進程復制,既保證了進程獨立性,又避免了不必要的內存浪費;進程終止機制通過退出碼傳遞狀態,讓程序的結束有跡可循;而wait
/waitpid
函數則解決了 “僵尸進程” 的資源泄漏問題,確保系統資源的有序回收。理解這些機制,不僅能幫助我們寫出更健壯的多進程程序(如避免僵尸進程、正確處理子進程狀態),更能讓我們體會到操作系統設計的精妙 —— 每一個函數接口的背后,都是對 “效率” 與 “安全” 的平衡,每一種機制的實現,都服務于 “讓多任務協作更可靠” 的終極目標。
進程生命周期的故事遠不止于此,它與進程調度、信號處理等機制緊密相連,共同構成了操作系統的核心能力。希望本文能成為你探索進程管理的起點,讓你在編寫或調試多進程程序時,多一份對底層邏輯的清晰認知,少一份面對 “僵尸進程”“孤兒進程” 時的困惑。