Linux信號

信號的機制

A給B發(fā)送信號,B收到信號之前執(zhí)行自己的代碼,收到信號后,不管執(zhí)行到程序的什么位置,都要暫停運行,去處理信號,處理完畢再繼續(xù)執(zhí)行。與硬件中斷類似——異步模式。但信號是軟件層面上實現(xiàn)的中斷,早期常被稱為“軟中斷”。
信號的特質:由于信號是通過軟件方法實現(xiàn),其實現(xiàn)手段導致信號有很強的延時性。但對于用戶來說,這個延遲時間非常短,不易察覺。
每個進程收到的所有信號,都是由內核負責發(fā)送的,內核處理。

與信號相關的事件和狀態(tài)

產生信號:

  1. 按鍵產生,如:Ctrl+c、Ctrl+z、Ctrl+\
  2. 系統(tǒng)調用產生,如:kill、raise、abort
  3. 軟件條件產生,如:定時器alarm
  4. 硬件異常產生,如:非法訪問內存(段錯誤)、除0(浮點數(shù)例外)、內存對齊出錯(總線錯誤)
  5. 命令產生,如:kill命令

遞達:遞送并且到達進程。
未決:產生和遞達之間的狀態(tài)。主要由于阻塞(屏蔽)導致該狀態(tài)。

信號的處理方式:

  1. 執(zhí)行默認動作

  2. 忽略(丟棄)

  3. 捕捉(調用戶處理函數(shù))

    Linux內核的進程控制塊PCB是一個結構體,task_struct, 除了包含進程id,狀態(tài),工作目錄,用戶id,組id,文件描述符表,還包含了信號相關的信息,主要指阻塞信號集和未決信號集。

阻塞信號集(信號屏蔽字): 將某些信號加入集合,對他們設置屏蔽,當屏蔽x信號后,再收到該信號,該信號的處理將推后(解除屏蔽后)

未決信號集:

  1. 信號產生,未決信號集中描述該信號的位立刻翻轉為1,表信號處于未決狀態(tài)。當信號被處理對應位翻轉回為0。這一時刻往往非常短暫。
  2. 信號產生后由于某些原因(主要是阻塞)不能抵達。這類信號的集合稱之為未決信號集。在屏蔽解除前,信號一直處于未決狀態(tài)。

信號的編號
可以使用kill –l命令查看當前系統(tǒng)可使用的信號有哪些。
不存在編號為0的信號。其中1-31號信號稱之為常規(guī)信號(也叫普通信號或標準信號),34-64稱之為實時信號,驅動編程與硬件相關。名字上區(qū)別不大。而前32個名字各不相同。

信號4要素

與變量三要素類似的,每個信號也有其必備4要素,分別是:

  1. 編號 2. 名稱 3. 事件 4. 默認處理動作
    可通過man 7 signal查看幫助文檔獲取。也可查看/usr/src/linux-headers-3.16.0-30/arch/s390/include/uapi/asm/signal.h
    Signal Value Action Comment
    ────────────────────────────────────────────
    SIGHUP 1 Term Hangup detected on controlling terminal or death of controlling process
    SIGINT 2 Term Interrupt from keyboard
    SIGQUIT 3 Core Quit from keyboard
    SIGILL 4 Core Illegal Instruction
    SIGFPE 8 Core Floating point exception
    SIGKILL 9 Term Kill signal
    SIGSEGV 11 Core Invalid memory reference
    SIGPIPE 13 Term Broken pipe: write to pipe with no readers
    SIGALRM 14 Term Timer signal from alarm(2)
    SIGTERM 15 Term Termination signal
    SIGUSR1 30,10,16 Term User-defined signal 1
    SIGUSR2 31,12,17 Term User-defined signal 2
    SIGCHLD 20,17,18 Ign Child stopped or terminated
    SIGCONT 19,18,25 Cont Continue if stopped
    SIGSTOP 17,19,23 Stop Stop process
    SIGTSTP 18,20,24 Stop Stop typed at terminal
    SIGTTIN 21,21,26 Stop Terminal input for background process
    SIGTTOU 22,22,27 Stop Terminal output for background process
    The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.

