1. 程式人生 > >Linux系統程式設計——程序的控制:結束程序、等待程序結束

Linux系統程式設計——程序的控制:結束程序、等待程序結束

結束程序

首先,我們回顧一下 C 語言中 continue, break, return 的作用:

continue: 結束本次迴圈

break: 跳出整個迴圈,或跳出 switch() 語句

return: 結束當前函式

而我們可以通過 exit() 或 _exit() 來結束當前程序。

所需標頭檔案:

#include <stdlib.h> 

void exit(int value);

功能:

結束呼叫此函式的程序。

引數:

status:返回給父程序的引數(低 8 位有效),至於這個引數是多少根據需要來填寫。

返回值:

所需標頭檔案:

#include <unistd.h>

void _exit(int value);

功能:

結束呼叫此函式的程序。

引數:

status:返回給父程序的引數(低 8 位有效),至於這個引數是多少根據需要來填寫。

返回值:

exit() 和 _exit() 函式功能和用法是一樣的,無非時所包含的標頭檔案不一樣,還有的區別就是:exit()屬於標準庫函式,_exit()屬於系統呼叫函式。


下面的例子驗證呼叫 exit() 函式,會重新整理 I/O 緩衝區(關於緩衝區的更多詳情,請看《淺談標準I/O緩衝區》):

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. int main(int argc, char *argv[])  
  5. {  
  6.     printf("hi, mike, you are so good"); // 列印,沒有換行符"\n"
  7.     exit(0);      // 結束程序,標準庫函式,重新整理緩衝區,printf()的內容能打印出來
  8.     // _exit(0);  // 結束程序,系統呼叫函式,printf()的內容不會顯示到螢幕
  9.     while(1);   // 不讓程式結束
  10.     return 0;  
  11. }  

執行結果如下:


上面的例子,結束程序的時候改為呼叫 _exit(),程式碼如下:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. int main(int argc, char *argv[])  
  5. {  
  6.     printf("hi, mike, you are so good"); // 列印,沒有換行符"\n"
  7.     //exit(0);      // 結束程序,標準庫函式,重新整理緩衝區,printf()的內容能打印出來
  8.     _exit(0);  // 結束程序,系統呼叫函式,printf()的內容不會顯示到螢幕
  9.     while(1);   // 不讓程式結束
  10.     return 0;  
  11. }  

printf() 的內容是不會顯示出來的,執行結果如下:


接下來,我們一起驗證一下結束函式( return )和結束程序( exit() )的區別。

測試程式碼如下:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. void fun()  
  5. {  
  6.     sleep(2);  
  7.     return;  // 結束 fun() 函式
  8.     while(1);  
  9. }  
  10. int main(int argc, char *argv[])  
  11. {  
  12.     fun();  
  13.     printf("after fun\n");  
  14.     while(1);   // 不讓程式結束
  15.     return 0;  
  16. }  

執行結果如下:


通過上面的執行結果得知,return 的作用只是結束呼叫 return 的所在函式,只要這個函式不是主函式( main() ),只要主函式沒有結束,return 並不能結束程序

我們將上面例子 fun() 裡的 return 改為 exit():

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. void fun()  
  5. {  
  6.     sleep(2);  
  7.     exit(0);  // 結束當前程序
  8.     while(1);  
  9. }  
  10. int main(int argc, char *argv[])  
  11. {  
  12.     fun();  
  13.     printf("after fun\n");  
  14.     while(1);   // 不讓程式結束
  15.     return 0;  
  16. }  



exit() 是可以結束 fun() 所在的程序,即可讓程式結束執行,結果圖如下:


等待程序結束

當一個程序正常或異常終止時,核心就向其父程序傳送 SIGCHLD 訊號,相當於告訴父親他哪個兒子掛了,而父程序可以通過 wait() 或 waitpid() 函式等待子程序結束,獲取子程序結束時的狀態,同時回收他們的資源(相當於,父親聽聽死去兒子的遺言同時好好安葬它)。

wait() 和 waitpid() 函式的功能一樣,區別在於,wait() 函式會阻塞,waitpid() 可以設定不阻塞,waitpid() 還可以指定等待哪個子程序結束。

所需標頭檔案:

#include <sys/types.h>

#include <sys/wait.h>

pid_t wait(int *status);

功能:

等待任意一個子程序結束,如果任意一個子程序結束了,此函式會回收該子程序的資源。

在每個程序退出的時候,核心釋放該程序所有的資源、包括開啟的檔案、佔用的記憶體等。但是仍然為其保留一定的資訊,這些資訊主要主要指程序控制塊的資訊(包括程序號、退出狀態、執行時間等)。

