Linux: 關於 SIGCHLD 的更多細節

殭屍進程

何爲殭屍進程?
一個進程使用fork建立子進程,若是子進程退出,而父進程並無調用 wait 或 waitpid
獲取子進程的狀態信息,那麼子進程的進程描述符仍然保存在系統中。這種進程稱之爲殭屍進程
成爲殭屍進程的因素
  1. 子進程 先於 父進程退出;
  2. 子進程的狀態信息,沒有被父進程回收;

那麼問題來了,子進程退出了,父進程怎麼知道呢?segmentfault

對該機制有稍微瞭解的話,不可貴知一個關鍵因素:SIGCHLD。正是這個SIGCHLD起到了通知的做用,因此後面的處理也是基於它而實現。spa

殭屍進程處理方案
  1. 父進程捕獲 SIGCHLD 信號,則顯示調用 waitwaitpid
  2. 父進程直接忽略該信號。signal(SIGCHLD, SIG_IGN),這樣子進程直接會退出。操作系統

    須要注意的是,雖然進程對於 `SIGCHLD`的默認動做是忽略,可是仍是顯示寫出來,纔能有效;
  3. 把父進程殺了,子進程直接過繼給 init,由 init伺候着。
    不用擔憂 init會掛着一堆殭屍, init自己的設計就有專門回收的處理,因此有多少回收多少;

SIGCHLD 還能幹嗎

剛纔咱們在處理到父子進程相關的問題時,多多少少接觸到SIGCHLD, 那麼,只有在回收子進程的時候才須要用到麼?感受好浪費不是麼?設計

別擔憂 ~ 這個做用確定是不止這樣的!code

其實對於SIGCHLD,咱們通常的理解是,子進程退出發送的信號,但其實不是的,這個信號表明的含義是:blog

子進程狀態變動了,例如中止、繼續、退出等,都會發送這個信號通知父進程。而父進程就能經過 wait/waitpid 來獲悉這些狀態了。

看起來有點意思,咱們彷彿能借此作些有趣的事情了。進程

wait / waitpid 相關知識
#include <sys/wait.h>
pid_t wait(int * statloc);
pid_t waitpid(pid_t pid,int *statloc,int options);

wait相對來講會經常使用點,由於不須要指定 pid,而waitpid就在一些須要指定特定pid時纔會比較常見,那麼它們之間的關係就真的是隻是這樣麼?ip

其實wait是對waitpid的封裝,專門用來回收子進程退出的信息,一樣的,它簡單粗暴的設置成了堵塞方式,若是沒有任何子進程退出,那麼就堵塞住。get

waitpid功能很是強大,pidoptions都提供了很是靈活的用法:string

pid:
         < -1: 取該 pid 的絕對值,若是任何子進程的進程組ID等於該值,則該進程組的任一子進程中的進程狀態發生變化,都會觸發`waitpid`的回調;
        == -1: 監聽範圍擴大到任意子進程,也就是 wait(status);
        ==  0: 監聽進程組ID和父進程同樣的子進程;
         >  0: 監聽該pid的子進程;

    options:
        WNOHANG: 調用時,指定的 pid 仍未結束運行,則 wait 當即返回 0;
        WUNTRACED: 當子進程被暫停時,則當即返回子進程的 pid;
        WCONTINUED: 當被暫停的子進程,又被信號恢復時,則當即返回子進程的pid;

而下面這些宏,將搭配status一塊兒使用:

WIFEXITED(status): 當子進程調用 exit、_exit或者正常從 main 返回等正常結束時,返回 true 
    --> WEXITSTATUS(status): 獲取上面的 exit_code
        
WIFSIGNALED(status): 當子進程被信號殺死時,返回 true;
    --> WTERMSIG(status): 獲取信號的值(int)

WIFSTOPPED(status): 當本身弄成被信號暫停執行時,返回 true;
    --> WSTOPSIG(status): 獲取該信號的值

WIFCONTINUED(status): 子進程接收到SIGCONT信號繼續執行時,返回 true
最小實踐

咱們來個最小的 demo 來講明上面的怎麼用:

#include<stdio.h>
#include<string.h>
#include<signal.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<unistd.h>
int main(){
    int pid;
    if((pid = fork()) == 0){
        while(1){
            printf("Child: %d\n", getpid());
            sleep(1);
        }
    }
    else{
        int status;
        pid_t w;
        while(1){
            // 但願堵塞,因此沒用 WNOHANG
            w = waitpid(pid, &status,WCONTINUED | WUNTRACED);
            if(WIFEXITED(status)){
                printf("子進程正常退出,狀態碼: %d\n", WEXITSTATUS(status));
                exit(0);
            } else if(WIFSIGNALED(status)){
                printf("子進程被信號殺死了! 信號值: %d\n", WTERMSIG(status));
                exit(0);
            } else if(WIFSTOPPED(status)){
                printf("子進程被信號暫停了! 信號值: %d\n", WSTOPSIG(status));
            } else if(WIFCONTINUED(status)){
                printf("子進程又恢復繼續運行了\n");
            }
        }
    }
}

終端輸出:

Child: 10848
Child: 10848                    # 子進程的 pid

子進程被信號暫停了!信號值:21       # kill -SIGTTIN 10848
子進程又恢復繼續運行了                # kill -SIGTTIN 10848
...
子進程被信號暫停了! 信號值: 19      # kill -SIGSTOP 10848
子進程又恢復繼續運行了                # kill -SIGTTIN 10848   
...
子進程被信號殺死了! 信號值: 15      # kill -SIGTERM 10848    

若是本身在子進程上面加個退出,就會打印:正常退出了

結語

在上面的實驗中,咱們已經發現經過SIGCHLD除了用來回收子進程,還能獲悉子進程的狀態!

在操做系統上,也有不少利用這個在工做的,例如:後臺進程,若是向標準輸入讀取內容時,是會被暫停的

clipboard.png
clipboard.png

爲何呢?

由於後臺進程,是和終端斷開鏈接的,當它從標準輸入讀取內容時,終端的驅動程序會發現這個操做,會發一個 SIGTTIN 給後臺進程,讓其暫停,而且通知用戶,只有用戶經過 fg 命令將其轉換成 前臺進程時,才能繼續工做

clipboard.png

正是有這樣的一套機制,因此咱們也能作出不少比較實在的東西了~

歡迎各位大神指點交流, QQ討論羣: 258498217
轉載請註明來源: https://segmentfault.com/a/11...

相關文章
相關標籤/搜索