在標準信號中,有一些信號是有三個“Value”,第一個值通常對alpha和sparc架構有效,中間值針對x86、arm和其他架構,最后一個應用于mips架構。一個‘-’表示在對應架構上尚未定義該信號。
不同的操作系統(tǒng)定義了不同的系統(tǒng)信號。因此有些信號出現(xiàn)在Unix系統(tǒng)內,也出現(xiàn)在Linux中,而有的信號出現(xiàn)在FreeBSD或Mac OS中卻沒有出現(xiàn)在Linux下。這里我們只研究Linux系統(tǒng)中的信號。

  • 默認動作
    Term:終止進程
    Ign: 忽略信號 (默認即時對該種信號忽略操作)
    Core:終止進程,生成Core文件。(查驗進程死亡原因, 用于gdb調試)
    Stop:停止(暫停)進程
    Cont:繼續(xù)運行進程

注意從man 7 signal幫助文檔中可看到 : The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.
這里特別強調了9) SIGKILL 和19) SIGSTOP信號,不允許忽略和捕捉,只能執(zhí)行默認動作。甚至不能將其設置為阻塞。
另外需清楚,只有每個信號所對應的事件發(fā)生了,該信號才會被遞送(但不一定遞達),不應亂發(fā)信號!!

Linux常規(guī)信號一覽表

  1. SIGHUP: 當用戶退出shell時,由該shell啟動的所有進程將收到這個信號,默認動作為終止進程
  2. SIGINT:當用戶按下了<Ctrl+C>組合鍵時,用戶終端向正在運行中的由該終端啟動的程序發(fā)出此信號。默認動
    作為終止進程。
  3. SIGQUIT:當用戶按下<ctrl+>組合鍵時產生該信號,用戶終端向正在運行中的由該終端啟動的程序發(fā)出些信
    號。默認動作為終止進程。
  4. SIGILL:CPU檢測到某進程執(zhí)行了非法指令。默認動作為終止進程并產生core文件
  5. SIGTRAP:該信號由斷點指令或其他 trap指令產生。默認動作為終止里程 并產生core文件。
  6. SIGABRT: 調用abort函數(shù)時產生該信號。默認動作為終止進程并產生core文件。
  7. SIGBUS:非法訪問內存地址,包括內存對齊出錯,默認動作為終止進程并產生core文件。
  8. SIGFPE:在發(fā)生致命的運算錯誤時發(fā)出。不僅包括浮點運算錯誤,還包括溢出及除數(shù)為0等所有的算法錯誤。默認動作為終止進程并產生core文件。
  9. SIGKILL:無條件終止進程。本信號不能被忽略,處理和阻塞。默認動作為終止進程。它向系統(tǒng)管理員提供了可以殺死任何進程的方法。
  10. SIGUSE1:用戶定義 的信號。即程序員可以在程序中定義并使用該信號。默認動作為終止進程。
  11. SIGSEGV:指示進程進行了無效內存訪問。默認動作為終止進程并產生core文件。
  12. SIGUSR2:另外一個用戶自定義信號,程序員可以在程序中定義并使用該信號。默認動作為終止進程。
  13. SIGPIPE:Broken pipe向一個沒有讀端的管道寫數(shù)據(jù)。默認動作為終止進程。
  14. SIGALRM: 定時器超時,超時的時間 由系統(tǒng)調用alarm設置。默認動作為終止進程。
  15. SIGTERM:程序結束信號,與SIGKILL不同的是,該信號可以被阻塞和終止。通常用來要示程序正常退出。執(zhí)行shell命令Kill時,缺省產生這個信號。默認動作為終止進程。
  16. SIGSTKFLT:Linux早期版本出現(xiàn)的信號,現(xiàn)仍保留向后兼容。默認動作為終止進程。
  17. SIGCHLD:子進程結束時,父進程會收到這個信號。默認動作為忽略這個信號。
  18. SIGCONT:如果進程已停止,則使其繼續(xù)運行。默認動作為繼續(xù)/忽略。
  19. SIGSTOP:停止進程的執(zhí)行。信號不能被忽略,處理和阻塞。默認動作為暫停進程。
  20. SIGTSTP:停止終端交互進程的運行。按下<ctrl+z>組合鍵時發(fā)出這個信號。默認動作為暫停進程。
  21. SIGTTIN:后臺進程讀終端控制臺。默認動作為暫停進程。
  22. SIGTTOU: 該信號類似于SIGTTIN,在后臺進程要向終端輸出數(shù)據(jù)時發(fā)生。默認動作為暫停進程。
  23. SIGURG:套接字上有緊急數(shù)據(jù)時,向當前正在運行的進程發(fā)出些信號,報告有緊急數(shù)據(jù)到達。如網(wǎng)絡帶外數(shù)據(jù)到達,默認動作為忽略該信號。
  24. SIGXCPU:進程執(zhí)行時間超過了分配給該進程的CPU時間 ,系統(tǒng)產生該信號并發(fā)送給該進程。默認動作為終止進程。
  25. SIGXFSZ:超過文件的最大長度設置。默認動作為終止進程。
  26. SIGVTALRM:虛擬時鐘超時時產生該信號。類似于SIGALRM,但是該信號只計算該進程占用CPU的使用時間。默認動作為終止進程。
  27. SGIPROF:類似于SIGVTALRM,它不公包括該進程占用CPU時間還包括執(zhí)行系統(tǒng)調用時間。默認動作為終止進程。
  28. SIGWINCH:窗口變化大小時發(fā)出。默認動作為忽略該信號。
  29. SIGIO:此信號向進程指示發(fā)出了一個異步IO事件。默認動作為忽略。
  30. SIGPWR:關機。默認動作為終止進程。
  31. SIGSYS:無效的系統(tǒng)調用。默認動作為終止進程并產生core文件。
  32. SIGRTMIN ~ (64) SIGRTMAX:LINUX的實時信號,它們沒有固定的含義(可以由用戶自定義)。所有的實時信號的默認動作都為終止進程。