呼叫 wait() 函式的程序會掛起(阻塞),直到它的一個子程序退出或收到一個不能被忽視的訊號時才被喚醒(相當於繼續往下執行)。

若呼叫程序沒有子程序,該函式立即返回;若它的子程序已經結束,該函式同樣會立即返回,並且會回收那個早已結束程序的資源。

所以,wait()函式的主要功能為回收已經結束子程序的資源。

引數:

status: 程序退出時的狀態資訊。

如果引數 status 的值不是 NULL,wait() 就會把子程序退出時的狀態取出並存入其中,這是一個整數值(int),指出了子程序是正常退出還是被非正常結束的。

這個退出資訊在一個 int 中包含了多個欄位,直接使用這個值是沒有意義的,我們需要用巨集定義取出其中的每個欄位

下面我們來學習一下其中最常用的兩個巨集定義,取出子程序的退出資訊:

WIFEXITED(status)

如果子程序是正常終止的,取出的欄位值非零。

WEXITSTATUS(status)

返回子程序的退出狀態,退出狀態儲存在 status 變數的 8~16 位。在用此巨集前應先用巨集 WIFEXITED 判斷子程序是否正常退出,正常退出才可以使用此巨集。

返回值:

成功:已經結束子程序的程序號

失敗:-1

從本質上講,系統呼叫 waitpid() 和 wait() 的作用是完全相同的,但 waitpid() 多出了兩個可由使用者控制的引數 pid 和 options,從而為我們程式設計提供了另一種更靈活的方式。

pid_t waitpid(pid_t pid, int *status, int options);

功能:

等待子程序終止,如果子程序終止了,此函式會回收子程序的資源。

引數:

pid: 引數 pid 的值有以下幾種型別:

pid > 0

等待程序 ID 等於 pid 的子程序。

pid = 0

等待同一個程序組中的任何子程序,如果子程序已經加入了別的程序組,waitpid 不會等待它。

pid = -1

等待任一子程序,此時 waitpid 和 wait 作用一樣。

pid < -1

等待指定程序組中的任何子程序,這個程序組的 ID 等於 pid 的絕對值。

status: 程序退出時的狀態資訊。和 wait() 用法一樣。

options: options 提供了一些額外的選項來控制 waitpid()。

0

同 wait(),阻塞父程序,等待子程序退出。

WNOHANG

沒有任何已經結束的子程序,則立即返回。

WUNTRACED

如果子程序暫停了則此函式馬上返回,並且不予以理會子程序的結束狀態。(由於涉及到一些跟蹤除錯方面的知識,加之極少用到,這裡就不多費筆墨了,有興趣的讀者可以自行查閱相關材料)

返回值:

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

當正常返回的時候,waitpid() 返回收集到的已經子程序的程序號;

如果設定了選項 WNOHANG,而呼叫中 waitpid() 發現沒有已退出的子程序可等待,則返回 0;

如果呼叫中出錯,則返回 -1,這時 errno 會被設定成相應的值以指示錯誤所在,如:當 pid 所對應的子程序不存在,或此程序存在,但不是呼叫程序的子程序,waitpid() 就會出錯返回,這時 errno 被設定為 ECHILD;

測試例子:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <sys/types.h>
  5. #include <sys/wait.h>
  6. int main(int argc, char *argv[])  
  7. {  
  8.     pid_t pid;  
  9.     pid = fork(); // 建立程序
  10.     if( pid < 0 ){ // 出錯
  11.         perror("fork");  
  12.         exit(0);  
  13.     }  
  14.     if( pid == 0 ){// 子程序
  15.         int i = 0;  
  16.         for(i=0;i<5;i++)  
  17.         {  
  18.             printf("this is son process\n");  
  19.             sleep(1);  
  20.         }  
  21.         _exit(2); // 子程序退出,數字 2 為子程序退出的狀態
  22.     }elseif( pid > 0){ // 父程序
  23.         int status = 0;  
  24.         // 等待子程序結束,回收子程序的資源
  25.         // 此函式會阻塞
  26.         // status 某個欄位儲存子程序呼叫 _exit(2) 的 2,需要用巨集定義取出
  27.         wait(&status);   
  28.         // waitpid(-1, &status, 0); // 和 wait() 沒區別,0:阻塞
  29.         // waitpid(pid, &status, 0); // 指定等待程序號為 pid 的子程序, 0 阻塞
  30.         // waitpid(pid, &status, WNOHANG); // WNOHANG:不阻塞
  31.         if(WIFEXITED(status) != 0){ // 子程序是否正常終止
  32.             printf("son process return %d\n", WEXITSTATUS(status));  
  33.         }  
  34.         printf("this is father process\n");   
  35.     }  
  36.     return 0;  
  37. }  

執行結果如下: