IPC——信號

延伸閱讀:Linux命令——trap    Linux命令——killall 、kill 、pkill、xkillhtml

什麼是信號ubuntu

  信號是一種通知進程某件事情發生了的一種通訊機制,經過向進程發送某個信號,能夠告訴進程發生了什麼事情,進程收到這個信號後,就知道某事情發生了,進程能夠作出相應的響應(處理)。與IPC中其餘進程通訊方式不一樣的是,信號屬於不精確通訊,信號只能告訴進程大概發生了什麼事情,可是不能準確的告訴進程詳細的細節信息。Linux下邊定義了不少的信號,全部的信號都是一個整數編號,不過爲了好辨識,Linux系統給這些整數編號都定義了對應的宏名,宏名都是以SIG開頭,好比SIGABRT。緩存

誰會向進程發信號ide

總結起來,會有三個「人」會向進程發送信號,分別是「另外一進程」、「OS內核」、「硬件」。函數

另外一個進程發送信號post

eg:在命令行終端窗口經過kill命令向某個進程發送一個信號將其終止。測試

kill   PIDui

內核發送信號url

發生了某個事件,Linux內核可能會發送該事件對應的信號給某個進程spa

eg管道通訊中,當全部讀文件描述符被關閉,進程會被內核發送一個SIGPIPE信號,提示讀管道出錯了。

這個過程再詳細一點?

計算機內部全部硬件鏈接在總線上。全部部件按照仲裁總線 或 中斷總線上給出的信號來判斷這個時刻總線能夠由哪一個部件來使用。產生仲裁總線 或 者中斷點位的能夠是CPU,也能夠是總線上的其餘設備。若是CPU要向某個設備作輸出操做,那麼就由CPU主動作中斷。若是某個設備請求向CPU發信號,則由這個設備主動產生中斷信號來通知CPU 。CPU運行操做系統內核的設備管理程序,從而產生了這些信號。

底層硬件發送信號

底層硬件發生了某個事件,會向進程發送對應的某個信號。

eg:按下ctrl+c按鍵終止進程時,內核收到ctrl+c按鍵後,會向正在運行的進程發送SIGINT信號,將其異常終止。

 注意:無論進程是被哪個信號給終止了,只要是被信號終止的,都是異常終止。

通常來講,大多數發送信號的緣由,都是由於內核、硬件發生了某些事件時,纔會向某個進程發送該事件專用的信號,告訴該進程這個事件發生了。對於咱們本身寫的進程來講,其實更可能是接收信號,而不是發送信號。

 

收到信號後進程如何應對

忽略

「鴕鳥策略」的作法,進程就當信號歷來沒有發生過。

捕獲

進程會調用相應的處理函數,進行相應的處理。

默認

若是不忽略也不捕獲的話,此時進程會使用系統設置的默認處理方式來處理信號。

 

Linux系統有哪些信號

查看Linux系統下有哪些信號

[root@localhost ~]# 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
16) SIGSTKFLT    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  

總共62個信號,也就是說每一個進程能夠接收的信號種類有62種,1~64爲信號的編號,SIG***爲信號的宏名。每一個信號表明着某種事件,通常狀況下,當進程收到某個信號時,就表示該信號所表明的事件發生了。

1~34:也不是全部的信號都要掌握,咱們只須要關心其中經常使用的信號。

35~64:這些信號是Linux後期增設的信號,這些個信號不須要關心,因此不用瞭解。

經常使用信號

 

常見由用戶發出終止進程的信號

Ctrl + C 發送SIGINT

Ctrl + \ 發送SIGQUIT

kill pid 發送SIGTERM

只有當進程有佔用命令行終端時,才能Ctrl + C、Ctrl + \ 來終止。當沒法使用Ctrl + C、Ctrl + \ 來終止進程時,每每就使用kill命令來終止進程。

 

kill 和 pkill

Linux命令——killall 、kill 、pkill、xkill