信號的產生

  • 終端按鍵產生信號
    Ctrl + c → 2) SIGINT(終止/中斷) "INT" ----Interrupt
    Ctrl + z → 20) SIGTSTP(暫停/停止) "T" ----Terminal 終端。
    Ctrl + \ → 3) SIGQUIT(退出)
  • 硬件異常產生信號
    除0操作 → 8) SIGFPE (浮點數(shù)例外) "F" -----float 浮點數(shù)。
    非法訪問內存 → 11) SIGSEGV (段錯誤)
    總線錯誤 → 7) SIGBUS
  • kill函數(shù)/命令產生信號
    kill命令產生信號:kill -SIGKILL pid
    kill函數(shù):給指定進程發(fā)送指定信號(不一定殺死)
    int kill(pid_t pid, int sig); 成功:0;失敗:-1 (ID非法,信號非法,普通用戶殺init進程等權級問題),設置errno
    sig:不推薦直接使用數(shù)字,應使用宏名,因為不同操作系統(tǒng)信號編號可能不同,但名稱一致。
    pid > 0: 發(fā)送信號給指定的進程。
    pid = 0: 發(fā)送信號給 與調用kill函數(shù)進程屬于同一進程組的所有進程。
    pid < 0: 取|pid|發(fā)給對應進程組。
    pid = -1:發(fā)送給進程有權限發(fā)送的系統(tǒng)中所有進程。
    進程組:每個進程都屬于一個進程組,進程組是一個或多個進程集合,他們相互關聯(lián),共同完成一個實體任務,每個進程組都有一個進程組長,默認進程組ID與進程組長ID相同。
    權限保護:super用戶(root)可以發(fā)送信號給任意用戶,普通用戶是不能向系統(tǒng)用戶發(fā)送信號的。 kill -9 (root用戶的pid) 是不可以的。同樣,普通用戶也不能向其他普通用戶發(fā)送信號,終止其進程。 只能向自己創(chuàng)建的進程發(fā)送信號。普通用戶基本規(guī)則是:發(fā)送者實際或有效用戶ID == 接收者實際或有效用戶ID

raise和abort函數(shù)

raise 函數(shù):給當前進程發(fā)送指定信號(自己給自己發(fā)) raise(signo) == kill(getpid(), signo);
int raise(int sig); 成功:0,失敗非0值

abort 函數(shù):給自己發(fā)送異常終止信號 6) SIGABRT 信號,終止并產生core文件
void abort(void); 該函數(shù)無返回

時鐘產生信號(alarm函數(shù))

設置定時器(鬧鐘)。在指定seconds后,內核會給當前進程發(fā)送14)SIGALRM信號。進程收到該信號,默認動作終止.每個進程都有且只有唯一個定時器
unsigned int alarm(unsigned int seconds); 返回0或剩余的秒數(shù),無失敗。

