UNIX再学习 -- exit 和 wait 系列函数
生活随笔
收集整理的這篇文章主要介紹了
UNIX再学习 -- exit 和 wait 系列函数
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
我們一開始講進程環境時,就有提到了。進程有 8 種方式使進程終止。
其中 5 種為正常終止,它們是:
(1)在 main 函數中執行 return
(2)調用 exit 函數,并不處理文件描述符,多進程
(3)調用 _exit 或 _Exit
(4)最后一個線程從其啟動例程返回
(5)從最后一個線程調用 pthread_exit
異常終止有 3 種方式,它們是:
(6)調用 abort,產生 SIGABRT 信號
(7)進程接收到某些信號
(8)最后一個線程對“取消”請求做出響應?
我們之前是講過的,參看:C語言再學習 -- 關鍵字return和exit ()函數? ?接下來重點介紹 exit 和 wait 系列函數。
一、exit 系列函數
1、exit 函數
#include <stdlib.h> void exit(int status)(1)函數功能
正常終止調用進程(2)參數解析
status:進程退出碼,相當于 main 函數的返回值。被終止的進程的父進程可以通過 wait 或 waitpid 函數,獲取 status 參數的低 8 位,以了解導致該進程終止的具體原因。exit(0)表示正常退出,exit(x)(x不為0)都表示異常退出,這個 x 是返回給操作系統(包括UNIX,Linux,和MS DOS)的,以供其他程序使用。通常情況下,程序成功執行完一個操作正常退出的時候會帶有值 EXIT_SUCCESS。在這里,EXIT_SUCCESS 是宏,它被定義為 0。如果程序中存在一種錯誤情況,當您退出程序時,會帶有狀態值EXIT_FAILURE,被定義為 1。
標準C中有 EXIT_SUCCESS 和 EXIT_FAILURE 兩個宏,位于?/usr/include/stdlib.h中:
#define EXIT_FAILURE 1 /* Failing exit status. */ #define EXIT_SUCCESS 0 /* Successful exit status. */
(3)示例說明
#include <stdio.h> #include <stdlib.h> int main () { printf("程序開頭....\n"); printf("退出程序....\n"); exit(0); //等同 return 0;printf("程序結尾....\n"); return(0); } 輸出結果: 程序開頭.... 退出程序....(4)進階
exit 和 return 的區別,我就不再重復了,之前講的夠詳細了?,F在講點新的知識點。exit 函數在終止調用進程之前還會做三件收尾工作:1》》調用實現通過 atexit 或 on_exit 函數注冊的退出處理函數。
注冊退出函數 atexit
#include <stdlib.h> int atexit(void (*function)(void)); 成功返回 0, 失敗返回非 0參數解析:function 為退出處理函數指針,所指向的函數既無返回值亦無參數,在進程終止前被調用,為進程料理臨終事宜。函數解析:注意 atexit 函數本身并不調用退出處理函數,而只是將 function 參數所表示的退出處理函數地址,保存(注冊)在系統內核的某個地方(進程表項)。待到 exit 函數被調用或在 main 函數里執行 return 語句時,再由系統內核根據這些退出處理函數的地址來調用它們。此過程亦稱回調。ISO C規定,一個進程最多可登記32個終止處理函數,這些函數由 exit 按登記相反的順序自動調用。如果同一函數登記多次,也會被調用多次。示例說明:#include <stdlib.h> #include <stdio.h> //所指向的函數既無返回值亦無參數 void my_exit1 (void) { printf ("first exit handler\n"); } void my_exit2 (void) { printf ("second exit handler\n"); } int main() { atexit (my_exit2); atexit (my_exit1); atexit (my_exit1); //回調順序正好和注冊順序相反printf ("main is done\n"); return 0; // 相當于exit(0) } 輸出結果: main is done first exit handler first exit handler second exit handler
注冊退出函數 on_exit
#include <stdlib.h> int on_exit(void (*function)(int , void *), void *arg); 成功返回 0,失敗返回非 0參數解析:第一個參數:退出處理函數指針,所指向的函數無返回值但有兩個參數。其中第一個參數來自傳遞給 exit 函數的 status 參數或在 main 函數里執行 return 語句的返回值,而第二個參數來自傳遞給 on_exit?函數的 arg 參數。該函數在進程終止前被調用,為進程料理臨終事宜。第二個參數:泛型指針,將作為第二個參數傳遞給 function 所指向的退出處理函數。示例說明:#include <stdlib.h> #include <stdio.h> //所指向的函數無返回值但有兩個參數 void my_exit1 (int status, void *arg) { printf ("%s", (char*)arg); printf ("status = %d\n", status); } void my_exit2 (int status, void *arg) { printf ("%s", (char*)arg); } int main() { on_exit (my_exit2, "second exit handler\n"); on_exit (my_exit1, "first exit handler\n"); on_exit (my_exit1, "first exit handler\n"); //回調順序正好和注冊順序相反printf ("main is done\n"); return 1; // 相當于exit(1) } 輸出結果: main is done first exit handler status = 1 first exit handler status = 1 second exit handler2》》沖刷并關閉所有仍處于打開狀態的標準 I/O 流。
仍處于打開狀態的標準 I/O 流,即緩沖 I/O。其特征就是對應每一個打開的文件,在內存中都有一片緩沖區。每次讀文件時,會連續讀出若干條記錄,這樣在下次讀文件時就可以直接從內存的緩沖區中讀取;同樣,每次寫文件時,也僅僅是寫入內存中的緩沖區,等滿足了一定的條件(如達到一定數量或遇到特定字符等,最典型的就是咱們的vim中使用的:w命令),再將緩沖區中的內容一次性寫入文件。這種技術大大增加了文件讀寫的速度,但也給咱們的編程帶來了一些麻煩。比如有些數據你認為已經被寫入到文件中,實際上因為沒有滿足特定的條件,它們還只是被保存在緩沖區內,這時用 _exit() 函數直接將進程關閉掉,緩沖區中的數據就會丟失。因此,若想保證數據的完整性,最好使用 exit() 函數。
緩沖區之前講過很多次了,參看:UNIX再學習 -- 標準I/O舉個例子://示例一 #include <stdio.h> #include <stdlib.h> int main (void) {printf ("111111111\n");printf ("222222222"); //沖刷了緩沖區exit (0); } 輸出結果: 111111111 222222222//示例二 #include <stdio.h> #include <stdlib.h> #include <unistd.h>int main (void) {printf ("111111111\n");printf ("222222222"); //緩沖區數據丟失_exit (0); } 輸出結果: 111111111使用 fflush() 沖刷緩沖:#include <stdio.h> #include <unistd.h>int main (void) {printf ("11111111111111\n");printf ("22222222222222");fflush (stdout);_exit (0); } 輸出結果: 11111111111111 22222222222222
3》》刪除所有通過 tmpfile 函數創建的臨時文件。
暫時不講 tempfile 。感興趣可自行 man tempfile 。最后注意,exit 并不處理文件描述符、多進程(父進程和子進程)以及作業控制。在 exit 函數的內部調用了更底層的系統調用函數 _exit,后者也可以被用戶空間的代碼直接調用,比如在用 vfork 函數創建的子進程里。
2、_exit/_Exit 函數
#include <unistd.h> void _exit(int status); #include <stdlib.h> void _Exit(int status); 不返回(1)函數功能
正常終止調用進程(2)參數解析
status:進程退出碼,相當于 main 函數的返回值。(3)函數解析
_exit 是UC函數,_Exit 是標C函數。在UNIX系統中,兩者是同義的,并不沖洗標準 I/O 流。?_exit 在終止調用進程之前也會做三件收尾工作,但與 exit 函數所做的不同。事實上,exit 函數在做完它那三件收尾工作之后緊接著就會調用 _exit 函數。1》》關閉所有仍處于打開狀態的文件描述符2》》將調用進程的所有子進程托付給 init 進程收養3》》向調用進程的父進程發送 SIGCHLD (17) 信號從這三件收尾工作可以看出,它們所影響完全都是調用進程自己的資源,與其父進程沒有任何關系。這對用 vfork 函數創建的子進程而言顯得尤其重要。因為用 vfork 函數創建的子進程存在太多與其父進程共享的資源。如果在子進程里調用了 exit 函數或者在子進程的 main 函數里執行了 return 語句(這兩者本質上是等價的),不僅銷毀了子進程的資源,同時也銷毀了父進程的資源,而父進程恰恰要在子進程終止以后從掛起中恢復運行,其后果可想而知。(4)示例說明
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> int main(void) { //使用vfork函數創建子進程 pid_t pid = vfork(); if(-1 == pid) { perror("vfork"),exit(-1); } if(0 == pid) //子進程 { printf("子進程%d開始運行\n",getpid()); sleep(3); printf("子進程結束\n"); //子進程不退出,則結果不可預知 _exit(0);//終止子進程 } printf("父進程%d開始執行\n",getpid()); printf("父進程結束\n"); return 0; } 輸出結果: 子進程2762開始運行 子進程結束 父進程2761開始執行 父進程結束3、pthread_exit 函數
不是今天的重點,等到講完 線程回過頭再詳細講它。 #include <pthread.h> void pthread_exit(void *retval);(1)函數功能
主要用于終止正在運行的線程,通過參數 retval 來帶出線程的退出狀態信息,在同一個進程中的其他線程可以通過調用 pthread_join 函數來獲取退出狀態信息。(2)示例說明
#include <stdio.h> #include <stdlib.h> #include <pthread.h> void* task(void* p) { static int i=0; // int i=0; for(i=0;i<=100;i++) { if(i==10) { pthread_exit((void*)&i); } printf("子進程中:i=%d\n",i); } } int main() { //1.啟動線程打印1~100之間的數 pthread_t tid; pthread_create(&tid,NULL,task,NULL); //2.等待子進程結束,并且獲取返回值 int* pi=NULL; pthread_join(tid,(void**)&pi); printf("子線程中變量的值是:%d\n",*pi); return 0; } 編譯:gcc test.c -pthread 輸出結果: 子進程中:i=0 子進程中:i=1 子進程中:i=2 子進程中:i=3 子進程中:i=4 子進程中:i=5 子進程中:i=6 子進程中:i=7 子進程中:i=8 子進程中:i=9 子線程中變量的值是:10二、wait 系列函數
下面我們來講一下 wait 系列函數。1、wait 函數?
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *status); 成功返回所回收子進程的 PID,失敗返回 -1(1)函數功能
主要用于掛起正在運行的進程進入等待狀態,直到有一個子進程終止。(2)參數解析
status 是一個整型指針,如果 status 不是一個空指針,則終止進程的終止狀態就存放在它所指向的單元內。如果不關心終止狀態,則可將該參數指定為空指針。(3)函數解析
1》》父進程在創建若干子進程以后調用 wait 函數
若所有子進程都在運行,則阻塞,直至有子進程終止。 若有一個子進程已終止,則返回該子進程的 PID 并通過 status 參數 (若非 NULL)輸出其終止狀態。 若沒有需要等待的子進程,則返回 -1,置 error 為 ECHILD。示例說明:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> #include <errno.h>int main(void) {pid_t pid1;if((pid1=fork())<0)perror("fork");if(pid1 == 0){printf("這是子進程,pid1=%d,",getpid());printf("父進程的pid1=%d\n",getppid());_exit(0);}pid_t pid2;if((pid2=fork())<0)perror("fork");if(pid2 == 0){printf("這是子進程,pid2=%d,",getpid());printf("父進程的pid2=%d\n",getppid());_exit(0);}printf("這是父進程,pid=%d\n",getpid());while (1){pid_t pid = wait (NULL);if (pid == -1){if (errno != ECHILD){perror ("wait");exit (EXIT_FAILURE);}printf ("子進程都死光了\n");break;}printf ("%d子進程終止\n", pid);}return 0; } 輸出結果: 這是父進程,pid=2944 這是子進程,pid1=2945,父進程的pid1=2944 2945子進程終止 這是子進程,pid2=2946,父進程的pid2=2944 2946子進程終止 子進程都死光了示例解析:
如果不使用 wait ,父進程先于子進程終止,則子進程成為孤兒進程了。使用 wait 在于可掛起正在運行的父進程進入等待狀態,直到有子進程終止。 思考,父進程使用 sleep 不是也可以保證子進程先被調度,避免產生孤兒進程么。 wait 和 sleep 有何區別,稍后會講。2》》如果一個子進程在 wait ?函數套用之前,已經終止并處于僵尸狀態,wait 函數會立即返回,并取得該子進程的終止狀態,同時子進程僵尸消失。由此可見 wait 函數主要完成三個任務。
1、阻塞父進程的運行,直到子進程終止再繼續,停等同步。 2、獲取子進程的 PID 和終止狀態,令父進程得知誰因何而死。 3、為子進程收尸,防止大量僵尸進程耗費系統資源。 以上三個任務中,即使前兩個與具體需求無關,僅僅第三個也足以凸顯 wait 函數的重要性,尤其是對那些多進程服務器型的應用而言。 僵尸進程的產生,上一篇有講,參看:UNIX再學習 -- 函數 fork 和 vfork 下面簡單用示例介紹一下僵尸進程: 參看:linux wait與waitpid函數的深入分析 #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main (void) { pid_t pid; pid = fork (); if (pid == -1) perror ("fail to fork"), exit (1); else if (pid == 0) { printf ("這是子進程 pid = %d", getpid ()); printf ("父進程的 ppid = %d\n", getppid ()); } else { //while (1); sleep (10); printf ("這是父進程 ppid = %d\n", getpid ()); } return 0; } 實驗: 在一個終端執行 ./a.out # ./a.out 這是子進程 pid = 2868父進程的 ppid = 2867 (十秒后) 這是父進程 ppid = 2867在另一個終端,查看進程信息 # ps -C a.out -o ppid,pid,stat,cmdPPID PID STAT CMD2366 2867 S+ ./a.out2867 2868 Z+ [a.out] <defunct> 結論: 雖然,子進程已經結束,但是父進程仍未終止,沒有回收子進程的退出狀態,子進程即成為僵尸進程。 而父進程調用 wait,會終止子進程的僵尸態,示例說明: #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main (void) { pid_t pid, pr; pid = fork (); if (pid == -1) perror ("fail to fork"), exit (1); else if (pid == 0) { printf ("這是子進程 pid = %d", getpid ()); printf ("父進程的 ppid = %d\n", getppid ()); } else { pr = wait (NULL);//while (1); sleep (10); //可以保證子進程先被調度 printf ("這是父進程 ppid = %d\n", getpid ()); } return 0; } 實驗: 在一個終端執行 ./a.out # ./a.out 這是子進程 pid = 2889父進程的 ppid = 2888 (十秒后) 這是父進程 ppid = 2888在另一個終端,查看進程信息 # ps -C a.out -o ppid,pid,stat,cmdPPID PID STAT CMD 結論: 可以發現,確實子進程的僵尸態沒有了。不過需要注意的是,wait 使用的位置。 上面有這樣一句話,如果一個子進程在 wait ?函數套用之前,已經終止并處于僵尸狀態,所以說,子進程必須在 wait 函數套用之前結束。如果,wait 先于子進程,是沒有效果的。#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main (void) { pid_t pid, pr; pr = wait (NULL);pid = fork (); if (pid == -1) perror ("fail to fork"), exit (1); else if (pid == 0) { printf ("這是子進程 pid = %d", getpid ()); printf ("父進程的 ppid = %d\n", getppid ()); } else { //while (1); sleep (10); printf ("這是父進程 ppid = %d\n", getpid ()); } return 0; } 實驗: 在一個終端執行 ./a.out # ./a.out 這是子進程 pid = 2932父進程的 ppid = 2931 (十秒后) 這是父進程 ppid = 2931在另一個終端,查看進程信息 # ps -C a.out -o ppid,pid,stat,cmdPPID PID STAT CMD2366 2931 S+ ./a.out2931 2932 Z+ [a.out] <defunct> 再有一個,上述例子中都是使用的 wait (NULL),如果參數非 NULL,則可以輸出子進程終止狀態。 #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main (void) { pid_t pid, pr; int status = 0; pid = fork (); if (pid == -1) perror ("fail to fork"), exit (1); else if (pid == 0) { printf ("這是子進程 pid = %d", getpid ()); printf ("父進程的 ppid = %d\n", getppid ()); _exit (100);} else { int status = 0; pr = wait (&status);//while (1); sleep (10); //可以保證子進程先被調度 printf ("這是父進程 ppid = %d\n", getpid ()); printf ("status = %d, pr = %d\n", status, pr);} return 0; } 輸出結果: 這是子進程 pid = 3078父進程的 ppid = 3077 這是父進程 ppid = 3077 status = 25600, pr = 3078 總結: 子進程異常退出(_exit (100)),通過 wait 函數參數 status 獲得終止狀態。
3》》子進程的終止狀態通過 wait 函數的 status 參數輸出給該函數調用者。<sys/wait.h> 頭文件提供了幾個輔助分析進程終止狀態的工具宏。
查看:?/usr/include/i386-linux-gnu/sys/wait.h? /* This will define all the `__W*' macros. */ # include <bits/waitstatus.h># define WEXITSTATUS(status) __WEXITSTATUS (__WAIT_INT (status)) # define WTERMSIG(status) __WTERMSIG (__WAIT_INT (status)) # define WSTOPSIG(status) __WSTOPSIG (__WAIT_INT (status)) # define WIFEXITED(status) __WIFEXITED (__WAIT_INT (status)) # define WIFSIGNALED(status) __WIFSIGNALED (__WAIT_INT (status)) # define WIFSTOPPED(status) __WIFSTOPPED (__WAIT_INT (status))講解: 參看:WAIT(2) WIFEXITED (status) ? (常用) 判斷子進程是否正常終止,是則為真。 WIEXITSTATUS (status) ?(常用) 獲取子進程調用 exit、_exit、Exit 函數時所傳入的參數或者 main 函數中 return 語句返回值的低 8 位。 WIFSIGNALED (status) 判斷子進程是否異常終止,是則為真? WTERMSIG (status)? 宏獲取導致子進程異常終止的信號。 WIFSTOPPED (status) 判斷當前暫停子進程是否返回,是則為真。 WSTOPSIG (status) 獲取使子進程暫停的信號。 示例說明: #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h>int main (void) {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);}else{pr = wait(&status);printf ("status = %d\n", status);printf ("status >> 8 = %d\n", status >> 8);if(WIFEXITED(status)){printf("The child process %d exit normally.\n",pr);printf("The WEXITSTATUS return code is %d.\n",WEXITSTATUS(status));printf("The WIFEXITED return code is %d.\n",WIFEXITED(status));}elseprintf("The child process %d exit abnormally.\n",pr);}return 0; } 輸出結果: This is child process with pid of 3370 status = 768 status >> 8 = 3 The child process 3370 exit normally. The WEXITSTATUS return code is 3. The WIFEXITED return code is 1. 示例解析: 通過示例可以看出 wait 返回子進程 ID;WEXITSTATUS (status) 返回 exit (3) 參數 3;子進程正常終止,WIFEXITED 為真。?思考:其中的我添加打印了 status 的值為 768,以及status >> 8 的值為 3,啥意思? status 到底是什么? 我們上面說了,status 是一個整型指針,如果 status 不是一個空指針,則終止進程的終止狀態就存放在它所指向的單元內。那終止狀態是什么,怎么表示的? 再有,獲取子進程調用 exit、_exit、Exit 函數時所傳入的參數或者 main 函數中 return 語句返回值的低 8 位
這句話就說明了,status 是?exit、_exit、Exit 函數時所傳入的參數或者 main 函數中 return 語句返回值的低 8 位。 那么 低 8 位、高 8 位 又是怎么回事? ? 下面開始講解下: 參看:wait函數返回值總結 exit、wait、WEXITSTATUS 中的 status 關系(1)exit 函數參數 status 為整型,稱為終止狀態(或退出狀態,exit status)。參看:Exit Status參看:waitpid之status意義解析查看 /linux-2.6.37.1/kernel/exit.cSYSCALL_DEFINE1(exit, int, error_code) {do_exit((error_code&0xff)<<8); }查看函數NORET_TYPE void do_exit(long code) 有 tsk->exit_code = code;所以子進程的返回值就是這么確定的,它的低8位為零,次低8位為真正退出碼。(2)wait 函數參數 status 為整型指針。高8位 記錄進程調用exit退出的狀態(正常退出);低8位 記錄進程接受到的信號 (非正常退出)。如果正常退出(exit) ---高8位是退出狀態號,低8位是0如果非正常退出(signal)----高八位是0,低8位是siganl id
(3)再查看?/usr/include/i386-linux-gnu/bits/waitstatus.h 獲得?__WEXITSTATUS(status) 定義。 /* Everything extant so far uses these same bits. *//* If WIFEXITED(STATUS), the low-order 8 bits of the status. */ #define __WEXITSTATUS(status) (((status) & 0xff00) >> 8)/* If WIFSIGNALED(STATUS), the terminating signal. */ #define __WTERMSIG(status) ((status) & 0x7f)/* If WIFSTOPPED(STATUS), the signal that stopped the child. */ #define __WSTOPSIG(status) __WEXITSTATUS(status)/* Nonzero if STATUS indicates normal termination. */ #define __WIFEXITED(status) (__WTERMSIG(status) == 0)/* Nonzero if STATUS indicates termination by a signal. */ #define __WIFSIGNALED(status) \(((signed char) (((status) & 0x7f) + 1) >> 1) > 0)/* Nonzero if STATUS indicates the child is stopped. */ #define __WIFSTOPPED(status) (((status) & 0xff) == 0x7f)/* Nonzero if STATUS indicates the child continued after a stop. We onlydefine this if <bits/waitflags.h> provides the WCONTINUED flag bit. */ #ifdef WCONTINUED # define __WIFCONTINUED(status) ((status) == __W_CONTINUED) #endif/* Nonzero if STATUS indicates the child dumped core. */ #define __WCOREDUMP(status) ((status) & __WCOREFLAG) 總結一下: wait 參數整型指針 (int *status),高8位記錄進程調用 exit 退出的狀態(即exit 的參數status)(正常退出);低8位 記錄進程接受到的信號 (非正常退出)。WEXITSTATUS 返回值為((status) & 0xff00) >> 8,即 exit 退出狀態。 簡單點來表示就是: ?exit (3) --> wait (&(3<<8 ))--> WEXITSTATUS (3<<8) = ((3<<8)&0xff) >>8 = 3
2、waitpid 函數
#include <sys/types.h> #include <sys/wait.h> pid_t waitpid(pid_t pid, int *status, int options); 成功返回所回收子進程的 PID 或 0,失敗返回 -1(1)函數功能
等待并回收任意或特定子進程(2)參數解析
第一個參數:進程號,可取以下值。 pid < -1 ? ? 等待并回收特定進程組(由 pid標識)的任意子進程 pid == -1 ?等待并回收任意子進程,相當于 wait 函數 ?(掌握) pid == 0 ? ?等待并回收與調用進程同進程組的任意子進程 pid > 0 ? ? ?等待并回收特定子進程(由pid標識) (掌握) 第二個參數:輸出子進程的終止狀態,可置 NULL 第三個參數:選項,可取以下值 (默認給 0 即可) 0 ? ?阻塞模式,若所等子進程仍在運行,則阻塞,直至其終止。 ?(掌握) WNOHANG ? 非阻塞模式,若所等子進程仍在運行,則返回 0 ?(掌握) WCONTINUED ?若實現支持作業控制,那么由 pid 指定的任一子進程在停止后已經繼續,但其裝填尚未報告,則返回其狀態。 WUNTRACED ?若某實現支持作業控制,而由 pid 指定的任一子進程已處于停止狀態,并且其狀態自停止以來還未報告過,則返回其狀態。WIFSTOPPED 宏確定返回值是否對應于一個停止的子進程。(3)函數解析
1》》回收特定子進程
事實上,無論一個進程是正常終止還是異常終止,都會通過系統內核向其父進程發送 SIGCHLD (17)信號。父進程可以忽略該信號,也可以提供一個針對該信號的處理函數,在信號處理函數中以異步的方式回收子進程。這樣做不僅流程簡單,而且僵尸的存貨時間短,回收效率高。其中 error 等于 ECHILD 表示沒有需要等待的子進程。
示例說明:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> #include <errno.h>int main (void) {pid_t pid;if ((pid = fork ()) < 0)perror ("fork"), exit (1);else if (pid == 0){printf ("這是子進程 pid = %d\n", getpid ());printf ("父進程的 pid = %d\n", getppid ());exit (0);}else printf ("這是父進程 pid = %d\n", getpid ());//回收特定子進程pid = waitpid (pid, NULL, 0);if (pid == -1){if (errno != ECHILD)perror ("waitpid"), exit (1);}elseprintf ("%d子進程終止\n", pid);printf ("%d父進程終止\n", getpid ());return 0; } 輸出結果: 這是父進程 pid = 2892 這是子進程 pid = 2893 父進程的 pid = 2892 2893子進程終止 2892父進程終止2》》非阻塞模式回收所有子進程
waitpid函數以非阻塞模式運行時,可以等待并回收所有子進程,等待的同時做空閑處理。示例說明:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> #include <errno.h>int main (void) {pid_t pid1;if ((pid1 = fork ()) < 0)perror ("fork"), exit (1);if (pid1 == 0){printf ("這是子進程pid1 = %d", getpid ());printf ("父進程pid1 = %d\n", getppid ());exit (0);}pid_t pid2;if ((pid2 = fork ()) < 0)perror ("fork"), exit (1);if (pid2 == 0){printf ("這是子進程pid2 = %d", getpid ());printf ("父進程pid2 = %d\n", getppid ());exit (0);}//sleep (2);printf ("這是父進程pid = %d\n", getpid ());while (1){pid_t pid = waitpid (-1, NULL, WNOHANG);if (pid == -1){if (errno != ECHILD)perror ("waitpid"), exit (1);printf ("子進程都死光了\n");break;}if (pid)printf ("%d子進程終止\n", pid);else printf ("在這里進行空閑處理\n");、//表示所等子進程仍在運行,此時父進程出現空閑時間,可在這里進行空閑處理。}return 0; } 輸出結果: 這是父進程pid = 3029 在這里進行空閑處理 在這里進行空閑處理 .... 在這里進行空閑處理 在這里進行空閑處理 這是子進程pid2 = 3031父進程pid2 = 3029 這是子進程pid1 = 3030父進程pid1 = 3029 在這里進行空閑處理 3031子進程終止 3030子進程終止 子進程都死光了3》》通過 WUNTRACED 和 WCONTINUED 支持作業控制 (了解)
這是兩個常數,可以用"|"運算符把它們連接起來使用,比如:ret=waitpid(-1,NULL,WNOHANG | WUNTRACED);
4》》使用2次 fork 避免僵尸進程
如果一個進程 fork 一個子進程,但不要它等待子進程終止,也不希望子進程處于僵尸狀態直到父進程終止,實現這一要求的訣竅是調用 fork 兩次。 #include <stdio.h> #include <sys/wait.h> #include <stdlib.h> int main(void) { pid_t pid; if((pid = fork()) < 0)perror("fork"), exit (1);else if(pid == 0){ if((pid = fork()) < 0) perror("fork"), exit (1);else if(pid > 0)exit(0); else{ sleep(2); printf("second child,parent pid = %d\n",getppid()); exit(0); } }if(waitpid(pid,NULL,0) != pid) //回收第一次fork的子進程,但是第二個沒有回收 perror("waitpid"), exit (1); return 0; } 輸出結果: second child,parent pid = 13、wait 和 waitpid 函數區別
(1)在一個子進程終止前,wait 使其調用者阻塞,而 waitpid 有一選項 WNOHANG ,可使調用者不阻塞。 (2)waitpid 并不等待在其調用之后的第一個終止子進程,它有若干個選項,可以控制它所等待的進程。 (說的是 waitpid 第一個參數選項,如等待特定的子進程) (3)waitpid?通過 WUNTRACED 和 WCONTINUED 支持作業控制。? (4)wait(&status) 等同于 waitpid(-1, &status, 0);4、wait 與 sleep 區別
我們上面簡單提到了一下兩者都可以避免產生孤兒進程。但是有什么區別呢,下面我們簡單介紹下。 講之前先說一下優先級,if (pid = fork () < 0) ? 對不對?? 答案是錯誤的。 '<' 的優先級 高于 '=',所有應該是加括號 ?if ((pid = fork ()) < 0)??(1)使用 wait 函數,避免孤兒進程
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main (void) { pid_t pid, pr; if ((pid = fork ()) < 0) perror ("fork"), exit (1); else if (pid == 0) { sleep (3); printf ("這是子進程 pid = %d", getpid ()); printf ("父進程的 ppid = %d\n", getppid ()); exit (0);} else { pr = wait (NULL);printf ("這是父進程 ppid = %d\n", getpid ()); } return 0; } 輸出結果: 這是子進程 pid = 3603父進程的 ppid = 3602 這是父進程 ppid = 3602(2)使用 sleep,避免孤兒進程
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main (void) { pid_t pid; if ((pid = fork ()) < 0) perror ("fork"), exit (1); else if (pid == 0) { sleep (3); printf ("這是子進程 pid = %d", getpid ()); printf ("父進程的 ppid = %d\n", getppid ()); exit (0);} else { sleep (5);printf ("這是父進程 ppid = %d\n", getpid ()); } return 0; } 輸出結果: 這是子進程 pid = 3611父進程的 ppid = 3610 這是父進程 ppid = 3610但是,如果你注意看輸出時的等待時間,就會發現。 wait 等待 3 秒然后,同時輸出結果。而 sleep 先等待 3 秒,打印子進程,然后再等待 2 秒打印父進程。所以說,sleep 是休眠指定時間,到時間繼續往下執行。而 wait 是等待,需要被觸發才能繼續往下執行。
當然,wait 還有其他功能,是 sleep 沒有的。上面講的很清楚了,就不一一說明了。 這里只是簡單提一句,等講到線程、Linux部分,可以繼續深入探討。
三、總結
第八章東西有點多,一章拆成了幾篇文章來寫,看的節奏有點慢。最近也有點分心了,周末出去玩沒問題,但是占用休息和本來用來學習的時間用在修圖上,這就不該了。收起玩心,抓緊時間復習吧!總結
以上是生活随笔為你收集整理的UNIX再学习 -- exit 和 wait 系列函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JAVA面试题(1~95)《上》
- 下一篇: UNIX再学习 -- exec 函数族