Linux下的進(jìn)程控制解讀
進(jìn)程創(chuàng)建
在Linux環(huán)境下,我們使用系統(tǒng)調(diào)用接口 fork()函數(shù),來創(chuàng)建進(jìn)程!

參數(shù):無參;
返回值:
創(chuàng)建失敗,返回負(fù)值;
創(chuàng)建成功,對于子進(jìn)程返回0,對于父進(jìn)程返回子進(jìn)程的pid;
fork返回值的認(rèn)識
可以移步至我的另一篇文章:Linux下的進(jìn)程地址空間----頁表
fork創(chuàng)建失敗的原因
1、物理內(nèi)存不夠了用了;創(chuàng)建子進(jìn)程也是需要消耗物理內(nèi)存的!
2、父進(jìn)程創(chuàng)建子進(jìn)程的上限到了,OS為了限制用戶父進(jìn)程無限制的創(chuàng)建子進(jìn)程,通常都會給父進(jìn)程設(shè)置一個"進(jìn)程上限";
fork函數(shù)的應(yīng)用場景
1、一個父進(jìn)程希望復(fù)制自己,使父子進(jìn)程同時執(zhí)行不同的代碼段。例如:父進(jìn)程等待客服端請求,生成子進(jìn)程來處理請求;
2、一個進(jìn)程需要執(zhí)行不同的程序。例如:子進(jìn)程從fork函數(shù)返回后,調(diào)用Add()接口,父進(jìn)程去執(zhí)行Sub()接口;
進(jìn)程終止
進(jìn)程終止的三種方式
1、程序正常運行,最后結(jié)果正確;
2、程序正常運行,最后結(jié)果錯誤;
3、程序異常終止?。ū热纾撼霈F(xiàn)除以0、對空指針解引用、kill -9 命令殺掉進(jìn)程等等)這本質(zhì)上:就是OS向該進(jìn)程發(fā)送了一種信號!
進(jìn)程退出碼
進(jìn)程退出碼存在的前提就是程序正常運行完畢!在其他情況下,進(jìn)程退出碼沒有意義!
那么什么是進(jìn)程退出碼?
一個程序是從main函數(shù)開始的吧,那么終止也是終止與main函數(shù)對吧,main函數(shù)最后是不是有個return 0;這個0就是進(jìn)程退出碼;
return 0,表示該進(jìn)程正常運行,且運行結(jié)果正確;
return 其他數(shù)據(jù),表示該進(jìn)程正常運行,但是運行結(jié)果錯誤,那么到底是為什么錯誤呢?作為父進(jìn)程需要知道原因,我們此時return 的數(shù)字就表示這個錯誤原因,這個數(shù)字也是進(jìn)程退出碼!
當(dāng)然不止main函數(shù)的返回值是進(jìn)程退出碼,exit()/_exit()函數(shù)的參數(shù)都是進(jìn)程退出碼!
我們可以理解為exit()/-exit()//函數(shù)的參數(shù)等價于return 數(shù)字;
return 數(shù)字只能在main函數(shù)內(nèi)部進(jìn)行返回,但是如果我們在main函數(shù)以外的地方,遇到了不合理的地方想要提前終止進(jìn)程(比如利用malloc開辟空間失敗,我們就可以提前終止進(jìn)程),我們就可以利用exit()/_exit(),來提前終止掉該進(jìn)程!
exit()/_exit()//的參數(shù)就相當(dāng)于main函數(shù)的返回值!0:表示正常運行結(jié)束,結(jié)果運行正確;其他數(shù)字表示正常運行結(jié)束,結(jié)果運行錯誤!
那么exit()/與_exit()看起來長得很像啊,那么他們有區(qū)別嗎?
當(dāng)然有,我們可以通過下面一段測試代碼來看待結(jié)果:

分析:printf在輸出“Hello World!”的時候,會首先將字符串發(fā)送到“緩沖區(qū)”,但是由于字符串末尾沒有’\n’,printf后面沒有輸入語句,printf語句后面沒有fflush強(qiáng)制刷新緩沖區(qū)!我們并不會立即看到字符串,在休眠2s過后進(jìn)程調(diào)用exit提前終止掉了,也就是程序結(jié)束了,程序結(jié)束是會刷新緩沖區(qū),也就是說我們會看到“Hello World!”,那么結(jié)果到底是不是?我們運行一下:

結(jié)果的確是這樣!
那么如果我們再來利用_exit()函數(shù)來提前終止程序呢?


我們會發(fā)現(xiàn)什么也沒有?。?!
嗯?為什么會這樣?
首先我們的字符串會被先發(fā)送到緩沖區(qū),這是沒有問題的?問題是我們利用_exit()提前結(jié)束掉進(jìn)程過后,屏幕上沒有出現(xiàn)字符串!但是利用exit()提前結(jié)束,確有字符串!
這到底是為什么?
首先我們需要明白,exit()是C語言庫函數(shù)里面的函數(shù)接口,而_exit()不屬于C語言,_exit()是系統(tǒng)調(diào)用;exit()內(nèi)部也是通過調(diào)用_exit()系統(tǒng)調(diào)用來終止進(jìn)程的,只不過exit()內(nèi)部更加豐富,在結(jié)束進(jìn)程之前,會做一些工作,比如:執(zhí)行用戶定義的清理
函數(shù)、刷新緩沖區(qū)、關(guān)閉流等;但是_exit()沒有這么多前戲,就是純粹的終止進(jìn)程!它不會刷新緩存區(qū),因此我們在調(diào)用_exit()結(jié)束進(jìn)程的時候,沒有刷新緩沖區(qū),直接就結(jié)束掉進(jìn)程了,我們的字符串也就一直停留在緩沖區(qū),沒有機(jī)會輸出到屏幕上!

同時我們的return 數(shù)字;準(zhǔn)確上來說等價于exit();因此在平常的使用中我們更推薦使用exit();
為什么需要進(jìn)程退出碼?
子進(jìn)程是被父進(jìn)程創(chuàng)建的,父進(jìn)程創(chuàng)建子進(jìn)程是為了讓子進(jìn)程幫助自己完成某樣任務(wù),那么子進(jìn)程運行結(jié)束了,子進(jìn)程到底把這個任務(wù)完成的怎么樣?是好?還是壞?作為父進(jìn)程是需要知道的!進(jìn)程退出碼就是用于告訴父進(jìn)程它交給子進(jìn)程完成的任
務(wù)完成的怎么樣的信息!
父進(jìn)程再把這個信息報告給我們用戶!
但是對于我們用戶來說,進(jìn)程退出碼就是一個單純的數(shù)字,我們作為人類只看的懂字符串!因此我們需要一樣表來映射進(jìn)程退出碼的對應(yīng)信息!在C語言庫中,給我們提供了一張這樣的映射表,我們可以通過strerror()函數(shù)來訪問這種表!

參數(shù): 錯誤號;
返回值: 錯誤號對應(yīng)的字符串首地址!
通過下面一段程序,我們來輸出一下“錯誤表”中對應(yīng)的錯誤信息:

運行結(jié)果:

我們可以看到“錯誤表”里面總共記錄了134個錯誤信息;當(dāng)然這只是Linux環(huán)境下的,Window環(huán)境下可能會不一樣!這張“錯誤表”雖然是C語言庫給我們提供的,但是我們沒必要一定要遵守這張表,我們可以映射自己的“錯誤表”;當(dāng)然這些錯誤信息最多也就255個;
如何查看進(jìn)程退出碼?
好,現(xiàn)在我知道了什么是進(jìn)程退出碼和為什么要有進(jìn)程退出碼,那么能不能讓我們見一見進(jìn)程退出碼?
當(dāng)然可以:查看進(jìn)程退出碼用命令echo $?//這個命令是查看最近一次進(jìn)程運行結(jié)束的退出碼!
比如現(xiàn)在我們故意將我們的進(jìn)程退出碼設(shè)置"特殊"一點,方便我們觀察:


當(dāng)然我們也可以用于產(chǎn)看系統(tǒng)指令的進(jìn)程退出碼,比如:

我們可以用這個進(jìn)程退出碼去查一下C庫的“錯誤表”:

發(fā)現(xiàn)ls的進(jìn)程退出碼(2)的確實對應(yīng)的“No such file or directory”的錯誤信息,表示ls命令采用了C庫的“錯誤表”;
當(dāng)然也有一些是命令是不遵循的,比如:

查看C庫“錯誤表”:

我們發(fā)現(xiàn)是匹配不上的,說明kill命令并沒有采用C庫里面的錯誤信息,而是采用了自己設(shè)計的!
進(jìn)程等待
為什么要進(jìn)行進(jìn)程等待?
進(jìn)程等待是誰等?等誰?
答案是:父進(jìn)程等,等子進(jìn)程!
明白了這一點,我們再來談為什么要進(jìn)行進(jìn)程等待?
1、子進(jìn)程不可能直接就推出了,父進(jìn)程需要獲取子進(jìn)程的退出碼,來獲知交給子進(jìn)程的任務(wù)完成的怎么樣;
2、因此,子進(jìn)程在終止過后會進(jìn)入僵尸狀態(tài),以此來讓父進(jìn)程獲取子進(jìn)程的退出碼;但是處于僵尸狀態(tài)有一個弊端:僵尸不死不滅,就算是用kill -9命令也無法將其殺死!因為它本身就已經(jīng)終止了,沒辦法殺死一個已經(jīng)死去的進(jìn)程!但是,該進(jìn)程占據(jù)的空間還沒有釋放,如果沒有人來釋放掉該空間的話,就會造成內(nèi)存泄漏!為此父進(jìn)程在獲取到子進(jìn)程的退出碼過后,會順便將子進(jìn)程的空間也一并釋放了;
什么是進(jìn)程等待?
知道了為什么需要進(jìn)行進(jìn)程等待,我們也就明白了進(jìn)程等待的本質(zhì):
進(jìn)程等待的本質(zhì):獲取子進(jìn)程退出碼以此來知曉父進(jìn)程交代給子進(jìn)程的任務(wù)子進(jìn)程完成的怎么樣,并順便釋放掉子進(jìn)程的空間!
怎么進(jìn)行進(jìn)程等待?
我們可以使用系統(tǒng)調(diào)用wait()/waitpid();來實現(xiàn)!
在具體使用之前,我們需要先了解一下這兩個系統(tǒng)調(diào)用:
pid_t wait(int*status);
Linux下包含頭文件:sys/types.h和sys/wait.h
參數(shù): 輸出型參數(shù)用于獲取進(jìn)程退出碼和信號(如果程序正常運行結(jié)束的話,信號是0);當(dāng)然如果我們不關(guān)心進(jìn)程的退出碼和信號的話,我們可以將其設(shè)置為NULL;
返回值: 等待成功:返回等待進(jìn)程的pid;等待失敗(比如父進(jìn)程沒有子進(jìn)程),返回-1;
下面我們就先來實際用一下wait()系統(tǒng)調(diào)用:

運行結(jié)果:

實際上我們不需要使用sleep()函數(shù),當(dāng)父進(jìn)程調(diào)用wait函數(shù)時,父進(jìn)程也會自動的等待子進(jìn)程運行完畢!相當(dāng)于在子進(jìn)程運行的這段期間,父進(jìn)程相當(dāng)于卡在了wait函數(shù)內(nèi)部!這叫做阻塞等待!父進(jìn)程會被OS放入一個等待隊列中進(jìn)行等待子進(jìn)程的運行結(jié)束!

靜態(tài)圖片不好演示父進(jìn)程等待的這個過程,讀者可以自行驗證!

注意:wait是處理當(dāng)前進(jìn)程中最先進(jìn)入僵尸狀態(tài)的子進(jìn)程;
上面介紹了wait()系統(tǒng)調(diào)用,接著我們來介紹一下waitpid()系統(tǒng)調(diào)用,waitpid與wait功能相似,wait是處理最先處于僵尸狀態(tài)的子進(jìn)程,waitpid是處理指定pid的子進(jìn)程;
pid_t waitpid(pid_t pid,int*status,int options);
參數(shù): pid:指定等待的子進(jìn)程;pid=-1時則處理最先處于僵尸狀態(tài)的子進(jìn)程;
- status:輸出型參數(shù);與wait的status功能一樣;
- option:決定父進(jìn)程在等待的過程中是否可與去做其他事情;
- 0:不可以;WNOHANG:可以!
返回值::
option=0:
- 等待成功,返回子進(jìn)程pid;
- 等待失敗,返回-1;
option=WNOHANG:
- 等待成功,返回子進(jìn)程pid;
- 子進(jìn)程還沒運行結(jié)束,返回0;
- 等待失?。ㄗ舆M(jìn)程不存在),返回-1,
具體用法如下:


現(xiàn)在如果我們關(guān)心子進(jìn)程的退出信息呢?
那么我們的status參數(shù)就不能在傳空指針了,我們需要傳遞一個int類型的指針過去,這個int類型的指針必須指向一塊有效的空間!相當(dāng)于:

也就是說status必須指向一個int類型的空間,這個int類型的空間用來存儲子進(jìn)程的退出碼+信號信息!
是的!你沒聽錯!這個int空間會被用來存儲兩種信息!
- int 類型有4個字節(jié)對吧!而我們的退出碼也就0~255種情況一個字節(jié)就能存儲起來,我們的信號信息也就幾十個情況,也能用一個字節(jié)存儲起來!
- status不能簡單的當(dāng)作整形來看待,可以當(dāng)作位圖來看待,具體細(xì)節(jié)如下圖(只研究status低16比特位):

那么我們?nèi)绾文贸龅竭M(jìn)程退出碼和終止信號?
我們可以利用(status指向的整形>>8)&0xFF的方式來拿出進(jìn)程退出碼!(status&0x7F)的方式來拿出終止信號;
下面我們來具體演示一下:

我們設(shè)置的子進(jìn)程的退出碼是107,如果子進(jìn)程是正常運行結(jié)束的話,那么終止信號也就是0;
那么結(jié)果是不是呢?

答案確實是這樣?
當(dāng)然如果程序是異常終止的話,那么信號也是其它數(shù)字,這時候在看進(jìn)程退出碼已經(jīng)沒有任何意義了!
(我們這里故意制造一個,子進(jìn)程除以0的語句,來使子進(jìn)程異常終止:)


那么父進(jìn)程是如何獲取到子進(jìn)程的進(jìn)程退出碼的和終止信號的?
在子進(jìn)程的pcb結(jié)構(gòu)體中,有兩個變量:
int exit_code;//用于記錄當(dāng)前進(jìn)程的退出碼; int exit_signal//用于記錄當(dāng)前進(jìn)程的終止信號;
當(dāng)子進(jìn)程進(jìn)入僵尸狀態(tài),也就是子進(jìn)程被終止掉了!OS會根據(jù)子進(jìn)程終止時的退出碼和終止信號來填充exit_code和exit_signal;當(dāng)父進(jìn)程使用wait()/waitpid()系統(tǒng)調(diào)用的時候,OS就會將子進(jìn)程pcb里面的
exit_code和exit_signal存儲于status指針指向的int空間中,而status所指向的空間是屬于父進(jìn)程的,父進(jìn)程也就自然而然的拿到了子進(jìn)程的退出碼和終止信號!
父進(jìn)程在使用wait()/waitpid()的期間在做什么?
通過上面使用wait()的時候我們發(fā)現(xiàn)。即使不用sleep()讓父進(jìn)程,而是直接調(diào)用wait()接口,父進(jìn)程依舊會等待子進(jìn)程的運行結(jié)束,父進(jìn)程并沒有直接越過wait()接口而去執(zhí)行后面的指令!在子進(jìn)程運行期間,父進(jìn)程就像是卡在了wait()函數(shù)內(nèi)部,我們
把這種情況叫做阻塞等待!在子進(jìn)程運行期間,OS會把父進(jìn)程放入一個阻塞隊列中進(jìn)行等待!在此期間父進(jìn)程什么也做不了!在進(jìn)程的pcb結(jié)構(gòu)體中有一個指針指向該pcb的父進(jìn)程pcb:

