加入星計(jì)劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長(zhǎng)期合作伙伴
立即加入
  • 正文
    • 7.2  Linux進(jìn)程控制編程
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請(qǐng)入駐 產(chǎn)業(yè)圖譜

進(jìn)程控制開(kāi)發(fā)之:Linux進(jìn)程控制編程

2013/09/13
1
閱讀需 38 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

?

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的定義
#include?<unistd.h>

函數(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é)束。
在實(shí)際編程時(shí),可以用wait()系統(tǒng)調(diào)用接收子進(jìn)程的返回值,從而針對(duì)不同的情況進(jìn)行不同的處理

(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>
#include?<sys/wait.h>

函數(shù)原型

pid_t?wait(int?*status)

函數(shù)傳入值

這里的status是一個(gè)整型指針,是該子進(jìn)程退出時(shí)的狀態(tài)
·??status若不為空,則通過(guò)它可以獲得子進(jìn)程的結(jié)束狀態(tài)
另外,子進(jìn)程的結(jié)束狀態(tài)可由Linux中一些特定的宏來(lái)測(cè)定

函數(shù)返回值

成功:已結(jié)束運(yùn)行的子進(jìn)程的進(jìn)程號(hào)
失?。?1

表7.7列出了waitpid()函數(shù)的語(yǔ)法規(guī)范。

表7.7 waitpid()函數(shù)語(yǔ)法

所需頭文件

#include?<sys/types.h>
#include?<sys/wait.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)程退出為止。

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

華清遠(yuǎn)見(jiàn)(www.farsight.com.cn)是國(guó)內(nèi)領(lǐng)先嵌入師培訓(xùn)機(jī)構(gòu),2004年注冊(cè)于中國(guó)北京海淀高科技園區(qū),除北京總部外,上海、深圳、成都、南京、武漢、西安、廣州均有直營(yíng)分公司。華清遠(yuǎn)見(jiàn)除提供嵌入式相關(guān)的長(zhǎng)期就業(yè)培訓(xùn)、短期高端培訓(xùn)、師資培訓(xùn)及企業(yè)員工內(nèi)訓(xùn)等業(yè)務(wù)外,其下屬研發(fā)中心還負(fù)責(zé)嵌入式、Android及物聯(lián)網(wǎng)方向的教學(xué)實(shí)驗(yàn)平臺(tái)的研發(fā)及培訓(xùn)教材的出版,截止目前為止已公開(kāi)出版70余本嵌入式/移動(dòng)開(kāi)發(fā)/物聯(lián)網(wǎng)相關(guān)圖書。企業(yè)理念:專業(yè)始于專注 卓識(shí)源于遠(yuǎn)見(jiàn)。企業(yè)價(jià)值觀:做良心教育、做專業(yè)教育,更要做受人尊敬的職業(yè)教育。