kill這個命令名字很嚇人。其實kill只是發送信號,至於進程會不會被終止,這就看信號的處理方式,處理方式若是是終止,那麼就會終止進程。若是把kill起名爲send估計更好理解些,由於kill所起到的做用只是發送信號。

發送信號的完整格式:kill  -信號編號  PID

信號編號寫數字和宏名均可以。若是不寫明信號編號的話:kill PID,默認發送的是15(SIGTERM)信號,等價於kill -SIGTERM PID或者kill -15 PID。只有發送15這個信號時才能省略信號編號,發送其它信號時必須寫明信號編號。

 

pkill用法與kill差很少,只不過kill是按照PID來識別進程的,pkill是按照名字來識別進程的。

發送信號的完整格式:pkill -信號編號 名字

一樣,若是不寫明信號編號的話,默認發送的是15(SIGTERM)這個信號。

 

core文件

  用於保存程序(進程)在當前結束的這一刻,進程在內存中的代碼和數據,core文件能夠用於分析進程在結束時的情況,不過因爲進程代碼和數據都是二進制的,因此把core文件直接打開後咱們是看不懂的,通常須要特殊軟件翻譯後才能看懂。並非全部的信號在終止進程時都會產生core文件,只有某個些信號在終止進程時纔會產生core文件,不過通常狀況下並不會建立這個文件,由於系統默認將產生core的設置給關閉了,只有打開後這個設置後纔會保存core文件。因此當你看到提示core dumped,這就表示這個信號終止進程時,會產生core文件,只不過因爲關閉了設置,所以core文件被丟棄了,dumped就是丟棄的意思。

[root@localhost ~]# ./a.out 
^\Quit (core dumped)

 

Linux信號處理API

signal函數

函數原型

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

功能

設置某個信號的處理方式。處理方式能夠被設置爲忽略,捕獲,默認。

進程的進程表(task_struct)中會有一個「信號處理方式登記表」,專門用於記錄信號的處理方式,調用signal函數設置某個信號的處理方式時,會將信號的處理方式登記到該表中。每一個進程擁有獨立的task_struct結構體變量,於是每一個進程的「信號處理方式登記表」都是獨立的,因此每一個進程對信號的處理方式天然也是獨立的,互不干擾。

參數

signum:

信號編號。handler個函數指針類型變量,函數指針類型是void (*)(int)

handler:

能夠取值

#define SIG_DFL    ((void (*)(int))0)    
#define SIG_IGN    ((void (*)(int))1)
#define SIG_ERR    ((void (*)(int))-1) 

這幾個宏定義在了<signal.h>頭文件中。

①忽略:SIG_IGN

除了SIGKILL(無條件終止一個進程)這個信號外,其它全部的信號均可被忽略和捕獲。

②默認:SIG_DFL

③捕獲:填寫類型爲void (*)(int)的捕獲函數的地址,當信號發生時,會自動調用捕獲函數來進行相應的處理。固然這個捕獲函數須要咱們本身來實現,捕獲函數的int參數,用於接收信號編號。捕獲函數也被稱爲信號處理函數

void signal_fun1(int signo){...}                     
void signal_fun2(int signo){...}
                
int main(void)
{
    signal(SIGINT, signal_fun1);
    signal(SIGSEGV, signal_fun2);                        
    return 0;
}

信號捕獲函數調用時機:

進程接收到信號時就調用,調用時會中斷進程的正常運行,當調用完畢後再會返回進程的正常運行。

更正前面的一個說法

在一開始提到:無論進程是被哪個信號給終止了,只要是被信號終止的,都是異常終止。

這種表達方式不嚴謹,應該說若是是在信號處理函數裏面調用exit、_exit來終止進程的這種方式的話,準確來說不該該算是「」信號異常終止進程」的狀況。信號異常終止進程,準確來說指的是信號默認的終止方式,這種狀況纔是「異常終止」。

返回值

成功:返回上一次的處理方式

失敗:返回宏值SIG_ERR,而且設置errno。

 

kill

函數原型

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

