因為筆者之前的文章里面有錯誤,今天發現,立馬做個修改。在下面我的一段關于sigchld信號相對于直接調用wait函數的好處時,我說調用wait函數要一直檢測子進程是否執行完其實是錯誤的, wait是阻塞函數,當主進程調用wait函數的時,主進程處于阻塞狀態,并沒有一直檢測的動作,他只是在等待,假設我們有三個子進程(編號1,2,3)假設2,3進程先執行完,然而1號子進程還沒有執行完,那么主進程的wait就一直處于阻塞狀態,這時候2,3可能產生僵尸進程。這里sigchld和wait的區別就很明顯了。
先來看看信號的基本概念:
信號kill-l查看linux信號及其宏定義編號,其中1~31非實時編號(發送的信號可能丟失,不支持信號排隊),31~64實時信號,發送的信號都會被接收(支持信號排隊)
信號的定義在/usr/include/bits/signum.h
1.信號是軟件中斷
2.信號是異步信號
3.信號的來源:
(1)、硬件來源:主要是硬件的驅動產生,如鍵盤,鼠標等
(2)、軟件來源:主要是一些信號函數、比如kill()、raise()、alarm()、setitimer()等函數,軟件來源包括一些非法運算等操作,軟件設置條件(gdb調試),信號由內核產生
#信號的處理
進程會采取三種方式響應和處理信號
1.忽略信號,sigkill和sigstop永遠不被忽略,忽略硬件異常、進程啟動時,sigusr1和sigusr2被忽略
2.執行默認操作
3.捕獲信號。告訴內核信號出現時調用自己的處理函數,SIGKILL和SIGSTOP不能被捕獲
#信號登記
void(*signal(int signo,void (*func)(int))(int)
signo--要登記的信號編號或者信號宏
func--信號處理函數指針、忽略信號(SIG_IGN)、默認信號(SIG_DEF)
今天我就不針對每個信號詳細介紹了,你也沒必要知道那么多信號,今天介紹一個很重要的信號,SIGCHLD這個信號的作用如下:
SIGCHLD?子進程狀態發生變化產生該信號(子進程運行結束)父進程調用wait函數,回收子進程的進程表項,task_struct結構體。有了這個信號父進程不需要處于阻塞狀態,任然可以干其他事情,當子進程結束時發送一個SIGCHLD信號給父進程,父進程調用wait回收子進程,避免僵尸進程的產生,提高了資源利用率。
?
再了解這個信號之前,先來簡單了解一下wait()函數:
?
pid_t wait(int *status)//狀態
<unistd.h>
<sys/wait.h>
等待子進程退出并回收,防止僵尸進程(子進程運行結束,但是內核中的task_struct沒有釋放)產生,凡是調用wait()就會阻塞,父進程等待,定時檢查子進程是否執行完畢
返回子進程id
pid_t waitpid(pid_t pid, int *status, int options);
成功返回子進程id,這是wait的非阻塞版本。
wait和waitpid區別:
1.在一個子進程終止前,wait使調用者阻塞
2.waitpid有一個選擇項,可以使調用者不阻塞
3。waitpid可以等待指定的一個子進程,wait等待所有的子進程,返回任意一個種植的子進程狀態。
子進程在運行中有暫停信號如果想要顯示暫停信號的信號碼不能使用wait()要用waitpid()
waitpid的宏WNOHANG(非阻塞)WUNTRACED(監聽信號)
我們處理僵尸進程有兩種方式:
1.kill -9 父進程 讓init進程回收僵尸進程
2.wait() 和 waitpid()讓父進程等待回收子進程
下面我們來使用信號實現解決和避免僵尸進程的第三種的方式:
[cpp]?view plain?copy
- #include<stdio.h>??
- #include<stdlib.h>??
- #include<sys/wait.h>??
- #include<signal.h>??
- void?sig_handler(int?signo)??
- {??
- ????printf("child?process??%d?stop\n",?signo);??
- ??
- }??
- void?out(int?n)??
- {??
- ????for(int?i?=?0;?i?<?n;?++i)??
- ????{??
- ????????printf("%d?out?%d\n",?getpid(),?i);??
- ????????sleep(1);??
- ????}??
- }??
- int?main(void)??
- {??
- ????if(signal(SIGCHLD,?sig_handler)?==?SIG_ERR)??
- ????{??
- ????????perror("sigchld?error");??
- ????????exit(1);??
- ????}??
- ????pid_t?pid?=?fork();??
- ????if(pid?<?0)??
- ????{??
- ????????perror("fork?error");??
- ????????exit(1);??
- ????}??
- ????else?if(pid?>?0)??
- ????{??
- ????????out(100);??
- ????}??
- ????else??
- ????{??
- ????????out(20);??
- ????}??
- ????return?0;??
- }??
這段代碼使用signal系統調用來捕獲信號,我們在signal里面注冊了SIGCHLD信號,程序中我們讓子進程現執行完,然后捕獲子進程執行完畢的信號。
下面是運行結果部分截圖:
?
這里進程pid3546為父進程3547為子進程
我們再次運行程序來觀察程序的運行狀態,把程序編譯gcc signal_sigchld.c -o child
運行程序./child
使用命令ps -aux|grep child 來觀察程序運行狀態,下面是結果截圖
你可以看到父子進程在子進程運行到20以前都處于S+即運行狀態,當子進程到達20的時候,signal捕獲到子進程退出的信號SIGCHLD
這時候子進程狀態變為Z+即僵尸進程。
?
所以我們說了那么多,為什么SIGCHLD沒有處理這個僵尸進程呢,這里我們要搞清楚,SIGCHLD只是子進程在運行結束的時候產生的一i個信號,我們要想處理這個僵尸狀態,還是要用到上面說的兩種方式。最好就是父進程調用wait(),你可能有要問,既然都要用到wait,那抹干嗎多此一舉使用信號呢?首先要知道父進程調用wait以后處于阻塞狀態,父進程不能干其他事情,使用效率降低,資源利用率低下,增加了開銷,而調用信號以后,當子進程執行完畢以后,自動產再生一個信號給父進程,父進程收到信號以后就調用wait揮手子進程沒有釋放的資源。這樣我的感覺就是子進程化被動為主動。父進程的工作也輕松了不少,可以做自己想做的事情。
?
所以為了避免僵尸進程的產生我們修改上面的代碼中的sig_handler函數如下:
[cpp]?view plain?copy
- void?sig_handler(int?signo)??
- {??
- ????printf("child?process??%d?stop\n",?signo);??
- ????wait(0);??
- }??
當父進程捕獲到SIGCHLD后調用wait。
按照上述步驟重新編譯,運行,用ps觀察進程運行狀態:
上面是子進程運行到20之前,下面看子進程運行完畢,父進程捕獲到SIGCHLD以后
這里你發現子進程沒有顯示,是因為紫禁城已經被回收釋放掉了。
這就是處理僵尸進程的第三種方式。
也是一種異步處理方式。