waitpid(-1, &status, 0);
waitpid() 系統調用掛起調用進程的執行直到由參數 pid 指定的孩子的狀態發生改變。默認狀況下,waitpid() 只等待孩子的終止,可是這個行爲可能經過 options 參數來改變,具體描述以下。html
pid 值能夠是:linux
options 的值是下面常量的中的零個或多個進行位或運算的結果:shell
(只對 Linux 有效的選項,見下面。)編程
若是 status 不是 NULL,wait() 和 waitpid() 保存狀態信息在那個 int 指針指向的內存裏。這個整數能夠經過下面的宏(它們接受整數自身,而不指向它的指針,wait() 和 waitpid() 須要指針!)進行審視:緩存
咱們運行以下命令,可看到Linux支持的信號列表:服務器
~$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE
9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD
18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN
22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO
30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1
36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5
40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9
44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13
52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9
56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5
60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1
64) SIGRTMAX網絡
列表中,編號爲1 ~ 31的信號爲傳統UNIX支持的信號,是不可靠信號(非實時的),編號爲32 ~ 63的信號是後來擴充的,稱作可靠信號(實時信號)。不可靠信號和可靠信號的區別在於前者不支持排隊,可能會形成信號丟失,然後者不會。session
下面咱們對編號小於SIGRTMIN的信號進行討論。併發
1) SIGHUP
本信號在用戶終端鏈接(正常或非正常)結束時發出, 一般是在終端的控制進程結束時, 通知同一session內的各個做業, 這時它們與控制終端再也不關聯。異步
登陸Linux時,系統會分配給登陸用戶一個終端(Session)。在這個終端運行的全部程序,包括前臺進程組和後臺進程組,通常都屬於這個 Session。當用戶退出Linux登陸時,前臺進程組和後臺有對終端輸出的進程將會收到SIGHUP信號。這個信號的默認操做爲終止進程,所以前臺進 程組和後臺有終端輸出的進程就會停止。不過能夠捕獲這個信號,好比wget能捕獲SIGHUP信號,並忽略它,這樣就算退出了Linux登陸,wget也 能繼續下載。
此外,對於與終端脫離關係的守護進程,這個信號用於通知它從新讀取配置文件。
2) SIGINT
程序終止(interrupt)信號, 在用戶鍵入INTR字符(一般是Ctrl-C)時發出,用於通知前臺進程組終止進程。
3) SIGQUIT
和SIGINT相似, 但由QUIT字符(一般是Ctrl-\)來控制. 進程在因收到SIGQUIT退出時會產生core文件, 在這個意義上相似於一個程序錯誤信號。
4) SIGILL
執行了非法指令. 一般是由於可執行文件自己出現錯誤, 或者試圖執行數據段. 堆棧溢出時也有可能產生這個信號。
5) SIGTRAP
由斷點指令或其它trap指令產生. 由debugger使用。
6) SIGABRT
調用abort函數生成的信號。
7) SIGBUS
非法地址, 包括內存地址對齊(alignment)出錯。好比訪問一個四個字長的整數, 但其地址不是4的倍數。它與SIGSEGV的區別在於後者是因爲對合法存儲地址的非法訪問觸發的(如訪問不屬於本身存儲空間或只讀存儲空間)。
8) SIGFPE
在發生致命的算術運算錯誤時發出. 不只包括浮點運算錯誤, 還包括溢出及除數爲0等其它全部的算術的錯誤。
9) SIGKILL
用來當即結束程序的運行. 本信號不能被阻塞、處理和忽略。若是管理員發現某個進程終止不了,可嘗試發送這個信號。
10) SIGUSR1
留給用戶使用
11) SIGSEGV
試圖訪問未分配給本身的內存, 或試圖往沒有寫權限的內存地址寫數據.
12) SIGUSR2
留給用戶使用
13) SIGPIPE
管道破裂。這個信號一般在進程間通訊產生,好比採用FIFO(管道)通訊的兩個進程,讀管道沒打開或者意外終止就往管道寫,寫進程會收到SIGPIPE信號。此外用Socket通訊的兩個進程,寫進程在寫Socket的時候,讀進程已經終止。
14) SIGALRM
時鐘定時信號, 計算的是實際的時間或時鐘時間. alarm函數使用該信號.
15) SIGTERM
程序結束(terminate)信號, 與SIGKILL不一樣的是該信號能夠被阻塞和處理。一般用來要求程序本身正常退出,shell命令kill缺省產生這個信號。若是進程終止不了,咱們纔會嘗試SIGKILL。
17) SIGCHLD
子進程結束時, 父進程會收到這個信號。
若是父進程沒有處理這個信號,也沒有等待(wait)子進程,子進程雖然終止,可是還會在內核進程表中佔有表項,這時的子進程稱爲殭屍進程。這種情 況咱們應該避免(父進程或者忽略SIGCHILD信號,或者捕捉它,或者wait它派生的子進程,或者父進程先終止,這時子進程的終止自動由init進程 來接管)。
18) SIGCONT
讓一箇中止(stopped)的進程繼續執行. 本信號不能被阻塞. 能夠用一個handler來讓程序在由stopped狀態變爲繼續執行時完成特定的工做. 例如, 從新顯示提示符
19) SIGSTOP
中止(stopped)進程的執行. 注意它和terminate以及interrupt的區別:該進程還未結束, 只是暫停執行. 本信號不能被阻塞, 處理或忽略.
20) SIGTSTP
中止進程的運行, 但該信號能夠被處理和忽略. 用戶鍵入SUSP字符時(一般是Ctrl-Z)發出這個信號
21) SIGTTIN
當後臺做業要從用戶終端讀數據時, 該做業中的全部進程會收到SIGTTIN信號. 缺省時這些進程會中止執行.
22) SIGTTOU
相似於SIGTTIN, 但在寫終端(或修改終端模式)時收到.
23) SIGURG
有」緊急」數據或out-of-band數據到達socket時產生.
24) SIGXCPU
超過CPU時間資源限制. 這個限制能夠由getrlimit/setrlimit來讀取/改變。
25) SIGXFSZ
當進程企圖擴大文件以致於超過文件大小資源限制。
26) SIGVTALRM
虛擬時鐘信號. 相似於SIGALRM, 可是計算的是該進程佔用的CPU時間.
27) SIGPROF
相似於SIGALRM/SIGVTALRM, 但包括該進程用的CPU時間以及系統調用的時間.
28) SIGWINCH
窗口大小改變時發出.
29) SIGIO
文件描述符準備就緒, 能夠開始進行輸入/輸出操做.
30) SIGPWR
Power failure
31) SIGSYS
非法的系統調用。
在以上列出的信號中,程序不可捕獲、阻塞或忽略的信號有:SIGKILL,SIGSTOP
不能恢復至默認動做的信號有:SIGILL,SIGTRAP
默認會致使進程流產的信號有:SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ
默認會致使進程退出的信號有:SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM
默認會致使進程中止的信號有:SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU
默認進程忽略的信號有:SIGCHLD,SIGPWR,SIGURG,SIGWINCH
此外,SIGIO在SVR4是退出,在4.3BSD中是忽略;SIGCONT在進程掛起時是繼續,不然是忽略,不能被阻塞
例子
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<sys/types.h> 4 #include<sys/wait.h> 5 6 7 8 void pr_exit(int); 9 10 11 12 int main(void) 13 { 14 pid_t pid; 15 int status; 16 17 if((pid=fork())<0){ 18 printf("fork error"); 19 }else if(pid==0){ 20 exit(7); 21 } 22 23 if(wait(&status)!=pid) 24 printf("wait error"); 25 pr_exit(status); 26 27 if((pid=fork())<0) 28 printf("fork error"); 29 else if(pid==0) 30 { 31 int n = 0; 32 //scanf("%d", &n); 33 // printf("child pid=%d\n",getpid()); 34 //printf("child pid=%d\n",getppid()); 35 abort(); 36 } 37 38 39 40 41 if(wait(&status)!=pid) 42 printf("wait error"); 43 44 pr_exit(status); 45 46 if((pid=fork())<0) 47 printf("fork error"); 48 else if(pid==0) 49 //printf(" parent pid=%d\n",getppid()); 50 51 //printf("child pid=%d\n",getpid()); 52 53 status/=0; 54 55 if(wait(&status)!=pid) 56 printf("wait error"); 57 pr_exit(status); 58 exit(0); 59 } 60 void pr_exit(int status) 61 { 62 if(WIFEXITED(status)) 63 printf("normal termination,exitstatus=%d\n",WEXITSTATUS(status)); 64 else if(WIFSIGNALED(status)) 65 printf("abnormal termination,signalstatus=%d\n",WTERMSIG(status), 66 #ifdef WCOREDUMP 67 WCOREDUMP(status)?"(core file generated)":""); 68 #else 69 ""); 70 #endif 71 else if(WIFSTOPPED(status)) 72 printf("child stopped ,signal number=%d\n", WSTOPSIG(status)); 73 }
輸出
normal termination,exitstatus=7
abnormal termination,signalstatus=6
abnormal termination,signalstatus=8
子進程結束的方式不一樣,子進程發送給父進程的信號也不一樣,6 是6) SIGABRT, 8是8) SIGFPE 在發生致命的算術運算錯誤時發出. 不只包括浮點運算錯誤, 還包括溢出及除數爲0等其它全部的算術的錯誤。
這都是abnormal 的結束
wait和waitpid函數
--------------------
當一個進程正常或異常終止時,內核就向其父進程發送SIGCHLD信號。由於子進程終止是個異步事件,因此這種信號也是內核向父進程發的異步通知。父進程能夠選擇忽略該信號,或者提供一個該信號發生時即被調用執行的函數(信號處理程序)。對於這種信號的系統默認動做是忽略它。父進程調用wait或waitpid時可能會發生以下狀況之一:
若是其全部子進程都還在運行,則阻塞。
若是一個子進程已終止,正等待父進程獲取其終止狀態,則取得該子進程的終止狀態當即返回。
若是它沒有任何子進程,則當即出錯返回。
#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);
實際應用中,waitpid函數的使用比wait函數更爲靈活,由於waitpid函數提供了wait函數沒有提供的三個功能:
waitpid可等待一個特定的進程,而wait則返回任一終止子進程的狀態。
waitpid提供了一個wait的非阻塞版本(將options設爲WNOHANG)。
waitpid支持做業控制(將options設爲WUNTRACED和WCONTINUED選項)。
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <errno.h> 4 #include <stdlib.h> 5 #include <sys/wait.h> 6 7 #include <stdio.h> 8 #include <unistd.h> 9 #include <sys/types.h> 10 11 void pr_exit(int status) 12 { 13 if (WIFEXITED(status)) 14 printf("normal termination, exit status = %d\n", 15 WEXITSTATUS(status)); 16 else if (WIFSIGNALED(status)) 17 printf("abnormal termination, signal number = %d%s\n", 18 WTERMSIG(status), 19 #ifdef WCOREDUMP 20 WCOREDUMP(status) ? " (core file generated)" : ""); 21 #else 22 ""); 23 #endif 24 else if (WIFSTOPPED(status)) 25 printf("child stopped, signal number = %d\n", 26 WSTOPSIG(status)); 27 else if (WIFCONTINUED(status)) 28 printf("child continuing...\n"); 29 } 30 31 static void sig_hup(int signo) 32 { 33 printf("SIGHUP received, pid = %d\n", getpid()); 34 } 35 36 static void sig_tstp(int signo) 37 { 38 printf("SIGTSTP received, pid = %d\n", getpid()); 39 } 40 41 static void pr_ids(char *name) 42 { 43 printf("%s: pid = %d, ppid = %d, pgrp = %d, tpgrp = %d\n", 44 name , getpid(), getppid(), getpgrp(), tcgetpgrp(STDIN_FILENO)); 45 fflush(stdout); 46 } 47 48 int main(void) 49 { 50 char c; 51 pid_t pid; 52 int status; 53 54 pr_ids("parent"); 55 if ((pid = fork()) < 0) 56 { 57 perror("fork error"); 58 return 0; 59 } 60 else if (pid > 0) 61 { 62 sleep(5); 63 printf("child process starts %d\n", pid); 64 printf("wait for child process to end\n" ); 65 waitpid(pid, &status, WUNTRACED |WCONTINUED); 66 printf("child process ends\n" ); 67 68 pr_exit(status); 69 kill(pid, SIGCONT); 70 waitpid(pid, &status, WUNTRACED|WCONTINUED ); 71 pr_exit(status); 72 exit(0); 73 } 74 else// child proc 75 { 76 sleep(10); 77 pr_ids("child 1"); 78 // signal(SIGHUP, sig_hup); 79 // signal(SIGTSTP, sig_tstp); 80 kill(getpid(), SIGTSTP); 81 } 82 83 return 0; 84 }
輸出結果:
parent: pid = 8116, ppid = 4151, pgrp = 8116, tpgrp = 8116
child process starts 8117
wait for child process to end
child 1: pid = 8117, ppid = 8116, pgrp = 8116, tpgrp = 8116
child process ends
child stopped, signal number = 20
child continuing...
可見抓到了stopped 和continue 信號
另外,對於處理SIG_CHLD信號的選擇,使用
1 void sig_chld(int signo) 2 { 3 pid_t pid; 4 int stat; 5 6 while((pid = waitpid(-1, &stat, WNOHANG)) > 0){ 7 printf("child %d terminated\n", pid); 8 } 9 return;
緣由是,一是要有循環,能夠避免SIG_CHILD信號丟失的狀況,而是要使用WNOHANG選項,來避免waitpid一直等待。。
使用waitpid而不適用wait的緣由是wait沒法指定NOHANG,使用while循環的緣由是處理信號不被緩存的狀況
當有多個子進程的SIGCHLD信號到達父進程的時候,若是父進程用wait等待,那麼父進程在處理第一個達到的SIGCHLD信號的時候,其餘的SIGCHLD信號被堵塞,並且信號不被緩存,這樣就會致使信號丟失,這樣會產生不少的殭屍進程。。解決辦法是父進程用waitpid來等待子進程信號。。。
正好看到有人問這樣一個問題
看unix網絡編程第一卷的時候,碰到書上這樣一個例子:
一個併發服務器, 每個客戶端鏈接服務器就fork一個子進程.書上講到當同時有n多個客戶端斷開鏈接時,
服務器端同時有n多個子進程終止, 這時候內核同時向父進程發送n多個sigchld信號.它的sigchld信號處理
函數以下:
void sig_chld(int signo)
{
pid_t pid;
int stat;
while((pid = waitpid(-1, &stat, WNOHANG)) > 0){
printf("child %d terminated\n", pid);
}
return;
}
個人問題是:既然sigchld是不可靠的信號,進程就不可能對sigchld進行排隊, 直接丟棄了sigchld信號(當進程註冊信號的時候,發現已有sigchld註冊進未決信號, 由於內核同時發送多個sigchld).請問你們上面的代碼是如何保證不產生殭屍進程的.謝謝!
超清晰的解答,來自與chinaunix論壇的flw大版主。。。:
根本就不須要找回來! 比如有五個進程, 不妨分別稱爲 p1 p2 p3 p4 p5, 一開始 p1 結束了,發了一個 SIGCHLD(s1), 這時父進程可能空閒了,因而開始處理這個信號,假設處理的過程當中 p2 又結束了,又發了一個 SIGCHLD(s2), 這時候已經有兩個信號了(一個正在處理,一個待處理),這時若是 p3 又結束了,那麼它發的那個 SIGCHLD(s3) 勢必會丟失, 丟失了怎麼辦? 不要緊,由於那個信號處理函數是個循環嘛, 因此 while(waitpid()) 的時候,會把 p1 p2 p3 都處理的。 即便是很不幸,由於十分湊巧的緣由,p3 沒有被回收,致使變成殭屍進程了,也不要緊, 由於還有 p4 p5 嘛,等到 p4 或者 p5 結束的時候, 又會再一次調用 while(waitpid()),到時候雖然說這個 while(waitpid()) 是由 p4/p5 引發的,可是它也會一併把 p3 也處理的,由於它是個循環嘛! 若是還搞不懂,你就再看看 waitpid 的 man。 記住一點: waitpid 和 SIGCHLD 不要緊,即便是某個子進程對應的 SIGCHLD 丟失了,只要父進程在任何一個時刻調用了 waitpid,那麼這個進程仍是能夠被回收的。 哎呀呀,簡直費勁死了,其實說白了,就是一個「生產者-消費者」問題。 子進程結束的時候,系統「生產」出一個殭屍進程, 同時用 SIGCHLD 通知父進程來「消費」這個殭屍進程, 即便是 SIGCHLD 丟失了,沒有來得及消費, 可是隻要有一次消費,就會把全部的殭屍進程都處理光光! .(我再說一遍:由於,while(waitpid()) 是個循環嘛!)