常用:取消定時器alarm(0),返回舊鬧鐘余下秒數(shù)。

setitimer函數(shù)

設置定時器(鬧鐘)。 可代替alarm函數(shù)。精度微秒us,可以實現(xiàn)周期定時。
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

  • 成功:0;失?。?1,設置errno
  • 參數(shù):which:指定定時方式
    ① 自然定時:ITIMER_REAL → 14)SIGLARM 計算自然時間
    ② 虛擬空間計時(用戶空間):ITIMER_VIRTUAL → 26)SIGVTALRM 只計算進程占用cpu的時間
    ③ 運行時計時(用戶+內核):ITIMER_PROF → 27)SIGPROF 計算占用cpu及執(zhí)行系統(tǒng)調用的時間

信號集操作函數(shù)

內核通過讀取未決信號集來判斷信號是否應被處理。信號屏蔽字mask可以影響未決信號集。而我們可以在應用程序中自定義set來改變mask。已達到屏蔽指定信號的目的。

信號集設定

sigset_t set; // typedef unsigned long sigset_t;
int sigemptyset(sigset_t *set); 將某個信號集清0 成功:0;失?。?1
int sigfillset(sigset_t *set); 將某個信號集置1 成功:0;失?。?1
int sigaddset(sigset_t *set, int signum); 將某個信號加入信號集 成功:0;失敗:-1
int sigdelset(sigset_t *set, int signum); 將某個信號清出信號集 成功:0;失?。?1
int sigismember(const sigset_t *set, int signum);判斷某個信號是否在信號集中 返回值:在集合:1;不在:0;出錯:-1

sigset_t類型的本質是位圖。但不應該直接使用位操作,而應該使用上述函數(shù),保證跨系統(tǒng)操作有效。
對比認知select 函數(shù)。

sigprocmask函數(shù)

用來屏蔽信號、解除屏蔽也使用該函數(shù)。其本質,讀取或修改進程的信號屏蔽字(PCB中)
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

嚴格注意,屏蔽信號:只是將信號處理延后執(zhí)行(延至解除屏蔽);而忽略表示將信號丟處理。

  • 返回值
    成功:0;失?。?1,設置errno
  • 參數(shù)
    set:傳入?yún)?shù),是一個位圖,set中哪位置1,就表示當前進程屏蔽哪個信號。
    oldset:傳出參數(shù),保存舊的信號屏蔽集。
    how參數(shù)取值: 假設當前的信號屏蔽字為mask
  1. SIG_BLOCK: 當how設置為此值,set表示需要屏蔽的信號。相當于 mask = mask|set
  2. SIG_UNBLOCK: 當how設置為此,set表示需要解除屏蔽的信號。相當于 mask = mask & ~set
  3. SIG_SETMASK: 當how設置為此,set表示用于替代原始屏蔽集的新屏蔽集。相當于 mask = set,調用sigprocmask解除了對當前若干個信號的阻塞,則在sigprocmask返回前,至少將其中一個信號遞達。

sigpending函數(shù)

讀取當前進程的未決信號集
int sigpending(sigset_t *set); set傳出參數(shù)。 返回值:成功:0;失?。?1,設置errno

信號捕捉

signal函數(shù)

注冊一個信號捕捉函數(shù):
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

該函數(shù)由ANSI定義,由于歷史原因在不同版本的Unix和不同版本的Linux中可能有不同的行為。因此應該盡量避免使用它,取而代之使用sigaction函數(shù)。

sigaction函數(shù)

修改信號處理動作(通常在Linux用其來注冊一個信號的捕捉函數(shù))
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

  • 返回值
    成功:0;失?。?1,設置errno
  • 參數(shù)
    act:傳入?yún)?shù),新的處理方式。
    oldact:傳出參數(shù),舊的處理方式。

struct sigaction結構體

    struct sigaction {
        void     (*sa_handler)(int);
        void     (*sa_sigaction)(int, siginfo_t *, void *);
        sigset_t   sa_mask; 
        int       sa_flags; 
        void     (*sa_restorer)(void);
    };

