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)建成功,對(duì)于子進(jìn)程返回0,對(duì)于父進(jìn)程返回子進(jìn)程的pid;
fork返回值的認(rèn)識(shí)
可以移步至我的另一篇文章: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)程,通常都會(huì)給父進(jìn)程設(shè)置一個(gè)"進(jìn)程上限";
fork函數(shù)的應(yīng)用場(chǎng)景
1、一個(gè)父進(jìn)程希望復(fù)制自己,使父子進(jìn)程同時(shí)執(zhí)行不同的代碼段。例如:父進(jìn)程等待客服端請(qǐng)求,生成子進(jìn)程來處理請(qǐng)求;
2、一個(gè)進(jìn)程需要執(zhí)行不同的程序。例如:子進(jìn)程從fork函數(shù)返回后,調(diào)用Add()接口,父進(jìn)程去執(zhí)行Sub()接口;
進(jìn)程終止
進(jìn)程終止的三種方式
1、程序正常運(yùn)行,最后結(jié)果正確;
2、程序正常運(yùn)行,最后結(jié)果錯(cuò)誤;
3、程序異常終止!(比如:出現(xiàn)除以0、對(duì)空指針解引用、kill -9 命令殺掉進(jìn)程等等)這本質(zhì)上:就是OS向該進(jìn)程發(fā)送了一種信號(hào)!
進(jìn)程退出碼
進(jìn)程退出碼存在的前提就是程序正常運(yùn)行完畢!在其他情況下,進(jìn)程退出碼沒有意義!
那么什么是進(jìn)程退出碼?
一個(gè)程序是從main函數(shù)開始的吧,那么終止也是終止與main函數(shù)對(duì)吧,main函數(shù)最后是不是有個(gè)return 0;這個(gè)0就是進(jìn)程退出碼;
return 0,表示該進(jìn)程正常運(yùn)行,且運(yùn)行結(jié)果正確;
return 其他數(shù)據(jù),表示該進(jìn)程正常運(yùn)行,但是運(yùn)行結(jié)果錯(cuò)誤,那么到底是為什么錯(cuò)誤呢?作為父進(jìn)程需要知道原因,我們此時(shí)return 的數(shù)字就表示這個(gè)錯(cuò)誤原因,這個(gè)數(shù)字也是進(jìn)程退出碼!
當(dāng)然不止main函數(shù)的返回值是進(jìn)程退出碼,exit()/_exit()函數(shù)的參數(shù)都是進(jìn)程退出碼!
我們可以理解為exit()/-exit()//函數(shù)的參數(shù)等價(jià)于return 數(shù)字;
return 數(shù)字只能在main函數(shù)內(nèi)部進(jìn)行返回,但是如果我們?cè)趍ain函數(shù)以外的地方,遇到了不合理的地方想要提前終止進(jìn)程(比如利用malloc開辟空間失敗,我們就可以提前終止進(jìn)程),我們就可以利用exit()/_exit(),來提前終止掉該進(jìn)程!
exit()/_exit()//的參數(shù)就相當(dāng)于main函數(shù)的返回值!0:表示正常運(yùn)行結(jié)束,結(jié)果運(yùn)行正確;其他數(shù)字表示正常運(yùn)行結(jié)束,結(jié)果運(yùn)行錯(cuò)誤!
那么exit()/與_exit()看起來長得很像啊,那么他們有區(qū)別嗎?
當(dāng)然有,我們可以通過下面一段測(cè)試代碼來看待結(jié)果:
分析:printf在輸出“Hello World!”的時(shí)候,會(huì)首先將字符串發(fā)送到“緩沖區(qū)”,但是由于字符串末尾沒有’\n’,printf后面沒有輸入語句,printf語句后面沒有fflush強(qiáng)制刷新緩沖區(qū)!我們并不會(huì)立即看到字符串,在休眠2s過后進(jìn)程調(diào)用exit提前終止掉了,也就是程序結(jié)束了,程序結(jié)束是會(huì)刷新緩沖區(qū),也就是說我們會(huì)看到“Hello World!”,那么結(jié)果到底是不是?我們運(yùn)行一下:
結(jié)果的確是這樣!
那么如果我們?cè)賮砝胈exit()函數(shù)來提前終止程序呢?
我們會(huì)發(fā)現(xiàn)什么也沒有?。?!
嗯?為什么會(huì)這樣?
首先我們的字符串會(huì)被先發(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)程之前,會(huì)做一些工作,比如:執(zhí)行用戶定義的清理
函數(shù)、刷新緩沖區(qū)、關(guān)閉流等;但是_exit()沒有這么多前戲,就是純粹的終止進(jìn)程!它不會(huì)刷新緩存區(qū),因此我們?cè)谡{(diào)用_exit()結(jié)束進(jìn)程的時(shí)候,沒有刷新緩沖區(qū),直接就結(jié)束掉進(jìn)程了,我們的字符串也就一直停留在緩沖區(qū),沒有機(jī)會(huì)輸出到屏幕上!
同時(shí)我們的return 數(shù)字;準(zhǔn)確上來說等價(jià)于exit();因此在平常的使用中我們更推薦使用exit();
為什么需要進(jìn)程退出碼?
子進(jìn)程是被父進(jìn)程創(chuàng)建的,父進(jìn)程創(chuàng)建子進(jìn)程是為了讓子進(jìn)程幫助自己完成某樣任務(wù),那么子進(jìn)程運(yùn)行結(jié)束了,子進(jìn)程到底把這個(gè)任務(wù)完成的怎么樣?是好?還是壞?作為父進(jìn)程是需要知道的!進(jìn)程退出碼就是用于告訴父進(jìn)程它交給子進(jìn)程完成的任
務(wù)完成的怎么樣的信息!
父進(jìn)程再把這個(gè)信息報(bào)告給我們用戶!
但是對(duì)于我們用戶來說,進(jìn)程退出碼就是一個(gè)單純的數(shù)字,我們作為人類只看的懂字符串!因此我們需要一樣表來映射進(jìn)程退出碼的對(duì)應(yīng)信息!在C語言庫中,給我們提供了一張這樣的映射表,我們可以通過strerror()函數(shù)來訪問這種表!
參數(shù): 錯(cuò)誤號(hào);
返回值: 錯(cuò)誤號(hào)對(duì)應(yīng)的字符串首地址!
通過下面一段程序,我們來輸出一下“錯(cuò)誤表”中對(duì)應(yīng)的錯(cuò)誤信息:
運(yùn)行結(jié)果:
我們可以看到“錯(cuò)誤表”里面總共記錄了134個(gè)錯(cuò)誤信息;當(dāng)然這只是Linux環(huán)境下的,Window環(huán)境下可能會(huì)不一樣!這張“錯(cuò)誤表”雖然是C語言庫給我們提供的,但是我們沒必要一定要遵守這張表,我們可以映射自己的“錯(cuò)誤表”;當(dāng)然這些錯(cuò)誤信息最多也就255個(gè);
如何查看進(jìn)程退出碼?
好,現(xiàn)在我知道了什么是進(jìn)程退出碼和為什么要有進(jìn)程退出碼,那么能不能讓我們見一見進(jìn)程退出碼?
當(dāng)然可以:查看進(jìn)程退出碼用命令echo $?
//這個(gè)命令是查看最近一次進(jìn)程運(yùn)行結(jié)束的退出碼!
比如現(xiàn)在我們故意將我們的進(jìn)程退出碼設(shè)置"特殊"一點(diǎn),方便我們觀察:
當(dāng)然我們也可以用于產(chǎn)看系統(tǒng)指令的進(jìn)程退出碼,比如:
我們可以用這個(gè)進(jìn)程退出碼去查一下C庫的“錯(cuò)誤表”:
發(fā)現(xiàn)ls的進(jìn)程退出碼(2)的確實(shí)對(duì)應(yīng)的“No such file or directory”的錯(cuò)誤信息,表示ls命令采用了C庫的“錯(cuò)誤表”;
當(dāng)然也有一些是命令是不遵循的,比如:
查看C庫“錯(cuò)誤表”:
我們發(fā)現(xiàn)是匹配不上的,說明kill命令并沒有采用C庫里面的錯(cuò)誤信息,而是采用了自己設(shè)計(jì)的!
進(jìn)程等待
為什么要進(jìn)行進(jìn)程等待?
進(jìn)程等待是誰等?等誰?
答案是:父進(jìn)程等,等子進(jìn)程!
明白了這一點(diǎn),我們?cè)賮碚劄槭裁匆M(jìn)行進(jìn)程等待?
1、子進(jìn)程不可能直接就推出了,父進(jìn)程需要獲取子進(jìn)程的退出碼,來獲知交給子進(jìn)程的任務(wù)完成的怎么樣;
2、因此,子進(jìn)程在終止過后會(huì)進(jìn)入僵尸狀態(tài),以此來讓父進(jìn)程獲取子進(jìn)程的退出碼;但是處于僵尸狀態(tài)有一個(gè)弊端:僵尸不死不滅,就算是用kill -9
命令也無法將其殺死!因?yàn)樗旧砭鸵呀?jīng)終止了,沒辦法殺死一個(gè)已經(jīng)死去的進(jìn)程!但是,該進(jìn)程占據(jù)的空間還沒有釋放,如果沒有人來釋放掉該空間的話,就會(huì)造成內(nèi)存泄漏!為此父進(jìn)程在獲取到子進(jìn)程的退出碼過后,會(huì)順便將子進(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();來實(shí)現(xiàn)!
在具體使用之前,我們需要先了解一下這兩個(gè)系統(tǒng)調(diào)用:
pid_t wait(int*status);
Linux下包含頭文件:sys/types.h和sys/wait.h
參數(shù): 輸出型參數(shù)用于獲取進(jìn)程退出碼和信號(hào)(如果程序正常運(yùn)行結(jié)束的話,信號(hào)是0);當(dāng)然如果我們不關(guān)心進(jìn)程的退出碼和信號(hào)的話,我們可以將其設(shè)置為NULL;
返回值: 等待成功:返回等待進(jìn)程的pid;等待失敗(比如父進(jìn)程沒有子進(jìn)程),返回-1;
下面我們就先來實(shí)際用一下wait()系統(tǒng)調(diào)用:
運(yùn)行結(jié)果:
實(shí)際上我們不需要使用sleep()函數(shù),當(dāng)父進(jìn)程調(diào)用wait函數(shù)時(shí),父進(jìn)程也會(huì)自動(dòng)的等待子進(jìn)程運(yùn)行完畢!相當(dāng)于在子進(jìn)程運(yùn)行的這段期間,父進(jìn)程相當(dāng)于卡在了wait函數(shù)內(nèi)部!這叫做阻塞等待!父進(jìn)程會(huì)被OS放入一個(gè)等待隊(duì)列中進(jìn)行等待子進(jìn)程的運(yùn)行結(jié)束!
靜態(tài)圖片不好演示父進(jìn)程等待的這個(gè)過程,讀者可以自行驗(yà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時(shí)則處理最先處于僵尸狀態(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)程還沒運(yùn)行結(jié)束,返回0;
- 等待失?。ㄗ舆M(jìn)程不存在),返回-1,
具體用法如下:
現(xiàn)在如果我們關(guān)心子進(jìn)程的退出信息呢?
那么我們的status參數(shù)就不能在傳空指針了,我們需要傳遞一個(gè)int類型的指針過去,這個(gè)int類型的指針必須指向一塊有效的空間!相當(dāng)于:
也就是說status必須指向一個(gè)int類型的空間,這個(gè)int類型的空間用來存儲(chǔ)子進(jìn)程的退出碼+信號(hào)信息!
是的!你沒聽錯(cuò)!這個(gè)int空間會(huì)被用來存儲(chǔ)兩種信息!
- int 類型有4個(gè)字節(jié)對(duì)吧!而我們的退出碼也就0~255種情況一個(gè)字節(jié)就能存儲(chǔ)起來,我們的信號(hào)信息也就幾十個(gè)情況,也能用一個(gè)字節(jié)存儲(chǔ)起來!
- status不能簡單的當(dāng)作整形來看待,可以當(dāng)作位圖來看待,具體細(xì)節(jié)如下圖(只研究status低16比特位):
那么我們?nèi)绾文贸龅竭M(jìn)程退出碼和終止信號(hào)?
我們可以利用(status指向的整形>>8)&0xFF的方式來拿出進(jìn)程退出碼!(status&0x7F)的方式來拿出終止信號(hào);
下面我們來具體演示一下:
我們?cè)O(shè)置的子進(jìn)程的退出碼是107,如果子進(jìn)程是正常運(yùn)行結(jié)束的話,那么終止信號(hào)也就是0;
那么結(jié)果是不是呢?
答案確實(shí)是這樣?
當(dāng)然如果程序是異常終止的話,那么信號(hào)也是其它數(shù)字,這時(shí)候在看進(jìn)程退出碼已經(jīng)沒有任何意義了!
(我們這里故意制造一個(gè),子進(jìn)程除以0的語句,來使子進(jìn)程異常終止:)
那么父進(jìn)程是如何獲取到子進(jìn)程的進(jìn)程退出碼的和終止信號(hào)的?
在子進(jìn)程的pcb結(jié)構(gòu)體中,有兩個(gè)變量:
int exit_code;//用于記錄當(dāng)前進(jìn)程的退出碼; int exit_signal//用于記錄當(dāng)前進(jìn)程的終止信號(hào);
當(dāng)子進(jìn)程進(jìn)入僵尸狀態(tài),也就是子進(jìn)程被終止掉了!OS會(huì)根據(jù)子進(jìn)程終止時(shí)的退出碼和終止信號(hào)來填充exit_code和exit_signal;當(dāng)父進(jìn)程使用wait()/waitpid()系統(tǒng)調(diào)用的時(shí)候,OS就會(huì)將子進(jìn)程pcb里面的
exit_code和exit_signal存儲(chǔ)于status指針指向的int空間中,而status所指向的空間是屬于父進(jìn)程的,父進(jìn)程也就自然而然的拿到了子進(jìn)程的退出碼和終止信號(hào)!
父進(jìn)程在使用wait()/waitpid()的期間在做什么?
通過上面使用wait()的時(shí)候我們發(fā)現(xiàn)。即使不用sleep()讓父進(jìn)程,而是直接調(diào)用wait()接口,父進(jìn)程依舊會(huì)等待子進(jìn)程的運(yùn)行結(jié)束,父進(jìn)程并沒有直接越過wait()接口而去執(zhí)行后面的指令!在子進(jìn)程運(yùn)行期間,父進(jìn)程就像是卡在了wait()函數(shù)內(nèi)部,我們
把這種情況叫做阻塞等待!在子進(jìn)程運(yùn)行期間,OS會(huì)把父進(jìn)程放入一個(gè)阻塞隊(duì)列中進(jìn)行等待!在此期間父進(jìn)程什么也做不了!在進(jìn)程的pcb結(jié)構(gòu)體中有一個(gè)指針指向該pcb的父進(jìn)程pcb:
當(dāng)我們的子進(jìn)程運(yùn)行結(jié)束過后,OS會(huì)根據(jù)子進(jìn)程parent指向找到該子進(jìn)程的父進(jìn)程,然后將其父進(jìn)程從阻塞隊(duì)列中喚醒!然后在讓父進(jìn)程來獲取子進(jìn)程的進(jìn)程退出碼和終止信號(hào)!最后順便完成子進(jìn)程的空間釋放!
看到這里,或許我們會(huì)疑惑,父進(jìn)程在使用wait()接口等待的時(shí)候就真什么也不做,一直等著子進(jìn)程,是不是效率有點(diǎn)低了?能不能讓父進(jìn)程在等待的這段時(shí)間去做一點(diǎn)別的事情?或者說讓父進(jìn)程每隔一個(gè)時(shí)間段去看一看子進(jìn)程運(yùn)行結(jié)束沒有,總之就是不要讓父進(jìn)程一直等待著子進(jìn)程?
當(dāng)然可以!當(dāng)然使用wait()接口肯定不行,因?yàn)閰?shù)太少了!只要父進(jìn)程已使用wait()接口等待,父進(jìn)程就會(huì)卡在wait()函數(shù)內(nèi)部,直到子進(jìn)程運(yùn)行結(jié)束!但是我們可以使用waitpid()接口哇!
還記得waitpid()有個(gè)參數(shù)option嗎?當(dāng)option為0的時(shí)候,父進(jìn)程在等待子進(jìn)程期間就會(huì)進(jìn)入阻塞隊(duì)列中進(jìn)行等待,也就是說父進(jìn)程會(huì)卡在waitpid()內(nèi)部,知道子進(jìn)程運(yùn)行結(jié)束!當(dāng)option=WNOHANG的時(shí)候父進(jìn)程不會(huì)進(jìn)入阻塞隊(duì)列中,而是檢測(cè)一下子進(jìn)程的狀態(tài),當(dāng)子進(jìn)程運(yùn)行結(jié)束時(shí)waitpid()會(huì)返回子進(jìn)程的pid;當(dāng)子進(jìn)程正在運(yùn)行時(shí)waitpid(),返回0;當(dāng)?shù)却r(shí),waitpid()返回-1;至此當(dāng)父進(jìn)程使用waitpid()并且option參數(shù)設(shè)計(jì)成WNOHANG時(shí)父進(jìn)程就不會(huì)卡在waitpid()接口內(nèi)部,不管是那種情況這個(gè)“模式”下的waitpid()都是會(huì)返回一個(gè)值,我們就可以根據(jù)這個(gè)返回值來設(shè)計(jì)父進(jìn)程下一步該做什么?如果返回值是0,說明子進(jìn)程還在運(yùn)行,我們可以讓父進(jìn)程去執(zhí)行一些其他代碼,然后循環(huán)回來繼續(xù)接收一下waitpid()的返回值!像這種情況叫做阻塞輪詢!
下面我們通過具體的代碼來表現(xiàn)一下阻塞輪詢:
運(yùn)行結(jié)果:
從運(yùn)行結(jié)果我們可以看出,當(dāng)我們把option參數(shù)設(shè)成WNOHANG過后,父進(jìn)程沒有卡在waitpid()內(nèi)部,而是和子進(jìn)程一起在運(yùn)行!
現(xiàn)在我們?cè)賮韺?duì)比一下,阻塞等待(也就是option參數(shù)設(shè)計(jì)成0即可):
我們可以看到在使用阻塞等待的時(shí)候,父進(jìn)程沒有去做其他事情(比如打印乘法口訣表)而是一直等待者子進(jìn)程的運(yùn)行結(jié)束,當(dāng)子進(jìn)程運(yùn)行結(jié)束后,父進(jìn)程通過waitpid()接收到子進(jìn)程pid,pid>0不滿足while進(jìn)入的條件!于是直接去執(zhí)行了printf子進(jìn)程運(yùn)行結(jié)束的語句!
這就是阻塞等待與阻塞輪詢的區(qū)別!
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Linux bash:./xxx:無法執(zhí)行二進(jìn)制文件報(bào)錯(cuò)
這篇文章主要介紹了Linux bash:./xxx:無法執(zhí)行二進(jìn)制文件報(bào)錯(cuò),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03ubuntu中python調(diào)用C/C++方法之動(dòng)態(tài)鏈接庫詳解
這篇文章主要給大家介紹了關(guān)于如何在ubuntu中python調(diào)用C/C++方法之動(dòng)態(tài)鏈接庫的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起看看吧2018-11-11在Linux系統(tǒng)下如何編譯并執(zhí)行C++程序
這篇文章主要介紹了在Linux系統(tǒng)下如何編譯并執(zhí)行C++程序問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01解決Linux系統(tǒng)yum安裝報(bào)錯(cuò)Cannot find a valid base
本文介紹了如何在Linux系統(tǒng)中設(shè)置本地yum源,包括修改yum配置文件、禁用默認(rèn)網(wǎng)絡(luò)源、創(chuàng)建掛載點(diǎn)以及掛載鏡像文件等步驟,操作詳細(xì),適合需要離線安裝軟件或更新系統(tǒng)的用戶參考2024-09-09Linux利用lsof/extundelete工具恢復(fù)誤刪除的文件或目錄
這篇文章主要給大家介紹了關(guān)于Linux利用lsof/extundelete工具恢復(fù)誤刪除的文件或目錄的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08