初識(shí)Linux · 進(jìn)程等待

前言:

通過(guò)前文的學(xué)習(xí),我們已經(jīng)了解了進(jìn)程終止的概念,包括終止的三種情況以及退出碼和錯(cuò)誤碼的使用。對(duì)于退出碼,我們知道可以通過(guò)echo $?來(lái)查看,并了解了如何終止進(jìn)程。

本文將深入探討進(jìn)程等待,從三個(gè)方面進(jìn)行分析:進(jìn)程等待是什么?為什么需要等待?等待時(shí)在做什么?通過(guò)這三個(gè)方面的學(xué)習(xí),相信同學(xué)們將對(duì)linux中的進(jìn)程等待有更深刻的理解。

進(jìn)程等待是什么思考:在什么情況下會(huì)發(fā)生等待的情況?

情況實(shí)例:當(dāng)父進(jìn)程創(chuàng)建了子進(jìn)程,而父進(jìn)程任務(wù)結(jié)束時(shí),子進(jìn)程尚未結(jié)束,父進(jìn)程需要等待子進(jìn)程退出。這種情況就是等待。

那么,如果父進(jìn)程不等待,直接退出,會(huì)引發(fā)什么后果呢?

如果父進(jìn)程不等待,直接退出,那么子進(jìn)程會(huì)變成僵尸進(jìn)程,導(dǎo)致的問(wèn)題包括內(nèi)存泄漏,這是一個(gè)非常危險(xiǎn)的問(wèn)題。因此,通常情況下,父進(jìn)程需要等待子進(jìn)程退出。

bash為例,如果我們執(zhí)行的所有指令和可執(zhí)行文件bash都不回收,那么內(nèi)存將一次性耗盡,我們的機(jī)器很快就會(huì)報(bào)廢。

因此,我們得出結(jié)論:

進(jìn)程等待是指父進(jìn)程在完成自己的任務(wù)后,為了系統(tǒng)的穩(wěn)定性,需要等待子進(jìn)程退出。

為什么需要進(jìn)程等待除了考慮內(nèi)存泄漏引發(fā)的安全問(wèn)題,父進(jìn)程還需要考慮獲取子進(jìn)程的退出信息,這是一個(gè)可選的選項(xiàng),因?yàn)椴皇撬械淖舆M(jìn)程都需要父進(jìn)程獲取退出信息。

進(jìn)程等待都在做什么前面兩點(diǎn),即便是沒(méi)有學(xué)習(xí)過(guò)進(jìn)程等待的同學(xué)也應(yīng)該有所了解。今天的重點(diǎn)實(shí)際上是在等待子進(jìn)程時(shí),父進(jìn)程在做什么。

為了介紹父進(jìn)程等待時(shí)在做什么,我們不妨從一個(gè)函數(shù)開(kāi)始——waitpid:

初識(shí)Linux · 進(jìn)程等待從man 2號(hào)手冊(cè)我們可以看到,waitpid的頭文件是sys/types.h和sys/wait.h。其實(shí),到現(xiàn)在為止,一個(gè)函數(shù)需要兩個(gè)頭文件我們已經(jīng)見(jiàn)怪不怪了,比如fork函數(shù),除了types還需要unistd頭文件,這也可以說(shuō)是一種學(xué)習(xí)的里程碑。

那么,參數(shù)方面,一共有三個(gè):

pid_t waitpid(pid_t pid, int *wstatus, int options);

這三個(gè)參數(shù)分別是:pid,即父進(jìn)程要等待的子進(jìn)程的pid,因?yàn)橐粋€(gè)父進(jìn)程可能創(chuàng)建多個(gè)子進(jìn)程,需要指定等待哪個(gè)子進(jìn)程;第二個(gè)參數(shù)是輸出型參數(shù),可能直接這么說(shuō)我們不好理解,看這段代碼就知道了:

int a = 0;scanf("%d",&a);

scanf的參數(shù)就是輸出型參數(shù),即不是給OS的,是給用戶看的。第三個(gè)參數(shù)就像ls -a -l -n,這么多選項(xiàng)一樣。

這里還有一個(gè)點(diǎn),如果我們將pid參數(shù)設(shè)置為-1會(huì)怎么樣呢?——等待的就是任意進(jìn)程了。

初識(shí)Linux · 進(jìn)程等待對(duì)于返回值,我們簡(jiǎn)單先理解為如果等待成功,返回的就是子進(jìn)程的pid,否則返回-1:

代碼為:

#include <stdio.h> #include <sys> #include <unistd.h> #include <wait.h> #include <stdlib.h> void ChildRun(){     //int *p = NULL;     int cnt = 5;     while(cnt)     {         printf("I am child process, pid: %d, ppid:%d, cnt: %dn", getpid(), getppid(), cnt);         sleep(1);         cnt--;         //*p = 100;     } } int main(){     printf("I am father, pid: %d, ppid:%dn", getpid(), getppid());     pid_t id = fork();     if(id == 0)     {         // child         ChildRun();         printf("child quit ...n");         exit(123);     }     sleep(7);     // fahter     //pid_t rid = wait(NULL);     int status = 0;     pid_t rid = waitpid(id, &status, 0);     if(rid > 0)     {         printf("wait success, rid: %dn", rid);     }     else     {         printf("wait failed !n");     }     sleep(3);     printf("father quit, status: %d, child quit code : %d, child quit signal: %dn", status, (status>>8)&0xFF, status & 0x7F);     return 0; } </stdlib.h> </wait.h> </unistd.h> </sys> </stdio.h>

