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緩衝區》):
-
#include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- int main(int argc, char *argv[])
- {
- printf("hi, mike, you are so good"); // 列印,沒有換行符"\n"
- exit(0); // 結束程序,標準庫函式,重新整理緩衝區,printf()的內容能打印出來
- // _exit(0); // 結束程序,系統呼叫函式,printf()的內容不會顯示到螢幕
- while(1); // 不讓程式結束
- return 0;
- }
執行結果如下:
上面的例子,結束程序的時候改為呼叫 _exit(),程式碼如下:
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- int main(int argc, char *argv[])
- {
- printf("hi, mike, you are so good"); // 列印,沒有換行符"\n"
- //exit(0); // 結束程序,標準庫函式,重新整理緩衝區,printf()的內容能打印出來
- _exit(0); // 結束程序,系統呼叫函式,printf()的內容不會顯示到螢幕
- while(1); // 不讓程式結束
- return 0;
- }
printf() 的內容是不會顯示出來的,執行結果如下:
接下來,我們一起驗證一下結束函式( return )和結束程序( exit() )的區別。
測試程式碼如下:
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- void fun()
- {
- sleep(2);
- return; // 結束 fun() 函式
- while(1);
- }
- int main(int argc, char *argv[])
- {
- fun();
- printf("after fun\n");
- while(1); // 不讓程式結束
- return 0;
- }
執行結果如下:
通過上面的執行結果得知,return 的作用只是結束呼叫 return 的所在函式,只要這個函式不是主函式( main() ),只要主函式沒有結束,return 並不能結束程序。
我們將上面例子 fun() 裡的 return 改為 exit():
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- void fun()
- {
- sleep(2);
- exit(0); // 結束當前程序
- while(1);
- }
- int main(int argc, char *argv[])
- {
- fun();
- printf("after fun\n");
- while(1); // 不讓程式結束
- return 0;
- }
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;
測試例子:
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/wait.h>
- int main(int argc, char *argv[])
- {
- pid_t pid;
- pid = fork(); // 建立程序
- if( pid < 0 ){ // 出錯
- perror("fork");
- exit(0);
- }
- if( pid == 0 ){// 子程序
- int i = 0;
- for(i=0;i<5;i++)
- {
- printf("this is son process\n");
- sleep(1);
- }
- _exit(2); // 子程序退出,數字 2 為子程序退出的狀態
- }elseif( pid > 0){ // 父程序
- int status = 0;
- // 等待子程序結束,回收子程序的資源
- // 此函式會阻塞
- // status 某個欄位儲存子程序呼叫 _exit(2) 的 2,需要用巨集定義取出
- wait(&status);
- // waitpid(-1, &status, 0); // 和 wait() 沒區別,0:阻塞
- // waitpid(pid, &status, 0); // 指定等待程序號為 pid 的子程序, 0 阻塞
- // waitpid(pid, &status, WNOHANG); // WNOHANG:不阻塞
- if(WIFEXITED(status) != 0){ // 子程序是否正常終止
- printf("son process return %d\n", WEXITSTATUS(status));
- }
- printf("this is father process\n");
- }
- return 0;
- }
執行結果如下: