之前一直沒太深入的去理解wait()函數,今天機緣巧合之前又看了看,發現之前沒有真正的理解該函數。
眾所周知,wait()函數一般用在父進程中等待回收子進程的資源,而防止僵屍進程的產生。
(In UNIX System terminology, a process that has terminated, but whose parent has not yet waited for it, is called a zombie. )
下面我就帶着問題,層層深入地來分析這個函數。
當一個進程正常或異常終止時,內核就向其父進程發送SIGCHLD信號。
因為子進程終止是個異步事件(這可以在父進程運行的任何是否發生),所以這種信號也是內核向父進程發的異步通知。
父進程可以選擇忽略該信號,或者提供一個信號處理程序。
對於這種信號的系統默認動作是忽略。
調用wait()或waitpid()的父進程會發生什么情況:
a. 如果其所有子進程都還在運行,則阻塞;
Q1:如果是一部分子進程終止,而另一部分還在運行,那么父進程還會阻塞嗎?
不會,只要有一個進程終止,wait就會返回。也就是說只要wait接收到一個SIGCHLD信號,wait()就會返回。對於兩個或多個子進程的情況,需要調用wait兩次或多次
b. 如果一個子進程已經終止,正等待父進程獲取其終止狀態,則取得該子進程的終止狀態立即返回;
c. 如果它沒有任何子進程,則立即出錯返回;
int wait(int* statloc);
int waitpid(pid_t pid, int* statloc, int options);
這兩個函數的區別如下:
1. 在一個子進程終止前,wait使其調用者阻塞,而waitpid有一個選項,可使調用者不阻塞;
2. waitpid()並不等待在其調用之后的第一個終止的子進程,它有若干個選項,可以控制它所等待的進程;
如果一個子進程已經終止,並且是一個僵屍進程,則wait立即返回並取得該子進程的終止狀態,否則wait使其調用者阻塞直到一個子進程終止。
如果調用者阻塞而且它有多個子進程,則在其一個子進程終止時,wait就立即返回。
因為wait的返回值是終止進程的進程ID,所以父進程總能知道哪一個子進程終止了。
參數statloc如果不是一個空指針,則終止進程的終止狀態就存放在statloc所指向的單元。
參數statloc如果是一個空指針,則表示父進程不關心子進程的終止狀態。
Q2:statloc中不同的值具體表示什么含義呢?
wait的輸出參數statloc中,某些位表示退出狀態(正常返回),其它位則指示信號編號(異常返回),有一位指示是否產生了一個core文件等等。
有四個互斥的宏可用來取得進程終止的原因。
1. WIFEXITED(status):若為正常終止子進程返回的狀態,則為真。WEXITSTATUS(status)可取得子進程傳送給exit、_exit或_Exit參數的低8位;
2. WIFSIGNALED(status):若為異常終止子進程返回的狀態,則為真(接到一個不捕捉的信號,如SIGCHLD)。對於這種情況,可執行WTERMSIG(status),取得是子進程終止的信號的編號。
另外,有些實現(非Single UNIX Specification)定義宏WCOREDUMP(status),若已產生終止進程的core文件,則它返回真。
3. WIFSTOPPED(status):若為當前暫停的子進程的返回的狀態,則為真。對於這種情況,可執行WSTOPSIG(status),取使子進程暫停的信號編號。
4. WIFCONTINUED(status):若在作業控制暫停后已經繼續的子進程返回了狀態,則為真.(POSIX.1的XSI擴展,僅用於waitpid)
Q3: wait函數和SIGCHLD信號的關系?兩者之間的關系,需要分成三個問題
已知系統默認是忽略SIGCHLD信號,在一個進程終止或停止時,會將SIGCHLD信號發送給其父進程。
已知父進程若不調用wait()獲取子進程的終止狀態,那么子進程就會變成僵屍進程。
Q3.1:wait()是關於是否產生僵屍進程的問題。
Q3.2:SIGCHLD信號是關於自己本身的處理方式的選擇問題。
當這兩個問題(Q3.1&Q3.2)結合在一起應用時,就產生了另外一個問題,父進程是同步還是異步的問題(或者描述為阻塞還是非阻塞問題)
當SIGCHLD的處理方式是系統默認時,父進程調用了wait()以防止子進程變成僵屍進程,那么父進程必須等待子進程結束之后才能執行wait()之后的流程,即同步問題。
當SIGCHLD的處理方式是捕獲時,在其信號處理程序中調用wait()函數,就能獲取子進程的終止狀態而不產生僵屍進程同時父進程並不會阻塞,做自己想做的事,即異步問題。
Q3.3:wait什么時候返回的問題,wait()的返回和SIGCHLD有什么關系?
根據wait()的描述:
All of these system calls are used to wait for state changes in a child of the calling process, and obtain information about the child whose
state has changed. A state change is considered to be: the child terminated; the child was stopped by a signal; or the child was resumed by a
signal. In the case of a terminated child, performing a wait allows the system to release the resources associated with the child; if a wait is
not performed, then terminated the child remains in a "zombie" state (see NOTES below).
If a child has already changed state, then these calls return immediately. Otherwise they block until either a child changes state or a signal
handler interrupts the call (assuming that system calls are not automatically restarted using the SA_RESTART flag of sigaction(2)). In the
remainder of this page, a child whose state has changed and which has not yet been waited upon by one of these system calls is termed waitable.
當子進程的狀態發生改變時,wait()返回;
當調用wait()的進程接收到一個被設置為SA_INTERRUPT的信號時,wait()返回;
因為SIGCHLD信號的產生必然是伴隨着子進程狀態的改變,所以當有SIGCHLD信號發生時,wait會返回。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <sys/wait.h> 4 #include <errno.h> 5 #include <signal.h> 6 7 void print_exit(int status) 8 { 9 if (WIFEXITED(status)) 10 printf("normal termination, exit status = %d\n", WEXITSTATUS(status)); 11 else if (WIFSIGNALED(status)) 12 printf("abnormal termination, signal number = %d%s\n", WTERMSIG(status), 13 #ifdef WCOREDUMP 14 WCOREDUMP(status) ? ("core file generated") : ("")); 15 #else 16 ""); 17 #endif 18 else if (WIFSTOPPED(status)) 19 printf("child stopped, signal number=%d\n", WSTOPSIG(status)); 20 } 21 22 void sig_child(int signo) 23 { 24 int status; 25 int ret; 26 ret = wait(&status); 27 printf("pid:%d, res:%d, status=%d, %s\n", getpid(), ret, status, strerror(errno)); 28 print_exit(status); 29 } 30 31 void sig_usr(int signo) 32 { 33 if (signo == SIGUSR1) 34 printf("received SIGUSR1\n"); 35 else if (signo == SIGUSR2) 36 printf("received SIGUSR2\n"); 37 else 38 printf("received signal %d\n", signo); 39 } 40 41 42 int main(int argc, char** argv) 43 { 44 pid_t pid; 45 int status; 46 int ret; 47 int remaintime=0; 48 struct sigaction act, oact; 49 sigset_t oldmask; 50 51 //signal(SIGCHLD, sig_child); 52 //signal(SIGUSR1, sig_usr); 53 54 act.sa_handler = sig_usr; 55 sigemptyset(&act.sa_mask); 56 act.sa_flags = 0|SA_INTERRUPT; 57 sigaction(SIGUSR1, &act, &oact); 58 59 if ((pid=fork()) < 0) 60 { 61 printf("fork error\n"); 62 return -1; 63 } 64 else if (pid == 0) 65 { 66 67 printf("child:pid:%d\n", getpid()); 68 remaintime = sleep(200); 69 printf("remiantime=%d\n", remaintime); 70 //exit(0); 71 //return 0; 72 // 73 //sleep(30);//SIGQUIT 74 } 75 else 76 { 77 printf("father:pid:%d\n", getpid()); 78 //while(1) 79 //{ 80 // sleep(1); 81 // printf("1111\n"); 82 //} 83 ret = wait(&status); 84 printf("res:%d, status=%d, %s\n", ret, status, strerror(errno)); 85 print_exit(status); 86 } 87 88 return 0; 89 }