初識(shí)Linux · 進(jìn)程等待當(dāng)然了,對(duì)于waitpid我們應(yīng)該先了解一下wait:

初識(shí)Linux · 進(jìn)程等待wait其實(shí)就一個(gè)輸出型參數(shù),所以,如果輸出型參數(shù)設(shè)置為NULL,就是代表不關(guān)心這個(gè)子進(jìn)程,也就沒(méi)了。因此,我們了解了waitpid之后,自然就了解了wait。waitpid的參數(shù)設(shè)置為-1也就和wait等效了。

對(duì)于正常來(lái)說(shuō),子進(jìn)程進(jìn)入了一個(gè)函數(shù),通過(guò)cnt進(jìn)行計(jì)時(shí),5秒之后,子進(jìn)程結(jié)束了,變成了僵尸,父進(jìn)程還沒(méi)有結(jié)束,父進(jìn)程sleep一過(guò)開(kāi)始回收,此時(shí)就回收成功:

初識(shí)Linux · 進(jìn)程等待我們通過(guò)指令:

while :; do ps -xaj | head -1 && ps -xaj | grep main | grep -v grep; sleep 1;done

就可以親眼看到進(jìn)程從僵尸狀態(tài)變成了正常狀態(tài)。

此時(shí),細(xì)心的同學(xué)發(fā)現(xiàn)了最后打印的時(shí)候,打印了子進(jìn)程的退出碼,以及一個(gè)信號(hào)碼:

初識(shí)Linux · 進(jìn)程等待那么因?yàn)檫@里都是正常退出的,所以退出碼我們自己設(shè)置的是123,所以打印出來(lái)也是123,至于有什么含義呢,我們自己規(guī)定即可。對(duì)于信號(hào)碼來(lái)說(shuō),我們需要了解一個(gè)點(diǎn):

退出信息的本質(zhì)是什么?

退出信息本質(zhì)上是一塊有16bit位的空間,0 – 7bit位代表的是信號(hào),8 – 15bit位代表的是退出碼,退出信息實(shí)際上等于退出碼 + 信號(hào)碼,退出信息里面的core dump我們暫且不考慮,我們需要知道退出碼從哪里看?

你看代碼,代碼打印退出碼,打印信息碼的時(shí)候,我們是不是通過(guò)按位與操作獲取了某個(gè)特定區(qū)域的bit位并且打印出來(lái)了。那個(gè)操作實(shí)際上就是代表的取退出碼和取信號(hào)碼。

那么你是否會(huì)覺(jué)得退出碼和信號(hào)碼為什么只需要這么多個(gè)?

我們可以看:

初識(shí)Linux · 進(jìn)程等待拿信號(hào)舉例,一共就那么多,7個(gè)bit位還多了呢,退出碼同理可得即可。

那么這里我們注意一下,11號(hào)信號(hào)是段錯(cuò)誤,我們?nèi)绻屪舆M(jìn)程發(fā)生越界訪問(wèn):

初識(shí)Linux · 進(jìn)程等待也就是這里讓空指針修改一下:

初識(shí)Linux · 進(jìn)程等待可以看到退出碼為0,可是我們知道如果發(fā)生了越界,進(jìn)程終止實(shí)際上是被信號(hào)所殺,退出碼實(shí)際上是沒(méi)有用處的,這里的信號(hào)碼為11,我們就知道了,是OS給子進(jìn)程發(fā)送了11號(hào)信號(hào),從而導(dǎo)致了子進(jìn)程終止,但是父進(jìn)程正常等待是成功了的。

父進(jìn)程等待的時(shí)候,就一點(diǎn)事兒都不做嗎?

不完全是的,父進(jìn)程等待的時(shí)候分為兩種等待,一種是阻塞等待,一種是非阻塞等待。對(duì)于阻塞等待,就像scanf,輸入數(shù)據(jù)之后,需要等待鍵盤(pán)數(shù)據(jù)就緒,這是一種阻塞,而子進(jìn)程本質(zhì)也是軟件,父進(jìn)程實(shí)際上就是等待該軟件就緒,也就是啥也不干,就等著唄。

這是阻塞等待。

那么非阻塞等待就需要借助我們的WNOHANG,也就是第三個(gè)參數(shù)。

此時(shí)是非阻塞等待,那么父進(jìn)程一般要做的就是邊做自己的事,通過(guò)循環(huán),每過(guò)一段時(shí)間就問(wèn)子進(jìn)程是否結(jié)束沒(méi)有,此時(shí)這個(gè)過(guò)程:非阻塞等待 + 循環(huán) = 非阻塞輪詢。

至于等待的三種情況,等待成功,pid_t返回的值是大于0,==0代表的是等待成功,但是子進(jìn)程正準(zhǔn)備結(jié)束了,

那么如果子進(jìn)程是個(gè)死循環(huán)父進(jìn)程一直等待不了怎么辦,這就是OS的事兒了。

非阻塞呢,就是將第三個(gè)參數(shù)設(shè)置為WNOHANG即可。

感謝閱讀!

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊12 分享