當(dāng)我們的子進(jìn)程運行結(jié)束過后,OS會根據(jù)子進(jìn)程parent指向找到該子進(jìn)程的父進(jìn)程,然后將其父進(jìn)程從阻塞隊列中喚醒!然后在讓父進(jìn)程來獲取子進(jìn)程的進(jìn)程退出碼和終止信號!最后順便完成子進(jìn)程的空間釋放!
看到這里,或許我們會疑惑,父進(jìn)程在使用wait()接口等待的時候就真什么也不做,一直等著子進(jìn)程,是不是效率有點低了?能不能讓父進(jìn)程在等待的這段時間去做一點別的事情?或者說讓父進(jìn)程每隔一個時間段去看一看子進(jìn)程運行結(jié)束沒有,總之就是不要讓父進(jìn)程一直等待著子進(jìn)程?
當(dāng)然可以!當(dāng)然使用wait()接口肯定不行,因為參數(shù)太少了!只要父進(jìn)程已使用wait()接口等待,父進(jìn)程就會卡在wait()函數(shù)內(nèi)部,直到子進(jìn)程運行結(jié)束!但是我們可以使用waitpid()接口哇!
還記得waitpid()有個參數(shù)option嗎?當(dāng)option為0的時候,父進(jìn)程在等待子進(jìn)程期間就會進(jìn)入阻塞隊列中進(jìn)行等待,也就是說父進(jìn)程會卡在waitpid()內(nèi)部,知道子進(jìn)程運行結(jié)束!當(dāng)option=WNOHANG的時候父進(jìn)程不會進(jìn)入阻塞隊列中,而是檢測一下子進(jìn)程的狀態(tài),當(dāng)子進(jìn)程運行結(jié)束時waitpid()會返回子進(jìn)程的pid;當(dāng)子進(jìn)程正在運行時waitpid(),返回0;當(dāng)?shù)却r,waitpid()返回-1;至此當(dāng)父進(jìn)程使用waitpid()并且option參數(shù)設(shè)計成WNOHANG時父進(jìn)程就不會卡在waitpid()接口內(nèi)部,不管是那種情況這個“模式”下的waitpid()都是會返回一個值,我們就可以根據(jù)這個返回值來設(shè)計父進(jìn)程下一步該做什么?如果返回值是0,說明子進(jìn)程還在運行,我們可以讓父進(jìn)程去執(zhí)行一些其他代碼,然后循環(huán)回來繼續(xù)接收一下waitpid()的返回值!像這種情況叫做阻塞輪詢!
下面我們通過具體的代碼來表現(xiàn)一下阻塞輪詢:

運行結(jié)果:

從運行結(jié)果我們可以看出,當(dāng)我們把option參數(shù)設(shè)成WNOHANG過后,父進(jìn)程沒有卡在waitpid()內(nèi)部,而是和子進(jìn)程一起在運行!
現(xiàn)在我們再來對比一下,阻塞等待(也就是option參數(shù)設(shè)計成0即可):


我們可以看到在使用阻塞等待的時候,父進(jìn)程沒有去做其他事情(比如打印乘法口訣表)而是一直等待者子進(jìn)程的運行結(jié)束,當(dāng)子進(jìn)程運行結(jié)束后,父進(jìn)程通過waitpid()接收到子進(jìn)程pid,pid>0不滿足while進(jìn)入的條件!于是直接去執(zhí)行了printf子進(jìn)程運行結(jié)束的語句!
這就是阻塞等待與阻塞輪詢的區(qū)別!
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Linux bash:./xxx:無法執(zhí)行二進(jìn)制文件報錯
這篇文章主要介紹了Linux bash:./xxx:無法執(zhí)行二進(jìn)制文件報錯,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
ubuntu中python調(diào)用C/C++方法之動態(tài)鏈接庫詳解
這篇文章主要給大家介紹了關(guān)于如何在ubuntu中python調(diào)用C/C++方法之動態(tài)鏈接庫的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起看看吧2018-11-11
在Linux系統(tǒng)下如何編譯并執(zhí)行C++程序
這篇文章主要介紹了在Linux系統(tǒng)下如何編譯并執(zhí)行C++程序問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01
解決Linux系統(tǒng)yum安裝報錯Cannot find a valid base
本文介紹了如何在Linux系統(tǒng)中設(shè)置本地yum源,包括修改yum配置文件、禁用默認(rèn)網(wǎng)絡(luò)源、創(chuàng)建掛載點以及掛載鏡像文件等步驟,操作詳細(xì),適合需要離線安裝軟件或更新系統(tǒng)的用戶參考2024-09-09
Linux利用lsof/extundelete工具恢復(fù)誤刪除的文件或目錄
這篇文章主要給大家介紹了關(guān)于Linux利用lsof/extundelete工具恢復(fù)誤刪除的文件或目錄的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08

