目錄
前言
一、進程等待
二、如何進行進程等待
1.wait
2.waitpid
2.1第二個參數?
2.2第三個參數?
3.?等待多個進程
三、為什么不用全局變量獲取子進程的退出信息
前言
前面我們花了大量的時間去學習進程的退出,退出并不難,但更深入的學習能為本章進程等待打好基礎,因此沒看過的小伙伴可以先學習進程退出。
一、進程等待
之前講過,子進程退出,父進程一直在運行,不對子進程進行回收,就可能造成‘僵尸進程’的問題,進而造成內存泄漏。
另外,進程一旦變成僵尸狀態,那就刀槍不入,“殺人不眨眼”的kill -9 也無能為力,因為誰也沒有辦法 殺死一個已經死去的進程。
父進程派給子進程的任務完成的如何,我們需要知道。如子進程運行完成,結果對還是不對, 或者是否正常退出。
父進程通過進程等待的方式,可以獲取子進程退出的信息。(雖然不是一定要獲取,但是得有這個功能)
二、如何進行進程等待
1.wait
我們看看2號手冊中的wait函數,他可以等待任意一個子進程的退出,參數是int類型的指針,等待成功返回子進程的pid,失敗返回-1。
我們使用如下代碼進行進程等待,這里wait的參數先給NULL,代表不關心子進程退出的狀態(后續會再提到)。子進程運行5秒后變成僵尸狀態,父進程先休眠10秒再去調用wait函數。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>void Work()
{int cnt = 5;while(cnt){printf("我是子進程, pid: %d, ppid: %d, cnt: %d\n",getpid(),getppid(),cnt--);sleep(1);}
}int main()
{pid_t id = fork();if(id == 0){//childWork();exit(0);}else{sleep(10);//fatherpid_t rid = wait(NULL);if(rid == id){printf("等待成功,pid: %d\n",getpid());}}return 0;
}
我們寫了一個腳本來監控進程的運行情況,代碼如下(注意myprocess是我設置的進程名)
while :; do ps ajx | head -1 && ps ajx | grep myprocess | grep -v "grep"; sleep 1; echo "###################"; done
結果發現0-5秒中,父子進程正常運行,5-10秒中,子進程變成了僵尸狀態,父進程此時在sleep,并沒有回收子進程,10秒后,父進程sleep結束,wait函數等到了子進程,于是將子進程回收了,同時父進程也運行完畢。?
由此我們可以得知:
進程等待能回收子進程僵尸狀態。
還有一個結論,在父進程進行等待的時候,如果子進程還沒有處理完,那么父進程必須在wait上進行阻塞等待,直到子進程僵尸,wait自動回收,再繼續執行后續代碼。這可以通過打印的方式查看。就類似于scanf需要等待用戶輸入一樣,用戶不輸入就一直在這里阻塞著,直到輸入后才繼續往后執行。
一般而言,父子進程誰先運行我們不知道,但能知道一般都是父進程最后退出,多進程由父進程發起,也由父進程統一回收
2.waitpid
wait是等待任意一個子進程,而waitpid可以等待指定的那一個,第一個參數傳等待子進程的pid代表等待這個進程,傳-1代表等待任意進程。?第二個參數和wait的參數一樣,第三個參數也先不管,設置為0代表默認阻塞等待。
將上面的代碼從wait修改為waitpid,因為我們只fork了一次,只創建了一個子進程,因此如下修改即可。
2.1第二個參數?
重點我們得講解一下第二個參數 status ,他是輸出型參數,我們可以隨便定義一個int變量,將變量的值傳遞給第二個參數,waitpid會將子進程退出碼和信號寫到這個變量里。
這里我們將子進程的退出碼設置為10,看看打印出來的status值為多少。
發現status為2560,這似乎不像退出碼。他是通過下面這個圖片的方式得來的,int整形32位,只用低16位,其中高8位代表退出碼,低8位代表終止信號,
正常終止看高八位即可,因為未收到信號,因此低8位為0。
被信號所殺,看低八位,其中第7位不看,他代表core dump標志(暫時不考慮),只看0-6位。?
那么2560的二進制為 0000 1010 0000 0000?如果右移8位,也就是只看高8位,即0000 1010,即為10,我們退出碼也就是10。
公式為? :? *status = (exit_code<<8)| exit_signal;
status不能直接使用,如下經過右移操作和與操作,就可以得到準確的退出碼和信號了。
執行一下,沒有問題?
小總結一下
- 當一個進程異常了(收到信號) ,那么退出碼就無意義了
- 通過信號碼是否非零,來判斷是否收到信號
- 手動殺死子進程,也能得到相應信號
雖然我們會通過位運算來得到退出碼與信號,但這樣也比較麻煩,linux系統提供了如下兩個接口幫我們處理status。
WIFEXITED(status): 若為正常終止子進程返回的狀態,則為真。(查看進程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子進程退出碼。(查看進程的退出碼)
如下,查看是否正常退出,退出碼為多少。?
結果也符合預期?
2.2第三個參數?
0:即阻塞等待
WNOHANG::若pid指定的子進程沒有結束,則waitpid()函數返回0,不予以等待。若正常結束,則返回該子進程的ID。(非阻塞式等待)
講個小故事:
????????張三約翠花去電競酒店打麻將, 他已經到翠花樓下了,現在在等待翠花先來一起出發。
????????此時張三有兩個方法,一個是打電話,詢問翠花在干嘛,什么時候下樓,打完翠花說等一下,她化個妝,于是張三就在樓下開一把金鏟鏟之戰,過了半個小時又打,翠花說在穿鞋了等一小下,于是張三掛斷電話去刷抖音,過一會再打電話,翠花說已經下樓了,剛剛準備出門又上了個廁所,張三沒有什么脾氣,誰叫我想約人家呢,于是掛斷電話,又去看看淘寶,要買點什么,最后再打電話,翠花此時終于到達了,于是兩個人開開心心的去打麻將了。
????????另一個方法也是打電話,詢問翠花在干嘛,什么時候下樓,翠花也說等一下,還在化妝,但是張三今天電話不掛,就一直等翠花,時刻知道翠花在干嘛,直到翠花下樓一起去打麻將。
在這個故事中
張三:父進程
翠花:子進程
打電話:調用系統接口
第一個方法:等待的條件不滿足,wait/waitpid不阻塞,而是立即返回!可以做自己占據時間并不多的事情。這是非阻塞式調用,即非阻塞+輪詢方案進行進程等待,該方案往往要進行重復調用。返回值>0等待成功,子進程已退出;返回值==0;等待成功,子進程未退出,返回值<0等待失敗
第二個方法:翠花不結束,電話不掛機,即阻塞式調用。子進程不退出,wait/waitpid不返回。
代碼如下,waitpid第三個參數為 WNOHANG?借此觀看非阻塞輪詢等待。?
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>void Work(int number)
{printf("我是子進程, pid: %d, ppid: %d, number: %d\n",getpid(),getppid(),number);
}int main()
{pid_t id = fork();if(id == 0){//childint number = 5;while(number){Work(number);number--;sleep(1);}exit(10);}//father int status = 0;while(1){pid_t rid = waitpid(id,&status,WNOHANG);if(rid>0){//等待成功,子進程退出了printf("等待子進程成功,子進程退出碼: %d,退出信號: %d\n",WEXITSTATUS(status),status&0x7F);break;}else if(rid == 0){//等待成功,但子進程沒有退出printf("等待成功,子進程還沒推出,父進程做其他事情去了\n");sleep(2);}else{printf("等待失敗\n");break;}}return 0;
}
運行結果如下,父進程間歇性詢問子進程是否完成,沒完成就做自己的事情,待會再來詢問。
3.?等待多個進程
我們使用for循壞來fork多個進程,同時給每個進程編號,創建順序從0-9。waitpid第一個參數為-1,代表等待任意的子進程。
雖然我們也可以用數組的方式,將子進程的pid放到數組里,但是這樣就只能一個一個進程的等待,比如最先會等待退出碼為0進程,如果該進程不結束,父進程會一直等待,那么后續的進程永遠不會被回收,就會造成內存泄漏的問題。
1: myprocess.c ? ? ?? buffers
#include<sys/types.h>
#include<sys/wait.h>void Work(int number)
{int cnt = 2;while(cnt){printf("我是子進程, pid: %d, ppid: %d, cnt: %d, number: %d\n",getpid(),getppid(),cnt--,number);sleep(1);}
}const int n = 10;int main()
{int i = 0;for(;i<n;i++){pid_t id = fork();if(id == 0){//childWork(i);exit(i);}}//fork的子進程已近全部退出了,下面是父進程執行的代碼for(i=0;i<n;i++){int status;pid_t rid = waitpid(-1,&status,0); //-1:任意一個子進程退出 if(rid>0) {printf("等待子進程 %d 成功, 退出碼: %d\n",rid, WEXITSTATUS(status));}}return 0;
}
看看運行的情況,發現調度運行與終止都是沒有規律的,誰先誰后我們不確定,我們只知道肯定是父進程先創建并最后退出。
三、為什么不用全局變量獲取子進程的退出信息
剛剛我們提到, 可以用數組獲取子進程的pid,然后傳值進行等待,雖然效果不一定很好,但這也算是一個解決辦法,為什么不用全局變量獲取子進程的退出信息,而是采用寫入的方式進行傳參獲取呢?
這是因為進程之間具有獨立性,父進程無法直接獲取子進程的退出信息,比如status我們設置為0,父子進程看到的status值就都為0,此時我們獲取到了子進程的退出碼,將子進程的退出碼寫入status變量,此時會發生寫時拷貝,子進程看到的是我自己寫的新值,而父進程看到的還是0。父子進程代碼共享,但數據不一定相同。
而父進程通過fork,返回的id是子進程的pid,子進程返回的id為0,已經寫時拷貝過了,因此可以獲取。