進(jìn)程創(chuàng)建再識(shí)fork函數(shù)
在 linux中 fork 函數(shù)是非常重要的函數(shù),它從已存在進(jìn)程中創(chuàng)建?個(gè)新進(jìn)程。創(chuàng)建出來的新進(jìn)程叫做子進(jìn)程,而原進(jìn)程則稱為父進(jìn)程。
在linux參考手冊(cè)中,fork函數(shù)的原型如下:(man 2 fork 指令查看)
代碼語言:JavaScript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
NAME fork - create a child processSYNOPSIS #include <sys/types.h> #include <unistd.h> pid_t fork(void);
如上不難看出:
fork 函數(shù)的功能是創(chuàng)建一個(gè)子進(jìn)程頭文件有
進(jìn)程調(diào)用 fork,當(dāng)控制轉(zhuǎn)移到內(nèi)核中的 fork 代碼后,內(nèi)核做如下幾件事:
分配新的內(nèi)存塊和內(nèi)核數(shù)據(jù)結(jié)構(gòu)給子進(jìn)程將父進(jìn)程部分?jǐn)?shù)據(jù)結(jié)構(gòu)內(nèi)容拷貝至子進(jìn)程添加子進(jìn)程到系統(tǒng)進(jìn)程列表當(dāng)中fork返回,開始調(diào)度器調(diào)度

當(dāng)?個(gè)進(jìn)程調(diào)用fork之后,就有兩個(gè)?進(jìn)制代碼相同的進(jìn)程。并且它們都運(yùn)行到相同的地方。但每個(gè)進(jìn)程都將可以開始屬于它們自己的旅程,看如下程序:
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
int main(void){ pid_t pid; printf("Before: pid is %dn", getpid()); if ((pid = fork()) == -1) pError("fork()"), exit(1); printf("After:pid is %d, fork return %dn", getpid(), pid); sleep(1); return 0;}
輸出:
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
Before: pid is 40176After:pid is 40176, fork return 40177After:pid is 40177, fork return 0
如下圖所示:

所以,fork之前父進(jìn)程獨(dú)立執(zhí)行,fork之后,父子進(jìn)程兩個(gè)執(zhí)行流分別執(zhí)行之后的代碼。值得注意的是,fork之后,誰先執(zhí)行完全由調(diào)度器決定,并沒有明確的先后關(guān)系!
fork函數(shù)返回值
fork創(chuàng)建成功:
子進(jìn)程返回0父進(jìn)程返回的是子進(jìn)程的 pid
為什么給父進(jìn)程返回子進(jìn)程的pid,這個(gè)問題我們之前已經(jīng)討論過:
為什么子進(jìn)程返回0
fork創(chuàng)建失敗:
返回 -1并設(shè)置錯(cuò)誤碼:
當(dāng)系統(tǒng)資源不足(如進(jìn)程數(shù)超限、內(nèi)存耗盡)時(shí),fork() 失敗。
錯(cuò)誤碼:
需檢查 errno 確定具體原因代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
if (pid == -1) { perror("fork failed"); // 輸出類似 "fork failed: Resource temporarily unavailable"}
常見錯(cuò)誤碼:
EAGAIN:進(jìn)程數(shù)超過限制(RLIMIT_NPROC)或內(nèi)存不足。ENOMEM:內(nèi)核無法分配必要數(shù)據(jù)結(jié)構(gòu)所需內(nèi)存。
寫時(shí)拷貝 copy-On-Write
為什么需要寫時(shí)拷貝?
在傳統(tǒng)的進(jìn)程創(chuàng)建方式中,fork() 會(huì)直接復(fù)制父進(jìn)程的所有內(nèi)存空間給子進(jìn)程。這種方式存在明顯問題:
內(nèi)存浪費(fèi):如果父進(jìn)程占用 1GB 內(nèi)存,子進(jìn)程即使不修改任何數(shù)據(jù),也會(huì)立即消耗額外 1GB 內(nèi)存。性能低下:復(fù)制大量?jī)?nèi)存需要時(shí)間,尤其是對(duì)大型進(jìn)程而言,fork() 會(huì)顯著延遲程序運(yùn)行。
COW 的解決思路:
推遲實(shí)際的內(nèi)存復(fù)制,直到父子進(jìn)程中某一方嘗試修改內(nèi)存頁時(shí),才進(jìn)行真正的拷貝。在此之前,父子進(jìn)程共享同一份物理內(nèi)存。
具體見下圖:

因?yàn)橛袑憰r(shí)拷貝技術(shù)的存在,所以父子進(jìn)程得以徹底分離!完成了進(jìn)程獨(dú)立性的技術(shù)保證! 寫時(shí)拷貝,是?種延時(shí)申請(qǐng)技術(shù),可以提高整機(jī)內(nèi)存的使用率。
寫時(shí)拷貝的工作流程
1、 fork() 調(diào)用時(shí)
共享內(nèi)存頁:內(nèi)核僅為子進(jìn)程創(chuàng)建虛擬內(nèi)存結(jié)構(gòu)(頁表),但物理內(nèi)存頁仍與父進(jìn)程共享。標(biāo)記為只讀:內(nèi)核將共享的物理內(nèi)存頁標(biāo)記為只讀(即使父進(jìn)程原本可寫)。
2、進(jìn)程嘗試寫入內(nèi)存
觸發(fā)頁錯(cuò)誤:當(dāng)父進(jìn)程或子進(jìn)程嘗試修改某個(gè)共享內(nèi)存頁時(shí),由于頁被標(biāo)記為只讀,CPU 會(huì)觸發(fā)頁錯(cuò)誤(Page Fault)。
內(nèi)核介入處理:操作系統(tǒng)會(huì)由用戶態(tài)陷入內(nèi)核態(tài)處理異常
分配新的物理內(nèi)存頁,復(fù)制原頁內(nèi)容到新頁。修改觸發(fā)寫入的進(jìn)程的頁表,使其指向新頁。將新頁標(biāo)記為可寫,恢復(fù)進(jìn)程執(zhí)行。
3、后續(xù)操作
修改后的進(jìn)程獨(dú)享新內(nèi)存頁,另一進(jìn)程仍使用原頁。未修改的內(nèi)存頁繼續(xù)共享,不做復(fù)制,操作系統(tǒng)不做任何無意義的事情。
進(jìn)程等待
之前我們?cè)谥v進(jìn)程概念的時(shí)候講過,如果父進(jìn)程創(chuàng)建出子進(jìn)程后,如果子進(jìn)程已經(jīng)退出,父進(jìn)程卻沒有對(duì)子進(jìn)程回收,那么就子進(jìn)程就會(huì)變成 “僵尸進(jìn)程” ,造成內(nèi)存泄露等問題。
進(jìn)程等待的必要性
僵尸進(jìn)程問題:
子進(jìn)程終止后,其退出狀態(tài)會(huì)保留在進(jìn)程表中,直到父進(jìn)程讀取。若父進(jìn)程未處理,子進(jìn)程將保持僵尸狀態(tài)(Zombie),占用系統(tǒng)資源。狀態(tài)收集:父進(jìn)程需知曉子進(jìn)程的執(zhí)行結(jié)果(成功、錯(cuò)誤代碼、信號(hào)終止等)。資源回收:內(nèi)核釋放子進(jìn)程占用的內(nèi)存、文件描述符等資源。進(jìn)程等待的方法wait代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
#include<sys/types.h>#include<sys/wait.h>pid_t wait(int* status);
具體功能:
阻塞父進(jìn)程,直到等待到任意一個(gè)子進(jìn)程終止。
參數(shù):
status:輸出型參數(shù),用來存儲(chǔ)子進(jìn)程退出狀態(tài)的指針(可為 NULL,表示不關(guān)心狀態(tài))。
返回值:
成功:返回終止的子進(jìn)程PID。失敗:返回-1(如無子進(jìn)程)。
waitpid代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
#include <sys/wait.h>pid_t waitpid(pid_t pid, int *status, int options);
功能:更靈活的等待方式,可指定子進(jìn)程或非阻塞等待模式。
參數(shù):
pid:
>0:等待指定 PID 的子進(jìn)程。-1:等待任意子進(jìn)程(等效于 wait)。0:等待同一進(jìn)程組的子進(jìn)程。
status:同 wait,輸出型參數(shù),表明子進(jìn)程的退出狀態(tài)。
options: 默認(rèn)為0,表示阻塞等待
WNOHANG:非阻塞模式,無子進(jìn)程終止時(shí)立即返回 0。WUNTRACED:報(bào)告已停止的子進(jìn)程(如被信號(hào)暫停)。
返回值:
成功:返回子進(jìn)程PID。WNOHANG 且無子進(jìn)程終止:返回0。失敗:返回-1。
做個(gè)總結(jié):
如果子進(jìn)程已經(jīng)退出,調(diào)用 wait / waitpid 時(shí),wait / waitpid 會(huì)立即返回,并且釋放資源,獲得子進(jìn)程退出信息。如果在任意時(shí)刻調(diào)用 wait / waitpid,子進(jìn)程存在且正常運(yùn)行,則進(jìn)程可能阻塞。 如果不存在該子進(jìn)程,則立即出錯(cuò)返回。

