linux程式控制-wait()

yangdelong發表於2009-09-26

http://blog.chinaunix.net/u1/53053/showart_425197.html

 

#include <sys/types.h> /* 提供型別pid_t的定義 */
#include <sys/wait.h>
pid_t wait(int *status)
程式一旦呼叫了wait,就立即阻塞自己,由wait自動分析是否當前程式的某個子程式已經退出,如果讓它找到了這樣一個已經變成殭屍的子程式,wait就會收集這個子程式的資訊,並把它徹底銷燬後返回;如果沒有找到這樣一個子程式,wait就會一直阻塞在這裡,直到有一個出現為止。
引數status用來儲存被收集程式退出時的一些狀態,它是一個指向int型別的指標。但如果我們對這個子程式是如何死掉的毫不在意,只想把這個殭屍程式消滅掉,(事實上絕大多數情況下,我們都會這樣想),我們就可以設定這個引數為NULL,就象下面這樣:
		pid = wait(NULL);
如果成功,wait會返回被收集的子程式的程式ID,如果呼叫程式沒有子程式,呼叫就會失敗,此時wait返回-1,同時errno被置為ECHILD。


下面就讓我們用一個例子來實戰應用一下wait呼叫:

/* wait1.c */
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
main()
{
pid_t pc,pr;
pc=fork();
if(pc<0) /* 如果出錯 */
printf("error ocurred!/n");
else if(pc==0){ /* 如果是子程式 */
printf("This is child process with pid of %d/n",getpid());
sleep(10); /* 睡眠10秒鐘 */
}
else{ /* 如果是父程式 */
pr=wait(NULL); /* 在這裡等待 */
printf("I catched a child process with pid of %d/n"),pr);
}
exit(0);
}

編譯並執行:

$ cc wait1.c -o wait1
$ ./wait1
This is child process with pid of 1508
I catched a child process with pid of 1508
可以明顯注意到,在第2行結果列印出來前有10 秒鐘的等待時間,這就是我們設定的讓子程式睡眠的時間,只有子程式從睡眠中甦醒過來,它才能正常退出,也就才能被父程式捕捉到。其實這裡我們不管設定子程式睡眠的時間有多長,父程式都會一直等待下去,讀者如果有興趣的話,可以試著自己修改一下這個數值,看看會出現怎樣的結果。

引數status:

如果引數status的值不是NULL,wait就會把子程式退出時的狀態取出並存入其中,這是一個整數值(int),指出了子程式是正常退出還是被非正常結束的(一個程式也可以被其他程式用訊號結束,我們將在以後的文章中介紹),以及正常結束時的返回值,或被哪一個訊號結束的等資訊。由於這些資訊被存放在一個整數的不同二進位制位中,所以用常規的方法讀取會非常麻煩,人們就設計了一套專門的巨集(macro)來完成這項工作,下面我們來學習一下其中最常用的兩個:

1,WIFEXITED(status) 這個巨集用來指出子程式是否為正常退出的,如果是,它會返回一個非零值。

(請注意,雖然名字一樣,這裡的引數status並不同於wait唯一的引數--指向整數的指標status,而是那個指標所指向的整數,切記不要搞混了。)

2, WEXITSTATUS(status) 當WIFEXITED返回非零值時,我們可以用這個巨集來提取子程式的返回值,如果子程式呼叫exit(5)退出,WEXITSTATUS(status) 就會返回5;如果子程式呼叫exit(7),WEXITSTATUS(status)就會返回7。請注意,如果程式不是正常退出的,也就是說, WIFEXITED返回0,這個值就毫無意義。

下面通過例子來實戰一下我們剛剛學到的內容:

/* wait2.c */
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
main()
{
int status;
pid_t pc,pr;
pc=fork();
if(pc<0) /* 如果出錯 */
printf("error ocurred!/n");
else if(pc==0){ /* 子程式 */
printf("This is child process with pid of %d./n",getpid());
exit(3); /* 子程式返回3 */
}
else{ /* 父程式 */
pr=wait(&status);
if(WIFEXITED(status)){ /* 如果WIFEXITED返回非零值 */
printf("the child process %d exit normally./n",pr);
printf("the return code is %d./n",WEXITSTATUS(status));
}else /* 如果WIFEXITED返回零 */
printf("the child process %d exit abnormally./n",pr);
}
}
編譯並執行:
$ cc wait2.c -o wait2
$ ./wait2
This is child process with pid of 1538.
the child process 1538 exit normally.
the return code is 3.
父程式準確捕捉到了子程式的返回值3,並把它列印了出來。

當然,處理程式退出狀態的巨集並不止這兩個,但它們當中的絕大部分在平時的程式設計中很少用到,就也不在這裡浪費篇幅介紹了,有興趣的讀者可以自己參閱Linux man pages去了解它們的用法。

程式同步:

有時候,父程式要求子程式的運算結果進行下一步的運算,或者子程式的功能是為父程式提供了下一步執行的先決條件(如:子程式建立檔案,而父程式寫入資料),此時父程式就必須在某一個位置停下來,等待子程式執行結束,而如果父程式不等待而直接執行下去的話,可以想見,會出現極大的混亂。這種情況稱為程式之間的同步,更準確地說,這是程式同步的一種特例。程式同步就是要協調好2個以上的程式,使之以安排好地次序依次執行。解決程式同步問題有更通用的方法,我們將在以後介紹,但對於我們假設的這種情況,則完全可以用wait系統呼叫簡單的予以解決。請看下面這段程式:

#include <sys/types.h>
#include <sys/wait.h>
main()
{
pid_t pc, pr;
int status;

pc=fork();

if(pc<0)
printf("Error occured on forking./n");
else if(pc==0){
/* 子程式的工作 */
exit(0);
}else{
/* 父程式的工作 */
pr=wait(&status);
/* 利用子程式的結果 */
}
}
這段程式只是個例子,不能真正拿來執行,但它卻說明了一些問題,首先,當fork呼叫成功後,父子程式各做各的事情,但當父程式的工作告一段落,需要用到子程式的結果時,它就停下來呼叫wait,一直等到子程式執行結束,然後利用子程式的結果繼續執行,這樣就圓滿地解決了我們提出的程式同步問題。





waitpid系統呼叫在Linux函式庫中的原型是:
#include <sys/types.h> /* 提供型別pid_t的定義 */
#include <sys/wait.h>
pid_t waitpid(pid_t pid,int *status,int options)
從本質上講,系統呼叫waitpid和wait的作用是完全相同的,但waitpid多出了兩個可由使用者控制的引數pid和options,從而為我們程式設計提供了另一種更靈活的方式。下面我們就來詳細介紹一下這兩個引數:

pid

從引數的名字pid和型別pid_t中就可以看出,這裡需要的是一個程式ID。但當pid取不同的值時,在這裡有不同的意義。

  1. pid>0時,只等待程式ID等於pid的子程式,不管其它已經有多少子程式執行結束退出了,只要指定的子程式還沒有結束,waitpid就會一直等下去。
  2. pid=-1時,等待任何一個子程式退出,沒有任何限制,此時waitpid和wait的作用一模一樣。
  3. pid=0時,等待同一個程式組中的任何子程式,如果子程式已經加入了別的程式組,waitpid不會對它做任何理睬。
  4. pid<-1時,等待一個指定程式組中的任何子程式,這個程式組的ID等於pid的絕對值。

options

options提供了一些額外的選項來控制waitpid,目前在Linux中只支援WNOHANG和WUNTRACED兩個選項,這是兩個常數,可以用"|"運算子把它們連線起來使用,比如:

ret=waitpid(-1,NULL,WNOHANG | WUNTRACED);
如果我們不想使用它們,也可以把options設為0,如:
ret=waitpid(-1,NULL,0);
如果使用了WNOHANG引數呼叫waitpid,即使沒有子程式退出,它也會立即返回,不會像wait那樣永遠等下去。

而WUNTRACED引數,由於涉及到一些跟蹤除錯方面的知識,加之極少用到,這裡就不多費筆墨了,有興趣的讀者可以自行查閱相關材料。

看到這裡,聰明的讀者可能已經看出端倪了--wait不就是經過包裝的waitpid嗎?沒錯,察看<核心原始碼目錄>/include/unistd.h檔案349-352行就會發現以下程式段:

static inline pid_t wait(int * wait_stat)
{
return waitpid(-1,wait_stat,0);
}

1.9.2 返回值和錯誤

waitpid的返回值比wait稍微複雜一些,一共有3種情況:

  1. 當正常返回的時候,waitpid返回收集到的子程式的程式ID;
  2. 如果設定了選項WNOHANG,而呼叫中waitpid發現沒有已退出的子程式可收集,則返回0;
  3. 如果呼叫中出錯,則返回-1,這時errno會被設定成相應的值以指示錯誤所在;

當pid所指示的子程式不存在,或此程式存在,但不是呼叫程式的子程式,waitpid就會出錯返回,這時errno被設定為ECHILD;

/* waitpid.c */
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
main()
{
pid_t pc, pr;

pc=fork();
if(pc<0) /* 如果fork出錯 */
printf("Error occured on forking./n");
else if(pc==0){ /* 如果是子程式 */
sleep(10); /* 睡眠10秒 */
exit(0);
}
/* 如果是父程式 */
do{
pr=waitpid(pc, NULL, WNOHANG); /* 使用了WNOHANG引數,waitpid不會在這裡等待 */
if(pr==0){ /* 如果沒有收集到子程式 */
printf("No child exited/n");
sleep(1);
}
}while(pr==0); /* 沒有收集到子程式,就回去繼續嘗試 */
if(pr==pc)
printf("successfully get child %d/n", pr);
else
printf("some error occured/n");
}

編譯並執行:

$ cc waitpid.c -o waitpid
$ ./waitpid
No child exited
No child exited
No child exited
No child exited
No child exited
No child exited
No child exited
No child exited
No child exited
No child exited
successfully get child 1526

父程式經過10次失敗的嘗試之後,終於收集到了退出的子程式。

因為這只是一個例子程式,不便寫得太複雜,所以我們就讓父程式和子程式分別睡眠了10秒鐘和1秒鐘,代表它們分別作了10秒鐘和1秒鐘的工作。父子程式都有工作要做,父程式利用工作的簡短間歇察看子程式的是否退出,如退出就收集它。

相關文章