一、進(jìn)程的創(chuàng)建fork()函數(shù)
由fork創(chuàng)建的新進(jìn)程被稱為子進(jìn)程(child process)。該函數(shù)被調(diào)用一次,但返回兩次。兩次返回的區(qū)別是子進(jìn)程的返回值是0,而父進(jìn)程的返回值則是 新子進(jìn)程的進(jìn)程ID。將子進(jìn)程ID返回給父進(jìn)程的理由是:因?yàn)橐粋€(gè)進(jìn)程的子進(jìn)程可以多于一個(gè),所有沒(méi)有一個(gè)函數(shù)使一個(gè)進(jìn)程可以獲得其所有子進(jìn)程的進(jìn)程ID。fork使子進(jìn)程得到返回值0的理由是:一個(gè)進(jìn)程只會(huì)有一個(gè)父進(jìn)程,所以子進(jìn)程總是可以調(diào)用getppid以獲得其父進(jìn)程的進(jìn)程ID(進(jìn)程 ID 0總是由交換進(jìn)程使用,所以一個(gè)子進(jìn)程的進(jìn)程ID不可能為0)。
子進(jìn)程和父進(jìn)程繼續(xù)執(zhí)行fork之后的指令。子進(jìn)程是父進(jìn)程的復(fù)制品。例如,子進(jìn)程獲得父進(jìn)程數(shù)據(jù)空間、堆和棧的復(fù)制品。注意,這是子進(jìn)程擁有的拷貝。父、子進(jìn)程并共享這些存儲(chǔ)部分。如果正文段是只讀的,則父、子進(jìn)程共享正文段。 現(xiàn)在很多的實(shí)現(xiàn)并不做一個(gè)父進(jìn)程數(shù)據(jù)段和堆的完全拷貝,因?yàn)樵趂ork之后經(jīng)常跟隨著exec。作為替代,使用了寫時(shí)復(fù)制(copy-on-write,cow)的技術(shù)。這些區(qū)域由父、子進(jìn)程共享,而且內(nèi)核將他們的存取許可權(quán)改變位只讀的。如果有進(jìn)程試圖修改這些區(qū)域,則內(nèi)核包異常,典型的是虛存系統(tǒng)中的“頁(yè)”,做一個(gè)拷貝。
實(shí)例1:
#include <stdio.h> #include <stdlib.h> #include <unistd.h>
int glob = 6; char buf[] = "a write to stdout\n";
int main() { int var; int pid;
var = 88;
if(write(STDOUT_FILENO,buf,sizeof(buf) -1) != sizeof(buf) -1) { perror("fail to write"); return -1; }
printf("before fork\n");
if((pid = fork()) < 0) { perror("fail to fork"); return -1; }else if(pid == 0) { glob ++; var ++; }else{ sleep(2); }
printf("pid = %d,glob = %d,var = %d\n",getpid(),glob,var); exit(0); }
運(yùn)行結(jié)果: 從上面可以看出,因?yàn)樽舆M(jìn)程和父進(jìn)程擁有獨(dú)立的物理內(nèi)存空間,所以當(dāng)子進(jìn)程對(duì)拷貝來(lái)的數(shù)據(jù)做修改的時(shí)候,并沒(méi)有影響到父進(jìn)程。
注意: 1.一般來(lái)說(shuō),fork之后父進(jìn)程先執(zhí)行還是子進(jìn)程先執(zhí)行是不確定的。這取決于內(nèi)核所使用的調(diào)度算法。 2.從上面可以看到兩次的運(yùn)行結(jié)果不一樣。我們知道write函數(shù)是不帶緩存的。因?yàn)樵趂ork之前調(diào)用write,所以其數(shù)據(jù)寫到標(biāo)準(zhǔn)輸出一次。但是,標(biāo)準(zhǔn) I/O庫(kù)是帶緩存的。如果標(biāo)準(zhǔn)輸出連到終端設(shè)備,則它是行緩存的,否則它是全緩存的。當(dāng)以交互方式運(yùn)行該程序時(shí),只得到printf輸出的行一次,其原因是標(biāo)準(zhǔn)輸出緩存由新行符刷新。但是當(dāng)將標(biāo)準(zhǔn)輸出重新定向到一個(gè)文件時(shí),卻得到printf輸出行兩次。其原因是,在fork之前調(diào)用了printf一次,當(dāng)調(diào)用fork時(shí),該行數(shù)據(jù)仍在緩存中,然后在父進(jìn)程數(shù)據(jù)空間復(fù)制到子進(jìn)程中時(shí),該緩存數(shù)據(jù)也被復(fù)制到子進(jìn)程中。于是那時(shí)父、子進(jìn)程各自有了帶該行內(nèi)容的緩存。在exit之前的第二個(gè)printf將其數(shù)據(jù)添加到現(xiàn)存的緩存中。當(dāng)每個(gè)進(jìn)程終止時(shí),其緩存中的內(nèi)容被寫到相應(yīng)文件中。
實(shí)例 2:
#include <stdio.h> #include <stdlib.h> #include <unistd.h>
int glob = 6;
int main() { int var; int pid;
var = 88;
printf("father:\n"); printf("&glob = %p\n",&glob); printf("&var = %p\n",&var); printf("__________________________________\n");
if((pid = fork()) < 0) { perror("fail to fork"); return -1;
}else if(pid == 0) { printf("child var value not change\n:"); printf("&glob = %p\n",&glob); printf("&var = %p\n",&var); glob ++; var ++;
printf("__________________________________\n"); printf("child var value change:\n"); printf("&glob = %p\n",&glob); printf("&var = %p\n",&var); }
exit(0); }
運(yùn)行結(jié)果如下:
從上面可以看出,根據(jù)copy-on-write的思想,在子進(jìn)程中,改變父進(jìn)程的數(shù)據(jù)時(shí),會(huì)先 復(fù)制父進(jìn)程的數(shù)據(jù)修然后再改,從而達(dá)到子進(jìn)程對(duì)數(shù)據(jù)的修改不影響父進(jìn)程。但是我們發(fā)現(xiàn),復(fù)制的前后,其值的地址都是一樣的。為什么呢?子進(jìn)程拷貝的時(shí)候也拷貝了父進(jìn)程的虛擬內(nèi)存"頁(yè)",這樣他們的虛擬地址都一樣,但是對(duì)應(yīng)不同的物理內(nèi)存空間。
二、copy-on-write工作原理
假設(shè)進(jìn)程A創(chuàng)建子進(jìn)程B,之后進(jìn)程A和進(jìn)程B共享A的地址空間,同時(shí)該地址空間中的頁(yè)面全部被標(biāo)識(shí)為寫保護(hù)。此時(shí)B若寫address的頁(yè)面,由于寫保護(hù)的原因會(huì)引起寫異常,在異常處理中,內(nèi)核將address所在的那個(gè)寫保護(hù)頁(yè)面復(fù)制為新的頁(yè)面,讓B的address頁(yè)表項(xiàng)指向該新的頁(yè)面,新頁(yè)面可寫。而A的address頁(yè)表項(xiàng)依然指向那個(gè)寫保護(hù)的頁(yè)面。然后當(dāng)B在訪問(wèn)address時(shí)就會(huì)直接訪問(wèn)新的頁(yè)面了,不會(huì)在訪問(wèn)到哪個(gè)寫保護(hù)的頁(yè)面。當(dāng)A試圖寫address所在的頁(yè)面時(shí),由于寫保護(hù)的原因此時(shí)也會(huì)引起異常,在異常處理中,內(nèi)核如果發(fā)現(xiàn)該頁(yè)面只有一個(gè)擁有進(jìn)程,此種情況下也就是A,則直接對(duì)該頁(yè)面取消寫保護(hù),此后當(dāng)A再訪問(wèn)address時(shí)不會(huì)在有寫保護(hù)錯(cuò)誤了。如果此時(shí)A又創(chuàng)建子進(jìn)程C,則該address所在的頁(yè)面又被設(shè)置為寫保護(hù),擁有進(jìn)程A和C,同時(shí)其他頁(yè)面例如PAGEX依然維持寫保護(hù),只是擁有進(jìn)程A、B和C。如果此時(shí)A訪問(wèn)PAGEX,則異常處理會(huì)創(chuàng)建一個(gè)新頁(yè)面并將PAGEX中的內(nèi)容復(fù)制到該頁(yè)面,同時(shí)A相應(yīng) 的pte指向該新頁(yè)面。如果此時(shí)C也訪問(wèn)PAGEX,也會(huì)復(fù)制新頁(yè)面并且讓C對(duì)應(yīng)的pte指向新頁(yè)面。如果B再訪問(wèn)PAGEX,則由于此時(shí)PAGEX只有一個(gè)擁有進(jìn)程B,故不再?gòu)?fù)制新頁(yè)面,而是直接取消該頁(yè)面的寫保護(hù),由于B的pte本來(lái)就是直接指向該頁(yè)面,所以無(wú)需要在做其它工作。
三、exit和_exit
(1)正常終止: (a)在main函數(shù)內(nèi)執(zhí)行return語(yǔ)句。這等效于調(diào)用exit。 (b)調(diào)用exit函數(shù) (c)調(diào)用_exit系統(tǒng)調(diào)用函數(shù)
(2)異常終止: (a)調(diào)用abort。它產(chǎn)生SIGABRT信號(hào),所以是一種異常終止的一種特列。 (b)當(dāng)進(jìn)程接收到某個(gè)信號(hào)時(shí)。例如,進(jìn)程越出其地址空間訪問(wèn)存儲(chǔ)單元,或者除以0,內(nèi)核就會(huì)為該進(jìn)程產(chǎn)生相應(yīng)的信號(hào)。
注意:不管進(jìn)程如何終止,最后都會(huì)執(zhí)行內(nèi)核中的同一段代碼。這段代碼為相應(yīng)進(jìn)程關(guān)閉所有打開描述符,釋放它所使用的存儲(chǔ)器等。
exit和_exit的不同 _exit()函數(shù)的作用最為簡(jiǎn)單:直接進(jìn)程停止運(yùn)行,清除其使用的內(nèi)存空間,并銷毀其在內(nèi)核中的各種數(shù)據(jù)結(jié)構(gòu);
exit()函數(shù)與_exit()函數(shù)最大的區(qū)別就在于exit()函數(shù)在調(diào)用exit系統(tǒng)調(diào)用之前要檢查文件的打開情況,把文件緩沖區(qū)中的內(nèi)容寫回文件,就是"清理I/O"緩沖。
探究 1._exit()
//_exit(0) exit(0) return 0 編譯運(yùn)行結(jié)果: 從上面我們看到,test.txt的內(nèi)容為空.為什么呢?因?yàn)闃?biāo)準(zhǔn)I/O函數(shù)是帶緩存的,進(jìn)行fputs的時(shí)候是先向緩存中寫的,只有當(dāng)緩存滿的時(shí)候才會(huì)刷新的緩沖區(qū)的。從以上我們發(fā)現(xiàn),當(dāng)進(jìn)程退出時(shí),執(zhí)行_exit()函數(shù)并沒(méi)有刷新緩沖區(qū)的數(shù)據(jù),而是直接終止進(jìn)程的。
探究2.exit() 編譯運(yùn)行結(jié)果: 從上面我們可以看到,當(dāng)exit()函數(shù)結(jié)束進(jìn)程的時(shí)候,對(duì)緩存進(jìn)行了處理,把緩存的數(shù)據(jù)寫到了磁盤文件中。
探究3.return
由讀者自己完成,其實(shí)return語(yǔ)句用在main函數(shù)中,和exit是一樣的。但是我們知道,return返回的值是給調(diào)用者的,它代表著一個(gè)函數(shù)的結(jié)束。
四、exec函數(shù)族
exec.c 調(diào)用exec其中的一個(gè)函數(shù); gcc exec.c -o exec; ./exec exec函數(shù)族提供了一種在進(jìn)程中啟動(dòng)另一個(gè)程序執(zhí)行的方法。它可以根據(jù)指定的文件名或目錄名找到可執(zhí)行文件,并用它來(lái)取代原調(diào)用進(jìn)程的數(shù)據(jù)段、代碼段、和堆棧段。在執(zhí)行完之后,原調(diào)用進(jìn)程的內(nèi)容除了進(jìn)程號(hào)外,其他全部都被替換了。
可執(zhí)行文件既可以是二進(jìn)制文件,也可以是任何Linux下可執(zhí)行的腳本文件。
何時(shí)使用?
當(dāng)進(jìn)程認(rèn)為自己不能再為系統(tǒng)和用戶做任何貢獻(xiàn)了就可以調(diào)用exec函數(shù)族中的函數(shù),讓自己執(zhí)行新的程序。 當(dāng)前目錄: 可執(zhí)行程序A B(1,2,3) 如果某個(gè)進(jìn)程想同時(shí)執(zhí)行另一個(gè)程序,它就可以調(diào)用fork函數(shù)創(chuàng)建子進(jìn)程,然后在子進(jìn)程中調(diào)用任何一個(gè)exec函數(shù)。這樣看起來(lái)就好像通過(guò)執(zhí)行應(yīng)用程序而產(chǎn)生了一個(gè)新進(jìn)程一樣。
execl("./B","B","1","2","3",NULL); char *const envp[] = {"B","1","2","3",NULL}
execv("./B",envp); 注意:不管file,第一個(gè)參數(shù)必須是可執(zhí)行文件的名字
可執(zhí)行文件查找方式 表中的前四個(gè)函數(shù)的查找方式都是指定完整的文件目錄路勁,而最后兩個(gè)函數(shù)(以p結(jié)尾的函數(shù))可以只給出文件名,系統(tǒng)會(huì)自動(dòng)從環(huán)境變量"$PATH"所包含的路徑中進(jìn)行查找。
參數(shù)表傳遞方式 兩種方式:一個(gè)一個(gè)列舉和將所有參數(shù)通過(guò)指針數(shù)組傳遞 一函數(shù)名的第5個(gè)字母按來(lái)區(qū)分,字母"l"(list)的表示一個(gè)一個(gè)列舉方式;字母"v"(vector)的表示將所有參數(shù)構(gòu)造成指針數(shù)組傳遞,其語(yǔ)法為char *const argv[]
環(huán)境變量的使用 exec函數(shù)族可以默認(rèn)使用系統(tǒng)的環(huán)境變量,也可以傳入指定的環(huán)境變量。這里,以"e"(Envirment)結(jié)尾的兩個(gè)函數(shù)execle、execve就可以在envp[]中傳遞當(dāng)前進(jìn)程所使用的環(huán)境變量。
使用的區(qū)別 可執(zhí)行文件查找方式 參數(shù)表傳遞方式 環(huán)境變量的使用 案例一execl
#include <stdio.h> #include <unistd.h>
int main(int argc,char *argv[]) { printf("start to execl.\n"); if(execl("/bin/ls","ls",NULL) < 0) { perror("Fail to execl"); return -1; } printf("end of execl.\n");
return 0; }
運(yùn)行結(jié)果如下: 案例二、execlp #include <stdio.h> #include <unistd.h>
int main(int argc,char *argv[]) { printf("start to execl.\n"); if(execlp("ls","ls","-l",NULL) < 0) { perror("Fail to execl"); return -1; } printf("end of execl.\n");
return 0; }
運(yùn)行結(jié)果: 案例三、execle
#include <stdio.h> #include <stdlib.h>
int main(int argc,char *argv[]) { if(getenv("B") == NULL) { printf("fail to getenv B.\n"); }else{ printf("env B = %s.\n",getenv("B")); } if(getenv("C") == NULL) { printf("fail to getenv C.\n"); }else{ printf("env C = %s.\n",getenv("C")); }
if(getenv("PATH") == NULL) { printf("fail to getenv PATH.\n"); }else{ printf("env PATH = %s.\n",getenv("PATH")); } return 0; }
運(yùn)行結(jié)果: #include <unistd.h>
int main(int argc,char *argv[]) { printf("start to execle.\n"); char * const envp[] = {"B=hello",NULL};
if(execle("./A.out","A.out",NULL,envp) < 0) { perror("Fail to execl"); return -1; }
printf("end of execl.\n");
return 0; }
運(yùn)行結(jié)果: 案例四:execv
#include <stdio.h> #include <unistd.h> #include <errno.h> #include <stdlib.h>
int main() { char * const arg[] = {"ps", "-ef", NULL}; //if (execl("/bin/ps", "ps", "-ef", NULL) < 0) if (execv("/bin/ps" ,arg) < 0) { perror("execl"); exit(-1); }
while (1);
return 0; }
五、進(jìn)程的創(chuàng)建vfork()函數(shù) vfork與fork一樣都創(chuàng)建一個(gè)子進(jìn)程,但是它并不將父進(jìn)程的地址空完全復(fù)制到子進(jìn)程中,因?yàn)樽舆M(jìn)程會(huì)立即調(diào)用exec(或exit)于是也就不會(huì)存、訪該地址空間。不過(guò)在子進(jìn)程調(diào)用exec或exit之前,它在父進(jìn)程的空間中運(yùn)行。 vfork和fork之間的另一個(gè)區(qū)別是:vfork保證子進(jìn)程先運(yùn)行,在它調(diào)用exec或exit之后 父進(jìn)程才可能被調(diào)度運(yùn)行。(如果在調(diào)用這兩個(gè)函數(shù)之前子進(jìn)程依賴于父進(jìn)程的進(jìn)一步動(dòng)作,則會(huì)導(dǎo)致死鎖)
探究1.vfork() 編譯運(yùn)行:因?yàn)槲覀冎纕fork保證子進(jìn)程先運(yùn)行,子進(jìn)程運(yùn)行結(jié)束后,父進(jìn)程才開始運(yùn)行。所以,第一次打印的是子進(jìn)程的打印的信息,可以看到var值變成了89。子進(jìn)程結(jié)束后,父進(jìn)程運(yùn)行,父進(jìn)程首先打印fork調(diào)用返回給他pid的值(就是子進(jìn)程pid)。以上我們可以看出,vfork創(chuàng)建的子進(jìn)程和父進(jìn)程運(yùn)行的地址空間相同(子進(jìn)程改變了var 值,父進(jìn)程中的var值也進(jìn)行了改變)。
注意:如果子進(jìn)程中執(zhí)行的是exec函數(shù),那就是典型的fork的copy-on-wirte。
五、wait和waitpid
wait函數(shù):調(diào)用該函數(shù)使進(jìn)程阻塞,直到任一個(gè)子進(jìn)程結(jié)束或者是該進(jìn)程接收到一個(gè)信號(hào)為止。如果該進(jìn)程沒(méi)有子進(jìn)程或者其子進(jìn)程已經(jīng)結(jié)束,wait函數(shù)會(huì)立即返回。
waitpid函數(shù):功能和wait函數(shù)類似??梢灾付ǖ却硞€(gè)子進(jìn)程結(jié)束以及等待的方式(阻塞或非阻塞)。 wait函數(shù) #include <sys/types.h> #include <sys/waith.h>
pid_t wait(int *status);
函數(shù)參數(shù):
status是一個(gè)整型指針,指向的對(duì)象用來(lái)保存子進(jìn)程退出時(shí)的狀態(tài)。
A.status若為空,表示忽略子進(jìn)程退出時(shí)的狀態(tài)
B.status若不為空,表示保存子進(jìn)程退出時(shí)的狀態(tài)
子進(jìn)程的結(jié)束狀態(tài)可由Linux中一些特定的宏來(lái)測(cè)定。
案例一、
#include <stdio.h> #include <stdlib.h>
int main() { int pid; if((pid = fork()) < 0) { perror("Fail to fork"); return -1; }else if(pid == 0){ printf("child exit now.\n"); exit(0); }else{ while(1); }
exit(0); }
運(yùn)行結(jié)果: 從以上可以看出,子進(jìn)程正常退出時(shí),處于僵尸態(tài)。這個(gè)時(shí)候子進(jìn)程的pid,以及內(nèi)核棧資源并沒(méi)有釋放,這樣是不合理的,我們應(yīng)該避免僵尸進(jìn)程。如果父進(jìn)程先退出呢,子進(jìn)程又會(huì)怎樣?
#include <stdio.h> #include <stdlib.h>
int main() { int pid; if((pid = fork()) < 0) { perror("Fail to fork"); return -1; }else if(pid == 0){ printf("child running now - pid : %d.\n",getpid()); while(1); }else{ getchar(); printf("Father exit now - pid : %d.\n",getpid()); exit(0); }
}
從上面可以看出,如果父進(jìn)程先退出,則子進(jìn)程的父進(jìn)程的ID號(hào)變?yōu)?,也就是說(shuō)當(dāng)一個(gè)子進(jìn)程的父進(jìn)程退出時(shí),這個(gè)子進(jìn)程會(huì)被init進(jìn)程自動(dòng)收養(yǎng)。
案例二、利用wait等待回收處于僵尸態(tài)的子進(jìn)程
#include <stdio.h> #include <stdlib.h>
int main() { int pid; if((pid = fork()) < 0) { perror("Fail to fork"); return -1; }else if(pid == 0){ printf("child runing now - pid : %d.\n",getpid()); getchar(); printf("child exiting now - pid : %d.\n",getpid()); exit(0); }else{ printf("Father wait zombie now - pid : %d.\n",getpid()); wait(NULL); printf("Father exiting now - pid : %d.\n",getpid()); exit(0); }
}
沒(méi)有輸入字符前: 輸入字符后: 此時(shí)我們沒(méi)有發(fā)現(xiàn)僵尸進(jìn)程,當(dāng)子進(jìn)程退出時(shí),父進(jìn)程的wait回收了子進(jìn)程未釋放的資源。
案例三、獲取進(jìn)程退出時(shí)的狀態(tài)
#include <stdio.h> #include <stdlib.h>
int main() { int pid; int status;
if((pid = fork()) < 0) { perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); printf("child process : %d calling exit(7).\n",getpid()); exit(7); }else{
if((pid = fork()) < 0 ){ perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); while(1); }else{
while((pid = wait(&status)) != -1) { if(WIFEXITED(status)) { printf("child process %d is normal exit,the value is %d.\n",pid,WEXITSTATUS(status)); }else if(WIFSIGNALED(status)){ printf("child process %d is exit by signal,the signal num is %d.\n",pid,WTERMSIG(status)); }else{ printf("Not know.\n"); } } } } printf("All child process is exit,father is exit.\n"); exit(0); } 給進(jìn)程15494發(fā)個(gè)信號(hào) 程序運(yùn)行結(jié)果: 從以上探究可以知道,每當(dāng)子進(jìn)程結(jié)束后,wait函數(shù)就會(huì)返回哪個(gè)子進(jìn)程結(jié)束的pid。如果沒(méi)有子進(jìn)程存在,wait函數(shù)就返回-1。
函數(shù)返回值: 成功:子進(jìn)程的進(jìn)程號(hào) 失敗:-1 #include <sys/types.h> #include <sys/wait.h>
pid_t waitpid(pid_t pid,int *status,int options);
參數(shù):
1.在父進(jìn)程中創(chuàng)建兩個(gè)子進(jìn)程(A B) 2.A進(jìn)程打印"child process %d exit",調(diào)用exit(2),結(jié)束 3.B進(jìn)程一直運(yùn)行
注意:父進(jìn)程調(diào)用while(waitpid(-1,&status,WUNTRACED) != -1 ) status:同wait
options:
WNOHANG,若由pid指定的子進(jìn)程并不立即可用,則waitpid不阻塞,此時(shí)返回值為0 WUNTRACED,若某實(shí)現(xiàn)支持作業(yè)控制,則由pid指定的任一子進(jìn)程狀態(tài)已暫停,且其狀態(tài)自暫停以來(lái)還沒(méi)報(bào)告過(guò),則返回其狀態(tài)。
0:同wait,阻塞父進(jìn)程,等待子進(jìn)程退出。
返回值 正常:結(jié)束的子進(jìn)程的進(jìn)程號(hào) 使用選項(xiàng)WNOHANG且沒(méi)有子進(jìn)程結(jié)束時(shí):0 調(diào)用出錯(cuò):-1
案例一、
#include <stdio.h> #include <stdlib.h>
int main() { int pid; int status;
if((pid = fork()) < 0) { perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); printf("child process : %d calling exit(7).\n",getpid()); exit(7); }else{
if((pid = fork()) < 0 ){ perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); while(1); }else{
while((pid = wait(&status)) != -1) { if(WIFEXITED(status)) { printf("child process %d is normal exit,the value is %d.\n",pid,WEXITSTATUS(status)); }else if(WIFSIGNALED(status)){ printf("child process %d is exit by signal,the signal num is %d.\n",pid,WTERMSIG(status)); }else{ printf("Not know.\n"); } } } } printf("All child process is exit,father is exit.\n"); exit(0); }
程序運(yùn)行結(jié)果: 使用ps -aux結(jié)果 從以上可以看出,子進(jìn)程15783退出時(shí),父進(jìn)程并沒(méi)有回收它的資源,此時(shí)可以看到它處于僵尸態(tài)。 由于父進(jìn)程調(diào)用waitpid等待子進(jìn)程15784退出,此時(shí)這個(gè)進(jìn)程還沒(méi)退出,看可以看到父進(jìn)程處于可中斷的睡眠狀態(tài)。
我們給子進(jìn)程15784發(fā)個(gè)信號(hào),在看看結(jié)果 程序運(yùn)行結(jié)果:
可以看到當(dāng)waitpid指定等待的進(jìn)程退出時(shí),waitpid立即返回,此時(shí)父進(jìn)程退出。
案例二、
#include <stdio.h> #include <stdlib.h>
int main() { int pid; int status;
if((pid = fork()) < 0) { perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); printf("child process : %d calling exit(7).\n",getpid()); exit(7); }else{
if((pid = fork()) < 0 ){ perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); while(1); }else{ sleep(2); printf("Father wait child %d exit.\n",pid); while((pid = waitpid(pid,NULL,WNOHANG))) { printf("The process %d is exit.\n",pid); } printf("The process %d is exit.\n",pid); } }
exit(0); } 從上面探究我們可以看出,如果有子進(jìn)程處于僵尸態(tài),waitpid(pid,NULL,WNOHANG)立即處理后返回,如果沒(méi)有子進(jìn)程處于僵尸態(tài),此時(shí)waitpid(pid,NULL,WNOHANG)也會(huì)立即返回,而不阻塞,此時(shí)返回值為0。
案例探究三、
#include <stdio.h> #include <stdlib.h> #include <string.h>
int main(int argc,char *argv[]) { int pid; int status;
if((pid = fork()) < 0) { perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); printf("child process in proces group %d.\n",getpgid(0)); printf("child process : %d calling exit(7).\n",getpid()); exit(7); }else{
if((pid = fork()) < 0 ){ perror("Fail to fork"); exit(-1); }else if(pid == 0){ sleep(3); printf("create child process : %d.\n",getpid()); setpgid(0,0); //讓子進(jìn)程屬于以自己ID作為組的進(jìn)程組 printf("child process in proces group %d.\n",getpgid(0)); printf("child process : %d calling exit(6).\n",getpid()); }else{ while(pid = waitpid(0,NULL,0)) { printf("Father wait the process %d is exit.\n",pid); } } }
exit(0); }
運(yùn)行結(jié)果: 當(dāng)在父進(jìn)中創(chuàng)建子進(jìn)程時(shí),父進(jìn)程和子進(jìn)程都在以父進(jìn)程ID號(hào)為組的進(jìn)程組。以上代碼中,有一個(gè)子進(jìn)程改變了自己所在的進(jìn)程組. 此時(shí)waitpid(0,NULL,0);只能處理以父進(jìn)程ID為組的進(jìn)程組中的進(jìn)程,可以看到第一個(gè)子進(jìn)程結(jié)束后waitpid函數(shù)回收了它未釋放的資源,而第二個(gè)子進(jìn)程則處于僵尸態(tài)
|