1. 程式人生 > >Linux中wait()函式

Linux中wait()函式

 

程式設計過程中,有時需要讓一個程序等待另一個程序,最常見的是父程序等待自己的子程序,或者父程序回收自己的子程序資源包括殭屍程序。這裡簡單介紹一下系統呼叫函式:wait()

函式原型是

#include <sys/types.h>

#include <wait.h>

int wait(int *status)

函式功能是:父程序一旦呼叫了wait就立即阻塞自己,由wait自動分析是否當前程序的某個子程序已經退出,如果讓它找到了這樣一個已經變成殭屍的子程序,wait就會收集這個子程序的資訊,並把它徹底銷燬後返回;如果沒有找到這樣一個子程序,wait就會一直阻塞在這裡,直到有一個出現為止。

注:

  當父程序忘了用wait()函式等待已終止的子程序時,子程序就會進入一種無父程序的狀態,此時子程序就是殭屍程序.

  wait()要與fork()配套出現,如果在使用fork()之前呼叫wait(),wait()的返回值則為-1,正常情況下wait()的返回值為子程序的PID.

  如果先終止父程序,子程序將繼續正常進行,只是它將由init程序(PID 1)繼承,當子程序終止時,init程序捕獲這個狀態.

  引數status用來儲存被收集程序退出時的一些狀態,它是一個指向int型別的指標。但如果我們對這個子程序是如何死掉毫不在意,只想把這個殭屍程序消滅掉,(事實上絕大多數情況下,我們都會這樣想),我們就可以設定這個引數為NULL,就像下面這樣:

pid = wait(NULL);

如果成功,wait會返回被收集的子程序的程序ID,如果呼叫程序沒有子程序,呼叫就會失敗,此時wait返回-1,同時errno被置為ECHILD。

  如果引數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,這個值就毫無意義。

 

程式碼示例:wait.c

複製程式碼

 1 #include <stdio.h>
 2 #include <sys/stat.h>
 3 #include <sys/types.h>
 4 #include <unistd.h>
 5 #include <wait.h>
 6 #include <errno.h>
 7 #include <stdlib.h>
 8 /***********************************************************
 9     功能說明:程序等待wait()方法的應用
10     author: [email protected]
11 
12 ***********************************************************/
13 void waitprocess();
14 
15 
16 int main(int argc, char * argv[])
17 {
18   waitprocess();
19   
20 }
21 
22 void waitprocess()
23 {
24 
25   int count = 0;
26 
27   pid_t pid = fork();
28   int status = -1;
29   
30   if(pid<0)
31   {
32     printf("fork error for %m\n",errno );
33   }else if(pid>0)
34   {
35     printf("this is parent ,pid = %d\n",getpid() );
36     wait(&status);//父程序執行到此,馬上阻塞自己,直到有子程序結束。當發現有子程序結束時,就會回收它的資源。
37     
38   }else
39   {
40     printf("this is child , pid = %d , ppid = %d\n",getpid(),getppid() );
41     int i;
42     
43     for (i = 0; i < 10; i++) {
44       count++;
45       sleep(1);
46       
47       printf("count = %d\n", count)  ;
48       
49     }
50 
51     exit(5);
52     
53   }
54   printf("child exit status is %d\n", WEXITSTATUS(status));//status是按位儲存的狀態資訊,需要呼叫相應的巨集來還原一下
55   
56   printf("end of program from pid = %d\n",getpid() );
57   
58   
59 }

複製程式碼

執行結果:

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多出了兩個可由使用者控制的引數pidoptions,從而為我們程式設計提供了另一種更靈活的方式。下面我們就來詳細介紹一下這兩個引數:

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中只支援WNOHANGWUNTRACED兩個選項,這是兩個常數,可以用"|"運算子把它們連線起來使用,比如:

     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);

}

返回值和錯誤

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>

int 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秒鐘的工作。父子程序都有工作要做,父程序利用工作的簡短間歇察看子程序的是否退出,如退出就收集它。

提示:可以嘗試在最後一個例子中把pr=waitpid(pc, NULL, WNOHANG); 改為pr=waitpid(pc, NULL, 0);或者pr=wait(NULL);看看執行結果有何變化?(修改後的結果使得父程序將自己阻塞,直到有子程序退出為止!)