功能

向PID所指向的進程發送指定的信號。

返回值

成功返回0,失敗返回-1,errno被設置。

 raise

函數原型

#include <signal.h>
int raise(int sig);

功能

向當前進程發送指定信號。

返回值

成功返回0,失敗返回非0。

 

alarm

alarm函數不會阻塞

函數原型

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

功能

設置一個定時時間,當所設置的時間到後,內核會向調用alarm的進程發送SIGALRM信號。SIGALRM的默認處理方式是終止。

測試代碼

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include<signal.h>
int main(int argc,char**argv,char**environ)
{
    alarm(5);
    while(1);
    return 0;
}

返回值

返回上一次調用alarm時所設置時間的剩餘值。若是以前沒有調用過alarm,又或者以前調用alarm所設置的時間早就到了,那麼返回的剩餘值就是0。

 

pause

函數原型

#include <unistd.h>
int pause(void);

功能

調用該函數的進程會永久掛起(阻塞或者休眠),直至被信號(任意一個信號)喚醒爲止。

返回值

只要一直處於休眠狀態,表示pause函數一直是調用成功的。當被信號喚醒後會返回-1,表示失敗了,errno的錯誤號被設置EINTR(表示函數被信號中斷)。

 

sleep

函數原型

#include <unistd.h>
unsigned int sleep(unsigned int seconds);

功能

調用該函數的進程會休眠若干seconds,休眠期間能夠被信號喚醒

返回值 

若是sleep時間到了,返回0。若是sleep期間被信號中斷了,返回剩餘秒數。

 

abort

函數原型

#include <stdlib.h>
void abort(void);

功能

向當前進程發一個SIGABRT信號,這個信號的默認處理方式是終止,所以若是不忽略和捕獲的話,會將當前進程終止掉。abort至關於raise的特例,只發送SIGABRT信號自當前進程。這個函數有個綽號:自殺函數

返回值

 

進程的休眠與喚醒

調用sleep、pause等函數時,這些函數會使進程進入休眠狀態。藉助信號機制能夠實現喚醒功能 ,這須要咱們建立空捕獲函數(給信號登記一個空捕獲函數,函數內容通常都是空的)。其喚醒過程以下:

當信號發送給進程後,會中斷當前休眠的函數,而後去執行捕獲函數,捕獲函數執行完畢返回後,再也不調用休眠函數,而是執行休眠函數以後的代碼,這樣函數就被喚醒了。

若是喚醒後想繼續休眠怎麼辦?

藉助goto語句造成循環

學C的時候不是很反對用goto嗎?  

pause休眠喚醒後繼續休眠

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

void signal_fun(int signo)
{
    printf("!!!!!!!\n");
}

int main(int argc, char **argv, char **environ)
{
    int ret = 0;    
    signal(SIGINT, signal_fun);

lable:    ret = pause();
    if(ret == -1 && errno == EINTR)
    {
        goto lable;
    }

    char buf[100] = {0};
    read(0, buf, sizeof(buf));
    printf("hello\n");
    while(1);
    return 0;
}
View Code

sleep休眠喚醒後繼續休眠

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

void signal_fun(int signo)
{
    printf("!!!!!!!\n");
}

int main(int argc, char **argv, char **environ)
{
    int ret = 0;    
    signal(SIGINT, signal_fun);

    ret = 10;
lable1:    ret =sleep(ret);
    if(ret != 0)
    {
        printf("ret = %d\n", ret);
        goto lable1;
    }

    char buf[100] = {0};
    read(0, buf, sizeof(buf));
    printf("hello\n");
    while(1);
    return 0;
}
View Code

pause、sleep致使的休眠,喚醒後想再次休眠須要手動重啓,全部函數(會致使休眠的)都須要手動重啓嗎?

並非,對於絕大多數休眠函數來講,被信號中斷後,若是你想繼續休眠的話,須要本身去手動重啓,不然就會繼續向後運行。這裏說一個比較特殊的函數read