獲取子進(jìn)程 status
wait 和 waitpid,都有?個(gè) status 參數(shù),該參數(shù)是?個(gè)輸出型參數(shù),由操作系統(tǒng)填充。
如果傳遞 NULL,表示不關(guān)心子進(jìn)程的退出狀態(tài)信息。否則,操作系統(tǒng)會(huì)根據(jù)該參數(shù),將子進(jìn)程的退出信息反饋給父進(jìn)程。
status 不能簡(jiǎn)單的當(dāng)作整形來看待,可以當(dāng)作位圖來看待,具體細(xì)節(jié)如下圖 (只研究 status 低16比特位):

如何理解呢? 子進(jìn)程的退出分為兩種情況:
正常終止
高 8 位(第 8 ~ 15 位):保存子進(jìn)程的退出狀態(tài)(退出碼)(即 exit(code) 或 return code 中的 code 值)。
第 7 位:通常為 0,表示正常終止。
示例:
若子進(jìn)程調(diào)用 exit(5),表明子進(jìn)程是正常退出,則 status 的高 8 位為 00000101(即十進(jìn)制 5)。
被信號(hào)所殺導(dǎo)致終止
低 7 位(第 0 ~ 6 位):保存導(dǎo)致子進(jìn)程終止的信號(hào)編號(hào)。
第 7 位:若為 1,表示子進(jìn)程在終止時(shí)生成了 core dump 文件(用于調(diào)試)。有關(guān) core dump 文件,后面會(huì)講,大家這里先了解一下即可。
第 8 ~ 15 位:未使用(通常為 0)。
示例:
若子進(jìn)程因 SIGKILL(信號(hào)編號(hào) 9)終止,則 status 的低 7 位為 0001001(即十進(jìn)制 9)。
做個(gè)小總結(jié):代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
低 16 位結(jié)構(gòu):| 15 14 13 12 11 10 9 8 | 7 | 6 5 4 3 2 1 0 |---------------------------------------------正常終止 → [ 退出狀態(tài)(高8位) ] 0 [ 未使用 ]被信號(hào)終止 → [ 未使用(全0) ] c [ 信號(hào)編號(hào) ]
如何解析 status?
使用宏定義檢查 status 的值:
宏
功能
WIFEXITED(status)
若子進(jìn)程正常終止(exit 或 return)返回真。
WEXITSTATUS(status)
若 WIFEXITED 為真,返回子進(jìn)程的退出碼(exit 的參數(shù)或 return 的值)。
WIFSIGNALED(status)
若子進(jìn)程因信號(hào)終止返回真。
WTERMSIG(status)
若 WIFSIGNALED 為真,返回導(dǎo)致終止的信號(hào)編號(hào)。
WCOREDUMP(status)
若子進(jìn)程生成了核心轉(zhuǎn)儲(chǔ)文件返回真。
常用的兩個(gè)宏:
WIFEXITED(status): 若為正常終止子進(jìn)程返回的狀態(tài),則為真。(查看進(jìn)程是 否是正常退出)WEXITSTATUS(status): 若WIFEXITED非零,提取子進(jìn)程退出碼。(查看進(jìn)程的 退出碼)
示例一:子進(jìn)程正常退出
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
int main(){ pid_t pid = fork(); if (pid == 0) { // 子進(jìn)程 printf("子進(jìn)程運(yùn)行中... PID=%dn", getpid()); // 1. 正常退出:調(diào)用 exit(42) exit(42); } else { // 父進(jìn)程 int status; waitpid(pid, &status, 0); // 等待子進(jìn)程結(jié)束 if (WIFEXITED(status)) { // 正常退出 printf("子進(jìn)程正常退出,退出碼: %dn", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { // 被信號(hào)終止 printf("子進(jìn)程被信號(hào)終止,信號(hào)編號(hào): %dn", WTERMSIG(status)); } } return 0;}
輸出:
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
子進(jìn)程運(yùn)行中... PID=56153子進(jìn)程正常退出,退出碼: 42
示例二:子進(jìn)程被信號(hào)終止
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
int main(){ pid_t pid = fork(); if (pid == 0) { // 子進(jìn)程 printf("子進(jìn)程運(yùn)行中... PID=%dn", getpid()); int *p = NULL; *p = 100; // 對(duì)空指針解引用,觸發(fā) SIGSEGV 被信號(hào)終止 } else { // 父進(jìn)程 int status; waitpid(pid, &status, 0); // 等待子進(jìn)程結(jié)束 if (WIFEXITED(status)) { // 正常退出 printf("子進(jìn)程正常退出,退出碼: %dn", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { // 被信號(hào)終止 printf("子進(jìn)程被信號(hào)終止,信號(hào)編號(hào): %dn", WTERMSIG(status)); } } return 0;}
輸出:
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
子進(jìn)程運(yùn)行中... PID=56203子進(jìn)程被信號(hào)終止,信號(hào)編號(hào): 11
阻塞等待與非阻塞等待阻塞等待(Blocking Wait)代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
pid_t waitpid(pid_t pid, int *status, 0); // options 參數(shù)為 0
示例:
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
int main(){ int status; pid_t child_pid = fork(); if (child_pid == 0) { // 子進(jìn)程執(zhí)行任務(wù) exit(10); } else { // 父進(jìn)程阻塞等待子進(jìn)程結(jié)束 waitpid(child_pid, &status, 0); if (WIFEXITED(status)) { printf("子進(jìn)程退出碼: %dn", WEXITSTATUS(status)); } }}
非阻塞等待(Non-blocking Wait)
關(guān)鍵選項(xiàng):宏 WNOHANG(定義在
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
pid_t waitpid(pid_t pid, int *status, WNOHANG);
示例:非阻塞輪詢方式
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
int main(){ int status; pid_t child_pid = fork(); if (child_pid == 0) { sleep(3); // 子進(jìn)程休眠 3 秒后退出 exit(10); } else { while (1) { pid_t ret = waitpid(child_pid, &status, WNOHANG); if (ret == -1) { perror("waitpid"); break; } else if (ret == 0) { printf("子進(jìn)程未結(jié)束,父進(jìn)程繼續(xù)工作...n"); sleep(1); // 避免頻繁輪詢消耗 CPU } else { if (WIFEXITED(status)) { printf("子進(jìn)程退出碼: %dn", WEXITSTATUS(status)); } break; } } }}
阻塞等待和非阻塞等待的對(duì)比:
場(chǎng)景
阻塞等待
非阻塞等待
父進(jìn)程任務(wù)優(yōu)先級(jí)
必須立即處理子進(jìn)程結(jié)果
需同時(shí)處理其他任務(wù)
子進(jìn)程執(zhí)行時(shí)間
較短或確定
較長(zhǎng)或不確定
資源消耗
CPU 空閑,無額外開銷
需輪詢,可能占用更多 CPU
典型應(yīng)用
簡(jiǎn)單腳本、單任務(wù)場(chǎng)景
多進(jìn)程管理、事件驅(qū)動(dòng)程序
進(jìn)程終止
進(jìn)程= 內(nèi)核數(shù)據(jù)結(jié)構(gòu) + 進(jìn)程自己的代碼和數(shù)據(jù)
進(jìn)程退出場(chǎng)景代碼運(yùn)行完畢,結(jié)果正確代碼運(yùn)行完畢,結(jié)果不正確代碼異常終止
如何理解這三種進(jìn)程退出的場(chǎng)景呢?舉個(gè)例子
代碼運(yùn)行完畢,結(jié)果正確
程序完整執(zhí)行了所有邏輯,未觸發(fā)任何錯(cuò)誤或異常。輸出結(jié)果與預(yù)期完全一致,符合功能需求或算法目標(biāo)。代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
int sum(int a, int b){ return a + b;}int main(){ int result = sum(3, 5); printf("Result: %dn", result); // 輸出 8,結(jié)果正確 return 0;}
輸出:
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
Result: 8
代碼運(yùn)行完畢,結(jié)果不正確
程序正常結(jié)束(無崩潰或異常),但輸出結(jié)果與預(yù)期不符。通常由邏輯錯(cuò)誤、算法錯(cuò)誤或數(shù)據(jù)處理錯(cuò)誤導(dǎo)致。
例如:
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
// 錯(cuò)誤實(shí)現(xiàn):本應(yīng)計(jì)算階乘,但初始值錯(cuò)誤int factorial(int n){ int result = 0; // 錯(cuò)誤!應(yīng)為 result = 1 for (int i = 1; i <= n; i++) { result *= i; } return result;}int main(){ printf("5! = %dn", factorial(5)); // 輸出 0,結(jié)果錯(cuò)誤 return 0;}
代碼未執(zhí)行完畢,異常終止
程序未執(zhí)行完畢就中途崩潰或被強(qiáng)制終止。通常由運(yùn)行時(shí)錯(cuò)誤、資源限制或外部信號(hào)觸發(fā)。比如除零錯(cuò)誤,對(duì)空指針解引用等異常
例如:
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
int main(){ int *ptr = NULL; *ptr = 42; // 對(duì)空指針解引用,觸發(fā)段錯(cuò)誤 printf("Value: %dn", *ptr); return 0;}
段錯(cuò)誤:
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
Segmentation fault
再比如:
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
int main(){ int a = 10; int b = a / 0; // 程序除零異常 printf("Value: %dn", b); return 0;}
浮點(diǎn)數(shù)異常:
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
Floating point exception
進(jìn)程常見退出方法
正常終止(可以通過 echo $? 查看進(jìn)程退出碼)
從main返回調(diào)用exit_exit
異常退出:
ctrl + c,信號(hào)終止
進(jìn)程退出碼
退出碼是一個(gè) 8 位無符號(hào)整數(shù)(8-bit unsigned Integer),因此取值范圍為 2^8=256 個(gè)值。
Linux Shell 中的常見退出碼:

退出碼 0 表示命令執(zhí)行有誤,這是完成命令的理想狀態(tài)。退出碼 1 我們也可以將其解釋為 “不被允許的操作”。例如在沒有 sudo 權(quán)限的情況下使用 yum;130 ( SIGINT 或 ^C )和 143 ( SIGTERM )等終止信號(hào)是非常典型的,它們屬于 128+n 信號(hào),其中 n 代表信號(hào)編號(hào)。
這里需要補(bǔ)充一點(diǎn):
進(jìn)程退出碼和錯(cuò)誤碼是兩個(gè)完全不同的概念,不要混為一談!
錯(cuò)誤碼
要理解錯(cuò)誤碼,首先要認(rèn)識(shí)全局變量 error
例如:fork函數(shù)調(diào)用失敗后,會(huì)立刻返回-1,并設(shè)置全局變量 error

定義:errno 是一個(gè)線程安全的整型變量,用于存儲(chǔ)最近一次系統(tǒng)調(diào)用或庫函數(shù)調(diào)用失敗的錯(cuò)誤碼。
特性:
成功調(diào)用不會(huì)重置 errno,因此必須在調(diào)用后立即檢查其值。每個(gè)線程有獨(dú)立的 errno 副本(多線程安全)。
頭文件:
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
#include <errno.h>
與之對(duì)應(yīng)的是 strerror 函數(shù),該函數(shù)可以將對(duì)應(yīng)的錯(cuò)誤碼轉(zhuǎn)化成字符串描述的錯(cuò)誤信息打印出來,方便程序員調(diào)試代碼。
實(shí)際上,我們可以通過 for 循環(huán)來打印查看Linux系統(tǒng)下所有的錯(cuò)誤碼以及其錯(cuò)誤信息:
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
int main(){ for (int i = 0; i < 135; ++i) { printf("%d-> %sn", i, strerror(i)); } return 0;}
不難看出,在Linux系統(tǒng)下,一共有 0 ~ 133 總共134個(gè)錯(cuò)誤碼,其中 0 表示 success ,即程序運(yùn)行成功, 1 ~ 133 則分別對(duì)應(yīng)一個(gè)錯(cuò)誤信息。
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
0-> Success1-> Operation not permitted2-> No such file or directory3-> No such process4-> Interrupted system call5-> Input/output error6-> No such device or address7-> Argument list too long8-> Exec format error9-> Bad file descriptor10-> No child processes11-> Resource temporarily unavailable12-> Cannot allocate memory13-> Permission denied14-> Bad address15-> Block device required16-> Device or resource busy17-> File exists18-> Invalid cross-device link19-> No such device20-> Not a directory21-> Is a directory22-> Invalid argument23-> Too many open files in system24-> Too many open files25-> Inappropriate ioctl for device26-> Text file busy27-> File too large28-> No space left on device29-> Illegal seek30-> Read-only file system31-> Too many links32-> Broken pipe33-> Numerical argument out of domain34-> Numerical result out of range35-> Resource deadlock avoided36-> File name too long37-> No locks available38-> Function not implemented39-> Directory not empty40-> Too many levels of symbolic links41-> Unknown error 4142-> No message of desired type43-> Identifier removed44-> Channel number out of range45-> Level 2 not synchronized46-> Level 3 halted47-> Level 3 reset48-> Link number out of range49-> Protocol driver not attached50-> No CSI structure available51-> Level 2 halted52-> Invalid exchange53-> Invalid request descriptor54-> Exchange full55-> No anode56-> Invalid request code57-> Invalid slot58-> Unknown error 5859-> Bad font file format60-> Device not a stream61-> No data available62-> Timer expired63-> Out of streams resources64-> Machine is not on the network65-> Package not installed66-> Object is remote67-> Link has been severed68-> Advertise error69-> Srmount error70-> Communication error on send71-> Protocol error72-> Multihop attempted73-> RFS specific error74-> Bad message75-> Value too large for defined data type76-> Name not unique on network77-> File descriptor in bad state78-> Remote address changed79-> Can not Access a needed shared library80-> Accessing a corrupted shared library81-> .lib section in a.out corrupted82-> Attempting to link in too many shared libraries83-> Cannot exec a shared library directly84-> Invalid or incomplete multibyte or wide character85-> Interrupted system call should be restarted86-> Streams pipe error87-> Too many users88-> Socket operation on non-socket89-> Destination address required90-> Message too long91-> Protocol wrong type for socket92-> Protocol not available93-> Protocol not supported94-> Socket type not supported95-> Operation not supported96-> Protocol family not supported97-> Address family not supported by protocol98-> Address already in use99-> Cannot assign requested address100-> Network is down101-> Network is unreachable102-> Network dropped connection on reset103-> Software caused connection abort104-> Connection reset by peer105-> No buffer space available106-> Transport endpoint is already connected107-> Transport endpoint is not connected108-> Cannot send after transport endpoint shutdown109-> Too many references: cannot splice110-> Connection timed out111-> Connection refused112-> Host is down113-> No route to host114-> Operation already in progress115-> Operation now in progress116-> Stale file handle117-> Structure needs cleaning118-> Not a XENIX named type file119-> No XENIX semaphores available120-> Is a named type file121-> Remote I/O error122-> Disk quota exceeded123-> No medium found124-> Wrong medium type125-> Operation canceled126-> Required key not available127-> Key has expired128-> Key has been revoked129-> Key was rejected by service130-> Owner died131-> State not recoverable132-> Operation not possible due to RF-kill133-> Memory page has hardware error134-> Unknown error 134
錯(cuò)誤碼的應(yīng)用:
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
int main(){ FILE *fp = fopen("invalid.txt", "r");//以只讀方式打開不存在的文件會(huì)出錯(cuò) if (fp == NULL) { // 使用 strerror 獲取錯(cuò)誤描述 printf("%d->%sn", errno,strerror(errno)); return 1; //退出碼設(shè)為1 } return 0;}
輸出:
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
2->No such file or directory
使用錯(cuò)誤碼和對(duì)應(yīng)的錯(cuò)誤信息可以幫助程序員快速定位錯(cuò)誤模塊,調(diào)試程序,掌握錯(cuò)誤碼的使用與調(diào)試技巧,是提升 Linux 編程效率和系統(tǒng)可靠性的關(guān)鍵。
_exit函數(shù)和exit函數(shù)
_exit函數(shù)
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
#include <unistd.h>void _exit(int status);
參數(shù) status:進(jìn)程的退出狀態(tài)碼,范圍是 0~255。父進(jìn)程可以通過 wait() 或 waitpid() 獲取該狀態(tài)碼。返回值:無(進(jìn)程直接終止,不會(huì)返回調(diào)用者)。
當(dāng)前進(jìn)程調(diào)用 _exit() 后,操作系統(tǒng)會(huì)立即介入,會(huì)從用戶態(tài)陷入內(nèi)核態(tài),執(zhí)行以下操作:
關(guān)閉所有文件描述符:內(nèi)核會(huì)關(guān)閉進(jìn)程打開的文件、套接字、管道等資源,但不會(huì)刷新標(biāo)準(zhǔn) I/O 庫(如 stdio)的緩沖區(qū)。釋放用戶空間內(nèi)存:回收進(jìn)程的代碼段、數(shù)據(jù)段、堆、棧等內(nèi)存資源。發(fā)送 SIGCHLD 信號(hào): 通知父進(jìn)程子進(jìn)程已終止,并傳遞退出狀態(tài)碼 status。終止進(jìn)程:進(jìn)程的狀態(tài)變?yōu)?ZOMBIE(僵尸進(jìn)程),直到父進(jìn)程通過 wait() 回收其資源。
本質(zhì)上,_exit() 最終會(huì)調(diào)用 Linux 內(nèi)核的 exit_group 系統(tǒng)調(diào)用(sys_exit_group),終止整個(gè)進(jìn)程及其所有線程。其內(nèi)核處理流程如下:
釋放進(jìn)程資源:
關(guān)閉所有文件描述符。釋放內(nèi)存映射(mmap)和虛擬內(nèi)存區(qū)域。解除信號(hào)處理程序綁定。
更新進(jìn)程狀態(tài):
將進(jìn)程狀態(tài)設(shè)為 TASK_DEAD向父進(jìn)程發(fā)送 SIGCHLD 信號(hào)。
調(diào)度器介入:
從運(yùn)行隊(duì)列中移除進(jìn)程。切換到下一個(gè)進(jìn)程執(zhí)行。
exit函數(shù)
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
#include <stdlib.h>void exit(int status); // C #include <cstdlib>void exit(int status); // c++
參數(shù) status:進(jìn)程的退出狀態(tài)碼,范圍 0~255(0 通常表示成功,非零表示異常)。返回值:無(進(jìn)程終止,不會(huì)返回調(diào)用者)。
進(jìn)程調(diào)用 exit 時(shí),按以下順序執(zhí)行操作:
調(diào)用 atexit 注冊(cè)的函數(shù):按注冊(cè)的逆序執(zhí)行所有通過 atexit 或 at_quick_exit(若使用quick_exit)注冊(cè)的函數(shù)。刷新所有標(biāo)準(zhǔn) I/O 緩沖區(qū):清空 stdout、stderr 等流的緩沖區(qū)。 注意: stderr 默認(rèn)無緩沖,stdout 在交互式設(shè)備上是行緩沖。關(guān)閉所有打開的文件流:調(diào)用 fclose 關(guān)閉所有通過 fopen 打開的文件。 注意:不會(huì)關(guān)閉底層文件描述符(需手動(dòng) close)。刪除臨時(shí)文件:刪除由 tmpfile 創(chuàng)建的臨時(shí)文件。終止進(jìn)程:向操作系統(tǒng)返回狀態(tài)碼 status。父進(jìn)程可通過 wait 或 waitpid 獲取該狀態(tài)碼。
其實(shí)本質(zhì)上,exit 是一個(gè)標(biāo)準(zhǔn)庫函數(shù),最后也會(huì)調(diào)用_exit,但是在這之前,exit還做了其他的清理工作:

我們舉個(gè)例子,幫大家直觀的感受一下這兩者的區(qū)別:
示例一:使用 exit 函數(shù)
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
int main(){ printf("hello"); exit(0);}
輸出:
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
[root@localhost linux]# ./a.outhello[root@localhost linux]#
示例二:使用 _exit 函數(shù)
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
int main(){ printf("hello"); _exit(0);}
輸出:
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
[root@localhost linux]# ./a.out[root@localhost linux]#
return 退出
狀態(tài)碼傳遞:
main函數(shù)中的 return 語句返回一個(gè)整數(shù)值(通常稱為退出狀態(tài)碼),表示程序的執(zhí)行結(jié)果:
0:表示程序成功執(zhí)行。非0:表示程序異常終止(具體數(shù)值由程序員定義)。
return與exit()的關(guān)系
隱式調(diào)用exit():
在 main 函數(shù)中使用 return 時(shí),C/C++運(yùn)行時(shí)會(huì)自動(dòng)調(diào)用 exit() 函數(shù),并將返回值作為參數(shù)傳遞給它。代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
int main(){ return 42; // 等價(jià)于 exit(42);}
return的執(zhí)行流程
當(dāng)在main函數(shù)中執(zhí)行return時(shí),程序會(huì)做以下幾件事:
返回值傳遞:將返回值傳遞給運(yùn)行時(shí)環(huán)境。
清理操作:
調(diào)用局部對(duì)象的析構(gòu)函數(shù)(按照創(chuàng)建順序的逆序)。調(diào)用全局對(duì)象的析構(gòu)函數(shù)(同樣逆序)。
調(diào)用exit():運(yùn)行時(shí)調(diào)用exit(),執(zhí)行以下操作:
刷新所有I/O緩沖區(qū)(如 std::cout)。關(guān)閉通過 fopen 打開的文件流。執(zhí)行通過 atexit() 注冊(cè)的函數(shù)。
終止進(jìn)程:將控制權(quán)交還給操作系統(tǒng)。
值得注意的一點(diǎn)是:在非main函數(shù)的其他函數(shù)中使用 return 僅退出當(dāng)前函數(shù),返回到調(diào)用者,不會(huì)終止進(jìn)程。
_exit、exit 和 return 對(duì)比
以下是一個(gè)詳細(xì)的表格供大家理解參考
特性
_exit() (系統(tǒng)調(diào)用)
exit() (標(biāo)準(zhǔn)庫函數(shù))
return (在 main 中)
所屬標(biāo)準(zhǔn)
POSIX 系統(tǒng)調(diào)用
C/C++ 標(biāo)準(zhǔn)庫函數(shù)
C/C++ 語言關(guān)鍵字
頭文件
無(語言內(nèi)置)
執(zhí)行流程
立即終止進(jìn)程,不執(zhí)行任何用戶空間清理。
1. 調(diào)用 atexit 注冊(cè)的函數(shù)2. 刷新 I/O 緩沖區(qū)3. 關(guān)閉文件流
1. 調(diào)用 C++ 局部對(duì)象析構(gòu)函數(shù)2. 隱式調(diào)用 exit() 完成后續(xù)清理
清理操作
內(nèi)核自動(dòng)回收進(jìn)程資源(內(nèi)存、文件描述符),不刷新緩沖區(qū)、不調(diào)用析構(gòu)函數(shù)
清理標(biāo)準(zhǔn)庫資源(刷新緩沖區(qū)、關(guān)閉文件流),但不調(diào)用 C++ 局部對(duì)象析構(gòu)函數(shù)
調(diào)用 C++ 局部和全局對(duì)象析構(gòu)函數(shù),并觸發(fā) exit() 的清理邏輯
多線程行為
立即終止所有線程,可能導(dǎo)致資源泄漏
終止整個(gè)進(jìn)程,但可能跳過部分線程資源釋放(如線程局部存儲(chǔ))
同 exit(),但在 C++ 中會(huì)正確析構(gòu)主線程的局部對(duì)象
C++ 析構(gòu)函數(shù)調(diào)用
? 不調(diào)用任何對(duì)象的析構(gòu)函數(shù)(包括全局對(duì)象)
? 不調(diào)用局部對(duì)象析構(gòu)函數(shù)? 調(diào)用全局對(duì)象析構(gòu)函數(shù)(C++)
? 調(diào)用局部和全局對(duì)象析構(gòu)函數(shù)(C++)
緩沖區(qū)處理
? 不刷新 stdio 緩沖區(qū)(如 printf 的輸出可能丟失)
? 刷新所有 stdio 緩沖區(qū)
? 通過隱式調(diào)用 exit() 刷新緩沖區(qū)
適用場(chǎng)景
1. 子進(jìn)程退出(避免重復(fù)刷新緩沖區(qū))2. 需要立即終止進(jìn)程(繞過清理邏輯)
1. 非 main 函數(shù)的程序終止2. 需要執(zhí)行注冊(cè)的清理函數(shù)(如日志收尾)
1. 在 main 函數(shù)中正常退出2. 需要確保 C++ 對(duì)象析構(gòu)(RAII 資源管理)
錯(cuò)誤處理
直接傳遞狀態(tài)碼給操作系統(tǒng),無錯(cuò)誤反饋機(jī)制
可通過 atexit 注冊(cè)錯(cuò)誤處理函數(shù),但無法捕獲局部對(duì)象析構(gòu)異常
可通過 C++ 異常機(jī)制處理錯(cuò)誤(需在 main 中捕獲)
信號(hào)安全
? 可在信號(hào)處理函數(shù)中安全調(diào)用(如 SIGINT)
? 不可在信號(hào)處理函數(shù)中調(diào)用(可能死鎖)
? 不可在信號(hào)處理函數(shù)中使用(僅限 main 函數(shù)流程)
資源泄漏風(fēng)險(xiǎn)
高(臨時(shí)文件、未釋放的手動(dòng)內(nèi)存等需內(nèi)核回收)
中(未關(guān)閉的文件描述符、手動(dòng)內(nèi)存需提前處理)
低(依賴 RAII 自動(dòng)釋放資源)
底層實(shí)現(xiàn)
直接調(diào)用內(nèi)核的 exit_group 系統(tǒng)調(diào)用
調(diào)用 C 標(biāo)準(zhǔn)庫的清理邏輯后,最終調(diào)用 _exit()
編譯器生成代碼調(diào)用析構(gòu)函數(shù),并跳轉(zhuǎn)到 main 結(jié)尾觸發(fā) exit()
最后總結(jié)下:
_exit():最底層的終止方式,適合需要繞過所有用戶空間清理的場(chǎng)景(如子進(jìn)程退出)。exit():平衡安全與效率,適合非 main 函數(shù)的程序終止,但需注意 C++ 對(duì)象析構(gòu)問題。return:C++ 中最安全的退出方式,優(yōu)先在 main 函數(shù)中使用,確保資源自動(dòng)釋放。
# linux# ai# 對(duì)象# 字符串# JavaScript# 操作系統(tǒng)# 事件# 算法# 線程# 循環(huán)# 數(shù)據(jù)結(jié)構(gòu)# for# c++# Access# NULL# 區(qū)別# 堆# 棧# Error# 多線程# 指針# echo# 標(biāo)準(zhǔn)庫# 全局變量# 主線程# copy# 空指針# 整型# Integer# printf# void# errno# 值傳遞# fopen# 析構(gòu)函數(shù)# typedef# fclose# strerror# 鴻蒙