sa_restorer:該元素是過時的,不應該使用,POSIX.1標準將不指定該元素。(棄用)
sa_sigaction:當sa_flags被指定為SA_SIGINFO標志時,使用該信號處理程序。(很少使用)
重點掌握
① sa_handler:指定信號捕捉后的處理函數(shù)名(即注冊函數(shù))。也可賦值為SIG_IGN表忽略 或 SIG_DFL表執(zhí)行默認動作
② sa_mask: 調用信號處理函數(shù)時,所要屏蔽的信號集合(信號屏蔽字)。注意:僅在處理函數(shù)被調用期間屏蔽生效,是臨時性設置。
③ sa_flags:通常設置為0,表使用默認屬性。

信號捕捉特性

  1. 進程正常運行時,默認PCB中有一個信號屏蔽字,假定為☆,它決定了進程自動屏蔽哪些信號。當注冊了某個信號捕捉函數(shù),捕捉到該信號以后,要調用該函數(shù)。而該函數(shù)有可能執(zhí)行很長時間,在這期間所屏蔽的信號不由☆來指定。而是用sa_mask來指定。調用完信號處理函數(shù),再恢復為☆。
  2. XXX信號捕捉函數(shù)執(zhí)行期間,XXX信號自動被屏蔽。
  3. 阻塞的常規(guī)信號不支持排隊,產生多次只記錄一次。(后32個實時信號支持排隊)

內核實現(xiàn)信號捕捉過程:

image.png

SIGCHLD信號

SIGCHLD的產生條件

子進程終止時
子進程接收到SIGSTOP信號停止時
子進程處在停止態(tài),接受到SIGCONT后喚醒時

借助SIGCHLD信號回收子進程

子進程結束運行,其父進程會收到SIGCHLD信號。該信號的默認處理動作是忽略??梢圆蹲皆撔盘?,在捕捉函數(shù)中完成子進程狀態(tài)的回收。

/*************************************************************************
    > File Name: sigchild.c
    > Author: fujie
    > Mail: 1243596620@qq.com 
    > Created Time: 2020年09月04日 星期五 00時10分02秒
 ************************************************************************/

#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

void sys_err(char *str)
{
    perror(str);
    exit(1);
}

void do_sig_child(int signo){
    int status;
    pid_t pid;

    while((pid = waitpid(0,&status,WNOHANG))>0){
        if(WIFEXITED(status)){
            printf("child %d exit %d\n",pid,WEXITSTATUS(status));
        }else if(WIFSIGNALED(status)){
            printf("child %d cancel signal %d\n",pid,WTERMSIG(status));
        }
    }
}


int main(void){
    pid_t pid;
    int i;
    for(i=0;i<10;i++){
        if((pid=fork())==0){
            break;
        }else if(pid<0)
            sys_err("fork");
    }

    if(pid == 0){
        int n = 1;
        while(n--){
            printf("child ID %d\n",getpid());
            sleep(1);
        }
        return i+1;
    }else if(pid>0){
        struct sigaction act;
        act.sa_handler = do_sig_child;
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        sigaction(SIGCHLD,&act,NULL);

        while(1){
            printf("Parent ID %d\n",getpid());
            sleep(1);
        }
    }
    return 0;
}

子進程結束status處理方式

pid_t waitpid(pid_t pid, int *status, int options)

  • options
    WNOHANG
    沒有子進程結束,立即返回
    WUNTRACED
    如果子進程由于被停止產生的SIGCHLD,waitpid則立即返回
    WCONTINUED
    如果子進程由于被SIGCONT喚醒而產生的SIGCHLD,waitpid則立即返回

  • 獲取status
    WIFEXITED(status)
    子進程正常exit終止,返回真
    WEXITSTATUS(status)返回子進程正常退出值
    WIFSIGNALED(status)
    子進程被信號終止,返回真
    WTERMSIG(status)返回終止子進程的信號值
    WIFSTOPPED(status)
    子進程被停止,返回真
    WSTOPSIG(status)返回停止子進程的信號值
    WIFCONTINUED(status)

SIGCHLD信號注意問題

  1. 子進程繼承了父進程的信號屏蔽字和信號處理動作,但子進程沒有繼承未決信號集spending。
  2. 注意注冊信號捕捉函數(shù)的位置。
  3. 應該在fork之前,阻塞SIGCHLD信號。注冊完捕捉函數(shù)后解除阻塞。
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容