?
7.2??Linux進(jìn)程控制編程
1.fork()
在Linux中創(chuàng)建一個(gè)新進(jìn)程的惟一方法是使用fork()函數(shù)。fork()函數(shù)是Linux中一個(gè)非常重要的函數(shù),和讀者以往遇到的函數(shù)有一些區(qū)別,因?yàn)樗雌饋?lái)執(zhí)行一次卻返回兩個(gè)值。難道一個(gè)函數(shù)真的能返回兩個(gè)值嗎?希望讀者能認(rèn)真地學(xué)習(xí)這一部分的內(nèi)容。
(1)fork()函數(shù)說(shuō)明。
fork()函數(shù)用于從已存在的進(jìn)程中創(chuàng)建一個(gè)新進(jìn)程。新進(jìn)程稱為子進(jìn)程,而原進(jìn)程稱為父進(jìn)程。使用fork()函數(shù)得到的子進(jìn)程是父進(jìn)程的一個(gè)復(fù)制品,它從父進(jìn)程處繼承了整個(gè)進(jìn)程的地址空間,包括進(jìn)程上下文、代碼段、進(jìn)程堆棧、內(nèi)存信息、打開(kāi)的文件描述符、信號(hào)控制設(shè)定、進(jìn)程優(yōu)先級(jí)、進(jìn)程組號(hào)、當(dāng)前工作目錄、根目錄、資源限制和控制終端等,而子進(jìn)程所獨(dú)有的只有它的進(jìn)程號(hào)、資源使用和計(jì)時(shí)器等。
因?yàn)樽舆M(jìn)程幾乎是父進(jìn)程的完全復(fù)制,所以父子兩個(gè)進(jìn)程會(huì)運(yùn)行同一個(gè)程序。因此需要用一種方式來(lái)區(qū)分它們,并使它們照此運(yùn)行,否則,這兩個(gè)進(jìn)程不可能做不同的事。
實(shí)際上是在父進(jìn)程中執(zhí)行fork()函數(shù)時(shí),父進(jìn)程會(huì)復(fù)制出一個(gè)子進(jìn)程,而且父子進(jìn)程的代碼從fork()函數(shù)的返回開(kāi)始分別在兩個(gè)地址空間中同時(shí)運(yùn)行。從而兩個(gè)進(jìn)程分別獲得其所屬fork()的返回值,其中在父進(jìn)程中的返回值是子進(jìn)程的進(jìn)程號(hào),而在子進(jìn)程中返回0。因此,可以通過(guò)返回值來(lái)判定該進(jìn)程是父進(jìn)程還是子進(jìn)程。
同時(shí)可以看出,使用fork()函數(shù)的代價(jià)是很大的,它復(fù)制了父進(jìn)程中的代碼段、數(shù)據(jù)段和堆棧段里的大部分內(nèi)容,使得fork()函數(shù)的系統(tǒng)開(kāi)銷比較大,而且執(zhí)行速度也不是很快。
(2)fork()函數(shù)語(yǔ)法。
表7.2列出了fork()函數(shù)的語(yǔ)法要點(diǎn)。
表7.2 fork()函數(shù)語(yǔ)法要點(diǎn)
所需頭文件 |
#include?<sys/types.h>?//?提供類型pid_t的定義 |
函數(shù)原型 |
pid_t?fork(void) |
函數(shù)返回值 |
0:子進(jìn)程 |
子進(jìn)程ID(大于0的整數(shù)):父進(jìn)程 |
|
-1:出錯(cuò) |
(3)fork()函數(shù)使用實(shí)例。
/*?fork.c?*/
#include?<sys/types.h>
#include?<unistd.h>
#include?<stdio.h>
#include?<stdlib.h>
int?main(void)
{
?????pid_t?result;
????
?????/*調(diào)用fork()函數(shù)*/
?????result?=?fork();
????
?????/*通過(guò)result的值來(lái)判斷fork()函數(shù)的返回情況,首先進(jìn)行出錯(cuò)處理*/
?????if(result?==??-1)
?????{
?????printf("Fork?errorn");
?????}
?????else?if?(result?==?0)?/*返回值為0代表子進(jìn)程*/
?????{
?????????printf("The?returned?value?is?%dn
????????????????????In?child?process!!nMy?PID?is?%dn",result,getpid());
?????}
?????else?/*返回值大于0代表父進(jìn)程*/
?????{
?????????printf("The?returned?value?is?%dn
????????????????????In?father?process!!nMy?PID?is?%dn",result,getpid());
?????}
?????return?result;
}
將可執(zhí)行程序下載到目標(biāo)板上,運(yùn)行結(jié)果如下所示:
$?arm-linux-gcc?fork.c?–o?fork?(或者修改Makefile)
$?./fork
The?returned?value?is?76???????/*?在父進(jìn)程中打印的信息?*/
In?father?process!!
My?PID?is?75
The?returned?value?is?:0???????/*?在子進(jìn)程中打印的信息?*/
In?child?process!!
My?PID?is?76
從該實(shí)例中可以看出,使用fork()函數(shù)新建了一個(gè)子進(jìn)程,其中的父進(jìn)程返回子進(jìn)程的PID,而子進(jìn)程的返回值為0。
?
(4)函數(shù)使用注意點(diǎn)。
fork()函數(shù)使用一次就創(chuàng)建一個(gè)進(jìn)程,所以若把fork()函數(shù)放在了if?else判斷語(yǔ)句中則要小心,不能多次使用fork()函數(shù)。
小知識(shí) |
由于fork()完整地復(fù)制了父進(jìn)程的整個(gè)地址空間,因此執(zhí)行速度是比較慢的。為了加快fork()的執(zhí)行速度,有些UNIX系統(tǒng)設(shè)計(jì)者創(chuàng)建了vfork()。vfork()也能創(chuàng)建新進(jìn)程,但它不產(chǎn)生父進(jìn)程的副本。它是通過(guò)允許父子進(jìn)程可訪問(wèn)相同物理內(nèi)存從而偽裝了對(duì)進(jìn)程地址空間的真實(shí)拷貝,當(dāng)子進(jìn)程需要改變內(nèi)存中數(shù)據(jù)時(shí)才復(fù)制父進(jìn)程。這就是著名的“寫操作時(shí)復(fù)制”(copy-on-write)技術(shù)。 現(xiàn)在很多嵌入式Linux系統(tǒng)的fork()函數(shù)調(diào)用都采用vfork()函數(shù)的實(shí)現(xiàn)方式,實(shí)際上uClinux所有的多進(jìn)程管理都通過(guò)vfork()來(lái)實(shí)現(xiàn)。 |
2.exec函數(shù)族
(1)exec函數(shù)族說(shuō)明。
fork()函數(shù)是用于創(chuàng)建一個(gè)子進(jìn)程,該子進(jìn)程幾乎復(fù)制了父進(jìn)程的全部?jī)?nèi)容,但是,這個(gè)新創(chuàng)建的進(jìn)程如何執(zhí)行呢?這個(gè)exec函數(shù)族就提供了一個(gè)在進(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)外,其他全部被新的進(jìn)程替換了。另外,這里的可執(zhí)行文件既可以是二進(jìn)制文件,也可以是Linux下任何可執(zhí)行的腳本文件。
在Linux中使用exec函數(shù)族主要有兩種情況。
n 當(dāng)進(jìn)程認(rèn)為自己不能再為系統(tǒng)和用戶做出任何貢獻(xiàn)時(shí),就可以調(diào)用exec函數(shù)族中的任意一個(gè)函數(shù)讓自己重生。
n 如果一個(gè)進(jìn)程想執(zhí)行另一個(gè)程序,那么它就可以調(diào)用fork()函數(shù)新建一個(gè)進(jìn)程,然后調(diào)用exec函數(shù)族中的任意一個(gè)函數(shù),這樣看起來(lái)就像通過(guò)執(zhí)行應(yīng)用程序而產(chǎn)生了一個(gè)新進(jìn)程(這種情況非常普遍)。
(2)exec函數(shù)族語(yǔ)法。
實(shí)際上,在Linux中并沒(méi)有exec()函數(shù),而是有6個(gè)以exec開(kāi)頭的函數(shù),它們之間語(yǔ)法有細(xì)微差別,本書在下面會(huì)詳細(xì)講解。
下表7.3列舉了exec函數(shù)族的6個(gè)成員函數(shù)的語(yǔ)法。
表7.3 exec函數(shù)族成員函數(shù)語(yǔ)法
所需頭文件 |
#include?<unistd.h> |
函數(shù)原型 |
int?execl(const?char?*path,?const?char?*arg,?...) |
int?execv(const?char?*path,?char?*const?argv[]) |
|
int?execle(const?char?*path,?const?char?*arg,?...,?char?*const?envp[]) |
|
int?execve(const?char?*path,?char?*const?argv[],?char?*const?envp[]) |
|
int?execlp(const?char?*file,?const?char?*arg,?...) |
|
int?execvp(const?char?*file,?char?*const?argv[]) |
|
函數(shù)返回值 |
-1:出錯(cuò) |
這6個(gè)函數(shù)在函數(shù)名和使用語(yǔ)法的規(guī)則上都有細(xì)微的區(qū)別,下面就可執(zhí)行文件查找方式、參數(shù)表傳遞方式及環(huán)境變量這幾個(gè)方面進(jìn)行比較。
n 查找方式。
讀者可以注意到,表7.3中的前4個(gè)函數(shù)的查找方式都是完整的文件目錄路徑,而最后2個(gè)函數(shù)(也就是以p結(jié)尾的兩個(gè)函數(shù))可以只給出文件名,系統(tǒng)就會(huì)自動(dòng)按照環(huán)境變量“$PATH”所指定的路徑進(jìn)行查找。
n 參數(shù)傳遞方式。
exec函數(shù)族的參數(shù)傳遞有兩種方式:一種是逐個(gè)列舉的方式,而另一種則是將所有參數(shù)整體構(gòu)造指針數(shù)組傳遞。
在這里是以函數(shù)名的第5位字母來(lái)區(qū)分的,字母為“l(fā)”(list)的表示逐個(gè)列舉參數(shù)的方式,其語(yǔ)法為char?*arg;字母為“v”(vertor)的表示將所有參數(shù)整體構(gòu)造指針數(shù)組傳遞,其語(yǔ)法為*const?argv[]。讀者可以觀察execl()、execle()、execlp()的語(yǔ)法與execv()、execve()、execvp()的區(qū)別。它們具體的用法在后面的實(shí)例講解中會(huì)具體說(shuō)明。
這里的參數(shù)實(shí)際上就是用戶在使用這個(gè)可執(zhí)行文件時(shí)所需的全部命令選項(xiàng)字符串(包括該可執(zhí)行程序命令本身)。要注意的是,這些參數(shù)必須以NULL表示結(jié)束,如果使用逐個(gè)列舉方式,那么要把它強(qiáng)制轉(zhuǎn)化成一個(gè)字符指針,否則exec將會(huì)把它解釋為一個(gè)整型參數(shù),如果一個(gè)整型數(shù)的長(zhǎng)度char?*的長(zhǎng)度不同,那么exec函數(shù)就會(huì)報(bào)錯(cuò)。
n 環(huán)境變量。
exec函數(shù)族可以默認(rèn)系統(tǒng)的環(huán)境變量,也可以傳入指定的環(huán)境變量。這里以“e”(environment)結(jié)尾的兩個(gè)函數(shù)execle()和execve()就可以在envp[]中指定當(dāng)前進(jìn)程所使用的環(huán)境變量。
表7.4是對(duì)這4個(gè)函數(shù)中函數(shù)名和對(duì)應(yīng)語(yǔ)法的小結(jié),主要指出了函數(shù)名中每一位所表明的含義,希望讀者結(jié)合此表加以記憶。
表7.4 exec函數(shù)名對(duì)應(yīng)含義
前4位 |
統(tǒng)一為:exec |
|
第5位 |
l:參數(shù)傳遞為逐個(gè)列舉方式 |
execl、execle、execlp |
v:參數(shù)傳遞為構(gòu)造指針數(shù)組方式 |
execv、execve、execvp |
|
第6位 |
e:可傳遞新進(jìn)程環(huán)境變量 |
execle、execve |
p:可執(zhí)行文件查找方式為文件名 |
execlp、execvp |
(3)exec使用實(shí)例。
下面的第一個(gè)示例說(shuō)明了如何使用文件名的方式來(lái)查找可執(zhí)行文件,同時(shí)使用參數(shù)列表的方式。這里用的函數(shù)是execlp()。
/*execlp.c*/
#include?<unistd.h>
#include?<stdio.h>
#include?<stdlib.h>
int?main()
{
?????if?(fork()?==?0)
?????{
?????????/*調(diào)用execlp()函數(shù),這里相當(dāng)于調(diào)用了"ps?-ef"命令*/
?????????if?((ret?=?execlp("ps",?"ps",?"-ef",?NULL))?<?0)
?????????{
?????????????printf("Execlp?errorn");
?????????}
?????}
}
在該程序中,首先使用fork()函數(shù)創(chuàng)建一個(gè)子進(jìn)程,然后在子進(jìn)程里使用execlp()函數(shù)。讀者可以看到,這里的參數(shù)列表列出了在shell中使用的命令名和選項(xiàng)。并且當(dāng)使用文件名進(jìn)行查找時(shí),系統(tǒng)會(huì)在默認(rèn)的環(huán)境變量PATH中尋找該可執(zhí)行文件。讀者可將編譯后的結(jié)果下載到目標(biāo)板上,運(yùn)行結(jié)果如下所示:
$?./execlp
??PID?TTY?????Uid????????Size?State?Command
????1?????????root???????1832?????S????init
????2?????????root??????????0?????S????[keventd]
????3?????????root??????????0?????S????[ksoftirqd_CPU0]
????4?????????root??????????0?????S????[kswapd]
????5?????????root??????????0?????S????[bdflush]
????6?????????root??????????0?????S????[kupdated]
????7?????????root??????????0?????S????[mtdblockd]
????8?????????root??????????0?????S????[khubd]
???35?????????root???????2104?????S????/bin/bash?/usr/etc/rc.local
???36?????????root???????2324?????S????/bin/bash
???41?????????root???????1364?????S????/sbin/inetd
???53?????????root??????14260?????S????/Qtopia/qtopia-free-1.7.0/bin/qpe?-qws
???54?????????root??????11672?????S????quicklauncher
???65?????????root??????????0?????S????[usb-storage-0]
???66?????????root??????????0?????S????[scsi_eh_0]
???83?????????root???????2020?????R????ps?-ef
$?env
……
PATH=/Qtopia/qtopia-free-1.7.0/bin:/usr/bin:/bin:/usr/sbin:/sbin
?????……
?
此程序的運(yùn)行結(jié)果與在shell中直接鍵入命令“ps?-ef”是一樣的,當(dāng)然,在不同系統(tǒng)的不同時(shí)刻都可能會(huì)有不同的結(jié)果。
接下來(lái)的示例使用完整的文件目錄來(lái)查找對(duì)應(yīng)的可執(zhí)行文件。注意目錄必須以“/”開(kāi)頭,否則將其視為文件名。
/*execl.c*/
#include?<unistd.h>
#include?<stdio.h>
#include?<stdlib.h>
int?main()
{
????if?(fork()?==?0)
????{
?????????/*調(diào)用execl()函數(shù),注意這里要給出ps程序所在的完整路徑*/
?????????if?(execl("/bin/ps","ps","-ef",NULL)?<?0)
?????????{
?????????printf("Execl?errorn");
?????????}
????}
}
同樣下載到目標(biāo)板上運(yùn)行,運(yùn)行結(jié)果同上例。
下面的示例利用函數(shù)execle(),將環(huán)境變量添加到新建的子進(jìn)程中,這里的“env”是查看當(dāng)前進(jìn)程環(huán)境變量的命令,如下所示:
/*?execle.c?*/
#include?<unistd.h>
#include?<stdio.h>
#include?<stdlib.h>
int?main()
{
????/*命令參數(shù)列表,必須以NULL結(jié)尾*/
????char?*envp[]={"PATH=/tmp","USER=david",?NULL};
????
????if?(fork()?==?0)
????{
?????????/*調(diào)用execle()函數(shù),注意這里也要指出env的完整路徑*/
?????????if?(execle("/usr/bin/env",?"env",?NULL,?envp)?<?0)
?????????{
?????????????printf("Execle?errorn");
?????????}
????}
}
下載到目標(biāo)板后的運(yùn)行結(jié)果如下所示:
$./execle
PATH=/tmp
USER=sunq
最后一個(gè)示例使用execve()函數(shù),通過(guò)構(gòu)造指針數(shù)組的方式來(lái)傳遞參數(shù),注意參數(shù)列表一定要以NULL作為結(jié)尾標(biāo)識(shí)符。其代碼和運(yùn)行結(jié)果如下所示:
#include?<unistd.h>
#include?<stdio.h>
#include?<stdlib.h>
int?main()
{
????/*命令參數(shù)列表,必須以NULL結(jié)尾*/
????char?*arg[]?=?{"env",?NULL};
????char?*envp[]?=?{"PATH=/tmp",?"USER=david",?NULL};
????
????if?(fork()?==?0)
????{
?????????if?(execve("/usr/bin/env",?arg,?envp)?<?0)
?????????{
?????????printf("Execve?errorn");
????????}
????}
}
下載到目標(biāo)板后的運(yùn)行結(jié)果如下所示:
$?./execve
PATH=/tmp
USER=david
(4)exec函數(shù)族使用注意點(diǎn)。
在使用exec函數(shù)族時(shí),一定要加上錯(cuò)誤判斷語(yǔ)句。exec很容易執(zhí)行失敗,其中最常見(jiàn)的原因有:
n 找不到文件或路徑,此時(shí)errno被設(shè)置為ENOENT;?
n 數(shù)組argv和envp忘記用NULL結(jié)束,此時(shí)errno被設(shè)置為EFAULT;?
n 沒(méi)有對(duì)應(yīng)可執(zhí)行文件的運(yùn)行權(quán)限,此時(shí)errno被設(shè)置為EACCES。
小知識(shí) |
事實(shí)上,這6個(gè)函數(shù)中真正的系統(tǒng)調(diào)用只有execve(),其他5個(gè)都是庫(kù)函數(shù),它們最終都會(huì)調(diào)用execve()這個(gè)系統(tǒng)調(diào)用。 |
?
3.exit()和_exit()
(1)exit()和_exit()函數(shù)說(shuō)明。
exit()和_exit()函數(shù)都是用來(lái)終止進(jìn)程的。當(dāng)程序執(zhí)行到exit()或_exit()時(shí),進(jìn)程會(huì)無(wú)條件地停止剩下的所有操作,清除包括PCB在內(nèi)的各種數(shù)據(jù)結(jié)構(gòu),并終止本進(jìn)程的運(yùn)行。但是,這兩個(gè)函數(shù)還是有區(qū)別的,這兩個(gè)函數(shù)的調(diào)用過(guò)程如圖7.4所示。
圖7.4??exit和_exit函數(shù)流程圖
從圖中可以看出,_exit()函數(shù)的作用是:直接使進(jìn)程停止運(yùn)行,清除其使用的內(nèi)存空間,并清除其在內(nèi)核中的各種數(shù)據(jù)結(jié)構(gòu);exit()函數(shù)則在這些基礎(chǔ)上做了一些包裝,在執(zhí)行退出之前加了若干道工序。exit()函數(shù)與_exit()函數(shù)最大的區(qū)別就在于exit()函數(shù)在調(diào)用exit系統(tǒng)之前要檢查文件的打開(kāi)情況,把文件緩沖區(qū)中的內(nèi)容寫回文件,就是圖中的“清理I/O緩沖”一項(xiàng)。
由于在Linux的標(biāo)準(zhǔn)函數(shù)庫(kù)中,有一種被稱作“緩沖I/O(buffered?I/O)”操作,其特征就是對(duì)應(yīng)每一個(gè)打開(kāi)的文件,在內(nèi)存中都有一片緩沖區(qū)。每次讀文件時(shí),會(huì)連續(xù)讀出若干條記錄,這樣在下次讀文件時(shí)就可以直接從內(nèi)存的緩沖區(qū)中讀??;同樣,每次寫文件的時(shí)候,也僅僅是寫入內(nèi)存中的緩沖區(qū),等滿足了一定的條件(如達(dá)到一定數(shù)量或遇到特定字符等),再將緩沖區(qū)中的內(nèi)容一次性寫入文件。
這種技術(shù)大大增加了文件讀寫的速度,但也為編程帶來(lái)了一些麻煩。比如有些數(shù)據(jù),認(rèn)為已經(jīng)被寫入文件中,實(shí)際上因?yàn)闆](méi)有滿足特定的條件,它們還只是被保存在緩沖區(qū)內(nèi),這時(shí)用_exit()函數(shù)直接將進(jìn)程關(guān)閉,緩沖區(qū)中的數(shù)據(jù)就會(huì)丟失。因此,若想保證數(shù)據(jù)的完整性,就一定要使用exit()函數(shù)。
(2)exit()和_exit()函數(shù)語(yǔ)法。
表7.5列出了exit()和_exit()函數(shù)的語(yǔ)法規(guī)范。
表7.5 exit()和_exit()函數(shù)族語(yǔ)法
所需頭文件 |
exit:#include?<stdlib.h> |
_exit:#include?<unistd.h> |
|
函數(shù)原型 |
exit:void?exit(int?status) |
_exit:void?_exit(int?status) |
|
函數(shù)傳入值 |
status是一個(gè)整型的參數(shù),可以利用這個(gè)參數(shù)傳遞進(jìn)程結(jié)束時(shí)的狀態(tài)。一般來(lái)說(shuō),0表示正常結(jié)束;其他的數(shù)值表示出現(xiàn)了錯(cuò)誤,進(jìn)程非正常結(jié)束。 |
(3)exit()和_exit()使用實(shí)例。
這兩個(gè)示例比較了exit()和_exit()兩個(gè)函數(shù)的區(qū)別。由于printf()函數(shù)使用的是緩沖I/O方式,該函數(shù)在遇到“n”換行符時(shí)自動(dòng)從緩沖區(qū)中將記錄讀出。示例中就是利用這個(gè)性質(zhì)來(lái)進(jìn)行比較的。以下是示例1的代碼:
/*?exit.c?*/
#include?<stdio.h>
#include?<stdlib.h>
int?main()
{
??????printf("Using?exit...n");
??????printf("This?is?the?content?in?buffer");
??????exit(0);
}
$./exit
Using?exit...
This?is?the?content?in?buffer?$
讀者從輸出的結(jié)果中可以看到,調(diào)用exit()函數(shù)時(shí),緩沖區(qū)中的記錄也能正常輸出。
以下是示例2的代碼:
/*?_exit.c?*/
#include?<stdio.h>
#include?<unistd.h>
int?main()
{
??????printf("Using?_exit...n");
??????printf("This?is?the?content?in?buffer");?/*?加上回車符之后結(jié)果又如何?*/
??????_exit(0);
}
$?./_exit
Using?_exit...
$
讀者從最后的結(jié)果中可以看到,調(diào)用_exit()函數(shù)無(wú)法輸出緩沖區(qū)中的記錄。
小知識(shí) |
在一個(gè)進(jìn)程調(diào)用了exit()之后,該進(jìn)程并不會(huì)立刻完全消失,而是留下一個(gè)稱為僵尸進(jìn)程(Zombie)的數(shù)據(jù)結(jié)構(gòu)。僵尸進(jìn)程是一種非常特殊的進(jìn)程,它已經(jīng)放棄了幾乎所有的內(nèi)存空間,沒(méi)有任何可執(zhí)行代碼,也不能被調(diào)度,僅僅在進(jìn)程列表中保留一個(gè)位置,記載該進(jìn)程的退出狀態(tài)等信息供其他進(jìn)程收集,除此之外,僵尸進(jìn)程不再占有任何內(nèi)存空間。 |
4.wait()和waitpid()
(1)wait()和waitpid()函數(shù)說(shuō)明。
wait()函數(shù)是用于使父進(jìn)程(也就是調(diào)用wait()的進(jìn)程)阻塞,直到一個(gè)子進(jìn)程結(jié)束或者該進(jìn)程接到了一個(gè)指定的信號(hào)為止。如果該父進(jìn)程沒(méi)有子進(jìn)程或者他的子進(jìn)程已經(jīng)結(jié)束,則wait()就會(huì)立即返回。
waitpid()的作用和wait()一樣,但它并不一定要等待第一個(gè)終止的子進(jìn)程,它還有若干選項(xiàng),如可提供一個(gè)非阻塞版本的wait()功能,也能支持作業(yè)控制。實(shí)際上wait()函數(shù)只是waitpid()函數(shù)的一個(gè)特例,在Linux內(nèi)部實(shí)現(xiàn)wait()函數(shù)時(shí)直接調(diào)用的就是waitpid()函數(shù)。
(2)wait()和waitpid()函數(shù)格式說(shuō)明。
表7.6列出了wait()函數(shù)的語(yǔ)法規(guī)范。
表7.6 wait()函數(shù)族語(yǔ)法
所需頭文件 |
#include?<sys/types.h> |
函數(shù)原型 |
pid_t?wait(int?*status) |
函數(shù)傳入值 |
這里的status是一個(gè)整型指針,是該子進(jìn)程退出時(shí)的狀態(tài) |
函數(shù)返回值 |
成功:已結(jié)束運(yùn)行的子進(jìn)程的進(jìn)程號(hào) |
表7.7列出了waitpid()函數(shù)的語(yǔ)法規(guī)范。
表7.7 waitpid()函數(shù)語(yǔ)法
所需頭文件 |
#include?<sys/types.h> |
函數(shù)原型 |
pid_t?waitpid(pid_t?pid,?int?*status,?int?options) |
續(xù)表
函數(shù)傳入值 |
Pid |
pid?>?0:只等待進(jìn)程ID等于pid的子進(jìn)程,不管已經(jīng)有其他子進(jìn)程運(yùn)行結(jié)束退出了,只要指定的子進(jìn)程還沒(méi)有結(jié)束,waitpid()就會(huì)一直等下去 |
||
pid?=?-1:等待任何一個(gè)子進(jìn)程退出,此時(shí)和wait()作用一樣 |
||||
pid?=?0:等待其組ID等于調(diào)用進(jìn)程的組ID的任一子進(jìn)程 |
||||
pid?<?-1:等待其組ID等于pid的絕對(duì)值的任一子進(jìn)程 |
||||
status |
同wait() |
|||
options |
WNOHANG:若由pid指定的子進(jìn)程不立即可用,則waitpid()不阻塞,此時(shí)返回值為0 |
|||
WUNTRACED:若實(shí)現(xiàn)某支持作業(yè)控制,則由pid指定的任一子進(jìn)程狀態(tài)已暫停,且其狀態(tài)自暫停以來(lái)還未報(bào)告過(guò),則返回其狀態(tài) |
||||
0:同wait(),阻塞父進(jìn)程,等待子進(jìn)程退出 |
||||
函數(shù)返回值 |
正常:已經(jīng)結(jié)束運(yùn)行的子進(jìn)程的進(jìn)程號(hào) |
|||
使用選項(xiàng)WNOHANG且沒(méi)有子進(jìn)程退出:0 |
||||
調(diào)用出錯(cuò):-1 |
?
3)waitpid()使用實(shí)例。
由于wait()函數(shù)的使用較為簡(jiǎn)單,在此僅以waitpid()為例進(jìn)行講解。本例中首先使用fork()創(chuàng)建一個(gè)子進(jìn)程,然后讓其子進(jìn)程暫停5s(使用了sleep()函數(shù))。接下來(lái)對(duì)原有的父進(jìn)程使用waitpid()函數(shù),并使用參數(shù)WNOHANG使該父進(jìn)程不會(huì)阻塞。若有子進(jìn)程退出,則waitpid()返回子進(jìn)程號(hào);若沒(méi)有子進(jìn)程退出,則waitpid()返回0,并且父進(jìn)程每隔一秒循環(huán)判斷一次。該程序的流程圖如圖7.5所示。
圖7.5??waitpid示例程序流
該程序源代碼如下所示:
/*?waitpid.c?*/
#include?<sys/types.h>
#include?<sys/wait.h>
#include?<unistd.h>
#include?<stdio.h>?????????????????????????
#include?<stdlib.h>?
int?main()
{
????pid_t?pc,?pr;
????
????pc?=?fork();
????if?(pc?<?0)
????{
????????printf("Error?forkn");
????}
????else?if?(pc?==?0)?/*子進(jìn)程*/
????{?
????????/*子進(jìn)程暫停5s*/
????????sleep(5);????????
????????/*子進(jìn)程正常退出*/
????????exit(0);
????}
????else?/*父進(jìn)程*/
????{
????????/*循環(huán)測(cè)試子進(jìn)程是否退出*/
???????do
????????{
?????????????/*調(diào)用waitpid,且父進(jìn)程不阻塞*/
?????????????pr?=?waitpid(pc,?NULL,?WNOHANG);
?????????????/*若子進(jìn)程還未退出,則父進(jìn)程暫停1s*/
?????????????if?(pr?==?0)
?????????????{
??????????????????printf("The?child?process?has?not?exitedn");
?????????????sleep(1);
?????????????}
????????}?while?(pr?==?0);
????????
????????/*若發(fā)現(xiàn)子進(jìn)程退出,打印出相應(yīng)情況*/
????????if?(pr?==?pc)
????????{
????????????printf("Get?child?exit?code:?%dn",pr);
????????}
????????else
????????{
????????????printf("Some?error?occured.n");
????????}
????}
}
將該程序交叉編譯,下載到目標(biāo)板后的運(yùn)行結(jié)果如下所示:
$./waitpid
The?child?process?has?not?exited
The?child?process?has?not?exited
The?child?process?has?not?exited
The?child?process?has?not?exited
The?child?process?has?not?exited
Get?child?exit?code:?75
可見(jiàn),該程序在經(jīng)過(guò)5次循環(huán)之后,捕獲到了子進(jìn)程的退出信號(hào),具體的子進(jìn)程號(hào)在不同的系統(tǒng)上會(huì)有所區(qū)別。
讀者還可以嘗試把“pr?=?waitpid(pc,?NULL,?WNOHANG);”這句改為“pr?=?waitpid(pc,?NULL,?0);”或者“pr?=?wait(NULL);”,運(yùn)行的結(jié)果為:
$./waitpid
Get?child?exit?code:?76
可見(jiàn),在上述兩種情況下,父進(jìn)程在調(diào)用waitpid()或wait()之后就將自己阻塞,直到有子進(jìn)程退出為止。