read

函數原型 

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count); 

功能

從fd指向的文件中,將數據讀到應用緩存buf中

參數

fd:指向打開的文件

buf:讀取到數據後,用於存放數據的應用緩存的起始地址

count:緩存大小(字節數)

返回值

成功:返回讀取到的字符的個數

失敗:返回-1,並自動將錯誤號設置給errno。

 

 

信號的發送、接收和處理的過程

信號屏蔽字

做用

用來屏蔽信號,有點像公司前臺,信號來了先問前臺(屏蔽字),我能被當即處理不,能就當即處理,不能處理就去未處理集呆着

每一個進程可以接收的信號有62種,信號屏蔽字的每一位記錄了每一個信號是被屏蔽的仍是被打開的。

若是是打開的就當即處理。

若是是屏蔽的就暫不處理

信號屏蔽字放在那裏

每個進程都有一個信號屏蔽字,它被放在了進程表(task_struct結構體變量)中。

如何修改信號屏蔽字

工做原理

定義一個64位的 與屏蔽字相似的 變量,將該變量設置爲要的值,再經過sigprocmask函數將某信號對應的位設置爲0或者爲1。

如何設置這麼個變量

函數原型

#include <signal.h>
            
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);

set就是咱們前面說的變量,至於變量名也能夠定義爲其它的名字,不必定非要叫set。

功能

設置變量的值
1)sigemptyset:將變量set的64位所有設置爲0。
2)sigfillset:將變量set的64位所有設置爲1。
3)sigaddset:將變量set中,signum(信號編號)對應的那一位設置爲1,其它爲不變。
4)sigdelset:將變量set的signum(信號編號)對應的那一位設置爲0,其它位不變。

返回值

調用成功返回0,失敗返回-1,而且errno被設置。

sigprocmask

函數原型

#include <signal.h>      
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

功能

使用設置好的變量set去修改信號屏蔽字。

參數

①how:修改方式,有三種。

一、SIG_BLOCK:屏蔽某個信號
屏蔽字=屏蔽字 | set

二、SIG_UNBLOCK:打開某個信號(不要屏蔽),實際就是對屏蔽字的某位進行清0操做。
屏蔽字=屏蔽字&(~set)

三、SIG_SETMASK:直接使用set的值替換掉屏蔽字

②set:set的地址

③oldset:保存修改以前屏蔽字的值
若是寫爲NULL的話,就表示不保存。

返回值

函數調用成功返回0,失敗返回-1。

代碼演示

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

void signal_fun(int signo)
{
    sigset_t set;
    printf("hello\n");
    sigemptyset(&set);
    sigaddset(&set, SIGINT);
    sigprocmask(SIG_UNBLOCK, &set, NULL);
    sleep(3);
    printf("world\n");    
}

int main(int argc, char **argv, char **environ)
{
    pid_t ret = 0;
    signal(SIGINT, signal_fun);
    while(1);
    return 0;
}
View Code

執行結果,按下Ctrl+C時觸發信號處理函數。這個進程殺不掉,必須用kill pid的方式

root@ubuntu:~# ./a.out 
^Chello
world
^Chello
world
^Chello
world
Terminated

 

未處理信號集

跟屏蔽字同樣,也一個64位的無符號整形數,專門用於記錄未處理的信號。「未處理信號集」一樣也是被放在了進程的進程表中(task_struct)。

信號來了,當進程的信號處理機制,檢查該信號在屏蔽字中的對應位時發現是1,表示該信號被屏蔽了,暫時不能被處理,此時就會將「未處理信號集」中該信號編號所對應的位設置爲1,這個記錄就表示,有一個信號未被處理。若是該信號發送了屢次,可是每一次都由於被屏蔽了而沒法處理的話,在「未處理信號集」中只記錄一次。當屏蔽字中該信號的位變成0時(被打開了),此時就回去檢查「未處理信號」,看該信號有沒有未決的狀況,有的話就處理它。

相關文章
相關標籤/搜索