Linux IPC 信號 kill,signal,sigaction,sigprocmask,sigpending信號處理機制

什麼是信號

信號是進程在運行過程中,由自身產生或由進程外部發過來的消息(事件)。信號是硬件中斷的軟件模擬(軟中斷)。每個信號用一個整型常量宏表示,以 SIG 開頭,比如 SIGCHLD、SIGINT 等,它們在系統頭文件 <signal.h>中定義,也可以通過在 shell 下鍵入 kill –l 查看信號列表, 或者鍵入 man 7 signal 查看更詳細的說明。

        信號         值      動作   說明
       ─────────────────────────────────────────────────────────────────────
       SIGHUP        1       A     在控制終端上是掛起信號, 或者控制進程結束
       SIGINT        2       A     從鍵盤輸入的中斷
       SIGQUIT       3       C     從鍵盤輸入的退出
       SIGILL        4       C     無效硬件指令
       SIGABRT       6       C     非正常終止, 可能來自 abort(3)
       SIGFPE        8       C     浮點運算例外
       SIGKILL       9      AEF    殺死進程信號
       SIGSEGV      11       C     無效的內存引用
       SIGPIPE      13       A     管道中止: 寫入無人讀取的管道
       SIGALRM      14       A     來自 alarm(2) 的超時信號
       SIGTERM      15       A     終止信號
       SIGUSR1   30,10,16    A     用戶定義的信號 1
       SIGUSR2   31,12,17    A     用戶定義的信號 2
       SIGCHLD   20,17,18    B     子進程結束或停止
       SIGCONT   19,18,25          繼續停止的進程
       SIGSTOP   17,19,23   DEF    停止進程
       SIGTSTP   18,20,24    D     終端上發出的停止信號
       SIGTTIN   21,21,26    D     後臺進程試圖從控制終端(tty)輸入
       SIGTTOU   22,22,27    D     後臺進程試圖在控制終端(tty)輸出

信號的生成來自內核,讓內核生成信號的請求來自 3 個地方
用戶:用戶能夠通過輸入 CTRL+c、Ctrl+\,或者是終端驅動程序分配給信號控制字符的其他任
何鍵來請求內核產生信號。
內核:當進程執行出錯時,內核會給進程發送一個信號,例如非法段存取(內存訪問違規)、浮
點數溢出等。
進程:一個進程可以通過系統調用 kill 給另一個進程發送信號,一個進程可以通過信號和另外
一個進程進行通信。

進程接收到信號以後,可以有如下 3 種選擇進行處理:
接收默認處理:接收默認處理的進程通常會導致進程本身消亡。例如連接到終端的進程,用戶
按下 CTRL+c,將導致內核向進程發送一個 SIGINT 的信號,進程如果不對該信號做特殊的處
理,系統將採用默認的方式處理該信號,即終止進程的執行; signal(SIGINT,SIG_DFL)。
忽略信號:進程可以通過代碼,顯示地忽略某個信號的處理,例如:signal(SIGINT,SIG_IGN);
但是某些信號是不能被忽略的。
捕捉信號並處理:進程可以事先註冊信號處理函數,當接收到信號時,由信號處理函數自動捕
捉並且處理信號。
有兩個信號既不能被忽略也不能被捕捉,它們是 SIGKILL 和 SIGSTOP。即進程接收到這兩個信號後,只能接受系統的默認處理,即終止進程。

The signals SIGKILL and SIGSTOP cannot be caught or ignored.

發送信號

Linux下,一個進程給其他進程發送信號的API是kill函數。其定義如下:

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

該函數把信號sig發送給目標進程;目標進程由pid參數指定,其可能的取值和含義如下:

pid > 0  信號發送給PID爲pid的進程
pid = 0  信號發送給本進程組內的其他進程
pid = -1  信號發送給除init進程之外的其他所有進程,但發送者需要擁有對目標進程發送信號的權限
pid < -1  信號發送給組ID爲-pid的進程組中的所有成員

該函數成功時返回0,失敗時返回-1並設置errno,幾種可能的errno如下所示:

EINVAL  無效的信號
EPERM   該進程沒有權限發送信號給任何一個目標進程
ESRCH   目標進程或者進程組不存在

信號處理方式

目標進程在收到信號時,需要定義一個接收函數來處理。信號處理函數的原型如下:

#include <signal.h>
typedef void (*_sighandler_t)(int);

信號處理函數只帶一個整型參數,該參數用來指示信號類型,信號處理函數應該是可重入的,否則就很容易引發一些競態條件。所以在信號函數中嚴禁調用一些不安全的函數。
除用戶自定義信號處理函數之外,bits/signum.h頭文件中還定義了信號的兩種其他處理方式——SIG_IGN和SIG_DEL:

#include <bits/signum.h>
#define SIG_DFL ((_sighandler_t) 0)
#define SIG_IGN ((_sighandler_t) 1)

SIG_IGN表示忽略目標信號,SIG_DFL表示使用信號的默認處理方式

中斷系統調用

如果程序在執行處於阻塞狀態的系統調用時接收到信號,並且我們爲該信號設置了信號處理函數,則默認情況下系統調用將被中斷,並且errno被設置爲EINTR。我們可以使用sigaction函數爲信號設置SA_RESTART標誌以自動啓動被該信號中斷的系統調用。

對於默認行爲是暫停進程的信號(比如SIGSTOP、SIGTTIN),如果我們沒有爲它們設置信號處理函數,則它們也可以中斷某些系統調用(比如:connect、epoll_wait)。POSIX沒有規定這種行爲,這是Linux獨有的。

signal 信號處理機制

可以用函數 signal 註冊一個信號捕捉函數。原型爲:

#include <signal.h>
typedef void (*_sighandler_t)(int);  //函數指針
_sighandler_t signal(int signum, _sighandler_t _handler);

signal 的第 1 個參數 signum 表示要捕捉的信號,第 2 個參數是個函數指針,表示要對該信號進行捕捉的函數, 該參數也可以是 SIG_DFL(表示交由系統缺省處理, 相當於白註冊了)或 SIG_IGN(表示忽略掉該信號而不做任何處理)。

signal() sets the disposition of the signal signum to handler, which is either SIG_IGN,SIG_DFL, or the address of a programmer-defined function (a “signal handler”).
If the signal signum is delivered to the process, then one of the following happens:
If the disposition is set to SIG_IGN, then the signal is ignored.
If the disposition is set to SIG_DFL, then the default action associated with the signal(see signal(7)) occurs.
If the disposition is set to a function, then first either the disposition is reset to SIG_DFL, or the signal is blocked , and then handler is called with argument signum. If invocation of the handler caused the signal to be blocked, then the signal is unblocked upon return from the handler.

signal 如果調用成功, 返回以前該信號的處理函數的地址, 否則返回 SIG_ERR

signal() returns the previous value of the signal handler, or SIG_ERR on error. In the event
of an error, errno is set to indicate the cause.

sighandler_t 是信號捕捉函數,由 signal 函數註冊,註冊以後,在整個進程運行過程中均有效,並且對不同的信號可以註冊同一個信號捕捉函數。該函數只有一個整型參數,表示信號值。

signal.c

#include <func.h>
void sigFunc(int signum)
{
    printf("%d is coming\n",signum);
}
int main(int argc, char* argv[])
{
    if(signal(SIGINT,sigFunc)==SIG_ERR)
    {
        perror("signal");
        return -1;
    }
    while(1);
    return 0;
}

^C2 is coming
^C2 is coming
^C2 is coming
^C2 is coming
^\退出 (核心已轉儲)

signal_sleep_recover.c

#include <func.h>
int main(int argc, char* argv[])
{
    if(signal(SIGINT,SIG_IGN)==SIG_ERR)
    {
        perror("signal");
        return -1;
    }
    sleep(10);
    signal(SIGINT,SIG_DFL);   //將該信號改成默認的
    while(1);
    return 0;
}

^C ^C ^C ^C ^C ^C
>sleep(10) 
^C
terminate

signal_read_block.c

#include <func.h>
void sigFunc(int signum)
{
    printf("%d is coming\n",signum);
}
int main(int argc, char* argv[])
{
    signal(SIGINT,sigFunc);
    char buf[128]={0};
    int ret=read(STDIN_FILENO,buf,sizeof(buf));
    printf("ret=%d,buf=%s\n",ret,buf);
    return 0;
}
^C2 is coming
^C2 is coming
^C2 is coming
^C2 is coming
^C2 is coming
^C2 is coming
hello
ret=6,buf=hello

signal_two_kind.c

#include <func.h>
void sigFunc(int signum)
{
    printf("before sleep %d is coming\n",signum);
    sleep(3);
    printf("after sleep %d is coming\n",signum);
}
int main(int argc, char* argv[])
{
    if(signal(SIGINT,sigFunc)==SIG_ERR)
    {
        perror("signal");
        return -1;
    }
    if(signal(SIGQUIT,sigFunc)==SIG_ERR)
    {
        perror("signal");
        return -1;
    }
    while(1);
    return 0;
}

對運行結果的分析:當發送2號信號的時候,這時2號信號在自己的sigFunc函數處理流程中,2號信號無法自身打斷自己。
而當3號信號到來時,3號信號會打斷2號信號的處理流程,去執行3號信號的sigFunc處理流程,這裏相當與函數的遞歸調用。
在3號信號的處理流程中sleep(3)後,會輸出after sleep 3 is coming這句話。接下來因爲在3號信號的處理程序之中繼續執行時,又從終端發送了3號信號,將內核中標誌位從0變爲了1,當處理程序結束時,內核發現3號信號的該標誌位爲1,所有會再次執行一次信號處理函數,打印before sleep 3 is coming和after sleep 3 is coming。之後再返回2號信號的處理流程中打印after sleep 2 is coming。同理,最後2號信號的處理流程也會多執行一次。

^Cbefore sleep 2 is coming
^C^C^C^C^C^C^\before sleep 3 is coming
^\^\^\^\^\after sleep 3 is coming
before sleep 3 is coming
after sleep 3 is coming
after sleep 2 is coming
before sleep 2 is coming
after sleep 2 is coming
^Z
[1]+  已停止               ./a.out

問題:當不同信號到來時,會打斷信號處理流程嘛,假設現在的信號發送是2 3 2,最後一次發送的2號信號會打斷3號信號的處理流程嘛?
不會,因爲第二次發送3號信號打斷了2號信號,但是還在2號信號的處理流程之中的。這時再發送2號信號,因爲本身就處於2號信號的處理流程,所以不能打斷。

sigaction 信號處理機制

函數原型:

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

sigaction 也用於註冊一個信號處理函數
參數 signum 爲需要捕捉的信號
參數 act 是一個結構體,裏面包含信號處理函數地址、處理方式等信息
參數 oldact 是一個傳出參數,sigaction 函數調用成功後,oldact 裏面包含以前對 signum 的處理方式的信息,通常爲 NULL。
如果函數調用成功,將返回 0,否則返回-1
結構體 struct sigaction(注意名稱與函數 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); //保留,不要使用
};
typedef struct {
	unsigned long sig[_NSIG_WORDS]} sigset_t

該結構體的各字段含義及使用方式:

1、字段 sa_handler 是一個函數指針,用於指向原型爲 void handler(int)的信號處理函數地址,
即老類型 的信號處理函數(如果用這個再將 sa_flags = 0,就等同於 signal()函數)

2、字段 sa_sigaction 也是一個函數指針,用於指向原型爲:
void handler(int iSignNum, siginfo_t *pSignInfo, void *pReserved);
的信號處理函數,即新類型的信號處理函數
該函數的三個參數含義爲:
iSignNum:傳入的信號
pSignInfo:與該信號相關的一些信息,它是個結構體
pReserved:保留,通常爲 NULL

siginfo_t {
               int      si_signo;     /* Signal number */
               int      si_errno;     /* An errno value */
               int      si_code;      /* Signal code */
               int      si_trapno;    /* Trap number that caused
                                         hardware-generated signal
                                         (unused on most architectures) */
               pid_t    si_pid;       /* Sending process ID */
               uid_t    si_uid;       /* Real user ID of sending process */
               int      si_status;    /* Exit value or signal */
               clock_t  si_utime;     /* User time consumed */
               clock_t  si_stime;     /* System time consumed */
               sigval_t si_value;     /* Signal value */
               int      si_int;       /* POSIX.1b signal */
               void    *si_ptr;       /* POSIX.1b signal */
               int      si_overrun;   /* Timer overrun count;
                                         POSIX.1b timers */
               int      si_timerid;   /* Timer ID; POSIX.1b timers */
               void    *si_addr;      /* Memory location which caused fault */
               long     si_band;      /* Band event (was int in
                                         glibc 2.3.2 and earlier) */
               int      si_fd;        /* File descriptor */
               short    si_addr_lsb;  /* Least significant bit of address
                                         (since Linux 2.6.32) */
               void    *si_lower;     /* Lower bound when address violation
                                         occurred (since Linux 3.19) */
               void    *si_upper;     /* Upper bound when address violation
                                         occurred (since Linux 3.19) */
               int      si_pkey;      /* Protection key on PTE that caused
                                         fault (since Linux 4.6) */
               void    *si_call_addr; /* Address of system call instruction
                                         (since Linux 3.5) */
               int      si_syscall;   /* Number of attempted system call
                                         (since Linux 3.5) */
               unsigned int si_arch;  /* Architecture of attempted system call
                                         (since Linux 3.5) */
           }

3、字段 sa_handler 和 sa_sigaction 只應該有一個生效,如果想採用老的信號處理機制,就應該讓
sa_handler 指向正確的信號處理函數,並且讓字段 sa_flags 爲 0;否則應該讓 sa_sigaction 指向正確的信號處理函數,並且讓字段 sa_flags 包含 SA_SIGINFO 選項

4、字段 sa_mask 是一個包含信號集合的結構體, 該結構體內的信號表示在進行信號處理時,將要
被阻塞的信號。針對 sigset_t 結構體,有一組專門的函數對它進行處理,它們是:

sa_mask specifies a mask of signals which should be blocked (i.e., added to the signal mask of the thread in which the signal handler is invoked) during execution of the signal han dler. In addition, the signal which triggered the handler will be blocked, unless the SA_NODEFER flag is used.

#include <signal.h>
int sigemptyset(sigset_t *set);     //清空信號集合 set
int sigfillset(sigset_t *set);      //將所有信號填充進 set 中
int sigaddset(sigset_t *set, int signum);  //往set中添加信號 signum
int sigdelset(sigset_t *set, int signum);  //從set中移除信號 signum
int sigismember(const sigset_t *set, int signum);  //判斷 signum 是否包含在 set 中(是:返回 1,否:0)
int sigpending(sigset_t  *set);    //被阻塞的信號集合由參數set指針返回(掛起信號)

其中,對於函數 sigismember 而言,如果 signum 在 set 集中,則返回 1。不在,則返回 0,出錯時返回-1,其他的函數都是成功返回 0,失敗返回-1。

5、 字段 sa_flags 是一組掩碼的合成值,指示信號處理時所應該採取的一些行爲,各掩碼的含義爲:

sa_flags specifies a set of flags which modify the behavior of the signal. It is formed by
the bitwise OR of zero or more of the following:

SA_RESETHAND
處理完畢要捕捉的信號後,將自動撤消信號處理函數的註冊,即必須再重新註冊信號處理函數,才能繼續處理接下來產生的信號。

Restore the signal action to the default upon entry to the signal handler. This flag is meaningful only when establishing a signal handler

SA_NODEFER
在處理信號時,如果又發生了其它的信號,則立即進入其它信號的處理,等其它信號處理完畢後,再繼續處理當前的信號,即遞歸地處理。

Do not prevent the signal from being received from within its own signal handler. This flag is meaningful only when establishing a signal handler.

SA_RESTART
如果在發生信號時,程序正阻塞在某個系統調用,例如調用read()函數,則在處理完畢信號後,接着從阻塞的系統調用返回。如果不指定該參數,中斷處理完畢之後,read 函數讀取失敗。

Provide behavior compatible with BSD signal semantics by making certain system calls restartable across signals. This flag is meaningful only when establishing a signal handler.

SA_SIGINFO
指示結構體的信號處理函數指針是哪個有效,如果 sa_flags 包含該掩碼 , 則 sa_sigaction 指針有效,否則是 sa_handler 指針有效。

The signal handler takes three arguments, not one. In this case, sa_sigaction should be set instead of sa_handler. This flag is meaningful only when establishing a signal handler.

sigaction.c

#include <func.h>
void sigFunc(int signum, siginfo_t *p,void *p1)
{
    printf("%d is coming\n",signum);
}
int main(int argc, char* argv[])
{
    struct sigaction act;        //定義sigaction結構體
    bzero(&act,sizeof(act));     //清空該結構體,置0
    act.sa_flags=SA_SIGINFO;     //信號處理方式掩碼
    act.sa_sigaction=sigFunc;    //新類型的信號處理函數
    int ret=sigaction(SIGINT,&act,NULL);  //SIGINT 2號信號
    ERROR_CHECK(ret,-1,"sigaction");
    while(1);
    return 0;
}

如果信號處理函數正在處理信號,並且還沒有處理完畢時,又發生了一個同類型的信號,這時
對於signal: 會挨着執行,後續相同信號忽略(會多執行一次)。
對於sigaction: 當信號處理函數正在處理信號,並且還沒有處理完畢時,若收到一個同類型的信號,sigaction是會打斷當下的處理程序。

^C2 is coming
^C2 is coming
^C2 is coming
^C2 is coming
^C2 is coming
^C2 is coming
^C2 is coming
^\退出 (核心已轉儲)

sigaction_mask.c

#include <func.h>
void sigFunc(int signum, siginfo_t *p,void *p1)
{
    printf("before sleep %d is coming\n",signum);
    sleep(3);
    printf("after sleep %d is coming\n",signum);
}
int main(int argc, char* argv[])
{
    struct sigaction act;
    bzero(&act,sizeof(act));
    act.sa_flags=SA_SIGINFO;
    act.sa_sigaction=sigFunc;
    sigemptyset(&act.sa_mask);    //清空信號集合sigset_t
    sigaddset(&act.sa_mask,SIGQUIT);   //向信號集合中添加3號信號
    int ret=sigaction(SIGINT,&act,NULL);
    ERROR_CHECK(ret,-1,"sigaction");
    ret=sigaction(SIGQUIT,&act,NULL);
    ERROR_CHECK(ret,-1,"sigaction");
    while(1);
    return 0;
}

如果信號處理函數正在處理信號,並且還沒有處理完畢時,又發生了一個不同類型的信號。sigaction可以實現不跳轉到另一個信號。比如,先發送2號信號,再發送3號信號,這時sigaction會將2號信號指向完畢再去執行3號信號,不會被3號信號打斷。而對於signal,則會直接跳轉去執行另一個信號,之後再返回執行剩下的沒有處理完的信號。

^Cbefore sleep 2 is coming
^C^C^Cafter sleep 2 is coming
before sleep 2 is coming
after sleep 2 is coming
^Cbefore sleep 2 is coming
^\^\^\after sleep 2 is coming
before sleep 3 is coming
after sleep 3 is coming
^Z
[1]+  已停止               ./sigaction_mask

sigaction_sa_nodefer.c

#include <func.h>
void sigFunc(int signum, siginfo_t *p,void *p1)
{
    printf("before sleep %d is coming\n",signum);
    sleep(3);
    printf("after sleep %d is coming\n",signum);
}
int main(int argc, char* argv[])
{
    struct sigaction act;
    bzero(&act,sizeof(act));
    act.sa_flags=SA_SIGINFO|SA_NODEFER;    //新增了參數SA_NODEFER
    act.sa_sigaction=sigFunc;
    int ret=sigaction(SIGINT,&act,NULL);
    ERROR_CHECK(ret,-1,"sigaction");
    ret=sigaction(SIGQUIT,&act,NULL);
    ERROR_CHECK(ret,-1,"sigaction");
    while(1);
    return 0;
}

SA_NODEFER:在處理信號時,如果又發生了其它的信號,則立即進入其它信號的處理,等其它信號處理完畢後,再繼續處理當前的信號,即遞歸地處理。

^Cbefore sleep 2 is coming
^Cbefore sleep 2 is coming
^Cbefore sleep 2 is coming
^\before sleep 3 is coming
^Cbefore sleep 2 is coming
^\before sleep 3 is coming
^\before sleep 3 is coming
after sleep 3 is coming
after sleep 3 is coming
after sleep 2 is coming
after sleep 3 is coming
after sleep 2 is coming
after sleep 2 is coming
after sleep 2 is coming

sigaction_pending.c

sigpending() returns the set of signals that are pending for delivery to the calling thread(i.e., the signals which have been raised while blocked). The mask of pending signals is returned in set.

#include <func.h>
void sigFunc(int signum, siginfo_t *p,void *p1)
{
    printf("before sleep %d is coming\n",signum);
    sleep(3);
    sigset_t pending;
    sigpending(&pending);  //從進程控制塊PCB中拿出進程尚未處理的信號
    if(sigismember(&pending,SIGQUIT))  //判斷3號信號是否被阻塞
    {
        printf("SIGQUIT is pending\n");
    }
    else
    {
        printf("SIGQUIT is not pending\n");
    }
    printf("after sleep %d is coming\n",signum);
}
int main(int argc, char* argv[])
{
    struct sigaction act;
    bzero(&act,sizeof(act));
    act.sa_flags=SA_SIGINFO;
    act.sa_sigaction=sigFunc;
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask,SIGQUIT);
    int ret=sigaction(SIGINT,&act,NULL);
    ERROR_CHECK(ret,-1,"sigaction");
    ret=sigaction(SIGQUIT,&act,NULL);
    ERROR_CHECK(ret,-1,"sigaction");
    while(1);
    return 0;
}
^Cbefore sleep 2 is coming
^C^CSIGQUIT is not pending
after sleep 2 is coming
before sleep 2 is coming
^\^\SIGQUIT is pending
after sleep 2 is coming
before sleep 3 is coming
SIGQUIT is not pending
after sleep 3 is coming

sigaction_sa_resethand.c

#include <func.h>
void sigFunc(int signum, siginfo_t *p,void *p1)
{
    printf("%d is coming\n",signum);
}
int main(int argc, char* argv[])
{
    struct sigaction act;
    bzero(&act,sizeof(act));
    act.sa_flags=SA_SIGINFO|SA_RESETHAND;   //添加了SA_RESETHAND
    act.sa_sigaction=sigFunc;
    int ret=sigaction(SIGINT,&act,NULL);
    ERROR_CHECK(ret,-1,"sigaction");
    while(1);
    return 0;
}

對於sigaction可以實現第一次信號生效,但當下次發送時就失效這種效果。

^C2 is coming
^C
terminate

sigaction_sa_restart.c

#include <func.h>
void sigFunc(int signum, siginfo_t *p,void *p1)
{
    printf("%d is coming\n",signum);
}
int main(int argc, char* argv[])
{
    struct sigaction act;
    bzero(&act,sizeof(act));
    act.sa_flags=SA_SIGINFO|SA_RESTART;
    act.sa_sigaction=sigFunc;
    int ret=sigaction(SIGINT,&act,NULL);
    ERROR_CHECK(ret,-1,"sigaction");
    char buf[128]={0};
    ret=read(STDIN_FILENO,buf,sizeof(buf));     //read()阻塞
    printf("ret=%d,buf=%s\n",ret,buf);
    return 0;
}

SA_RESTART: 如果在發生信號時,程序正阻塞在某個系統調用,例如調用read()函數,則在處理完畢信號後,接着從阻塞的系統返回。如果不指定該參數,中斷處理完畢之後,read 函數讀取失敗。

^C2 is coming
^C2 is coming
^C2 is coming
^C2 is coming
hello
ret=6,buf=hello

若不加SA_RESTART的結果:

^C2 is coming
ret=-1,buf=

sigaction_siginfo.c

#include <func.h>
void sigFunc(int signum, siginfo_t *p,void *p1)
{
    printf("%d is coming,send pid=%d,send uid=%d\n",signum,p->si_pid,p->si_uid);
}
int main(int argc, char* argv[])
{
    struct sigaction act;
    bzero(&act,sizeof(act));
    act.sa_flags=SA_SIGINFO;
    act.sa_sigaction=sigFunc;
    int ret=sigaction(SIGINT,&act,NULL);
    ERROR_CHECK(ret,-1,"sigaction");
    while(1);
    return 0;
}

sigprocmask信號阻塞機制

函數 sigaction 中設置的被阻塞信號集合只是針對於要處理的信號,例如:

struct sigaction act;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask,SIGQUIT);
sigaction(SIGINT,&act,NULL);

表示只有在處理信號 SIGINT 時,才阻塞信號 SIGQUIT;
函數 sigprocmask 是全程阻塞,在 sigprocmask 中設置了阻塞集合後,被阻塞的信號將不能再被信號處理函數捕捉,直到重新設置阻塞信號集合。

sigprocmask() is used to fetch and/or change the signal mask of the calling thread. The signal mask is the set of signals whose delivery is currently blocked for the caller

函數原型:

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

參數 how的值爲如下 3 者之一:
SIG_BLOCK ,將參數 2 的信號集合添加到進程原有的阻塞信號集合中
SIG_UNBLOCK ,從進程原有的阻塞信號集合移除參數 2 中包含的信號
SIG_SETMASK,重新設置進程的阻塞信號集爲參數 2 的信號集
參數 set 爲阻塞信號集
參數 oldset 是傳出參數,存放進程原有的信號集,通常爲 NULL

If oldset is non-NULL, the previous value of the signal mask is stored in oldset.
If set is NULL, then the signal mask is unchanged

signal_ignore_def.c

#include <func.h>
int main(int argc, char* argv[])
{
    signal(SIGINT,SIG_IGN);
    slepp(10);   //關鍵代碼
    signal(SIGINT,SIG_DFL);   //迴歸成默認
    return 0;
}

sigprocmask.c

#include <func.h>
int main(int argc, char* argv[])
{
    sigset_t procMask;      //定義阻塞集合
    sigemptyset(&procMask);    //清空集合
    sigaddset(&procMask,SIGINT);   //向集合中添加SIGINT信號
    int ret;
    ret=sigprocmask(SIG_BLOCK,&procMask,NULL);  //全程阻塞該信號,不能被捕捉
    ERROR_CHECK(ret,-1,"sigprocmask");
    sleep(10);
    ret=sigprocmask(SIG_UNBLOCK,&procMask,NULL);  //重新設置信號阻塞集合,取出SIGINT
    ERROR_CHECK(ret,-1,"sigprocmask");
    return 0;
}

sigprocmask_pending.c

#include <func.h>

int main(int argc, char* argv[])
{
    sigset_t procMask;
    sigemptyset(&procMask);
    sigaddset(&procMask,SIGINT);
    int ret;
    ret=sigprocmask(SIG_BLOCK,&procMask,NULL);
    ERROR_CHECK(ret,-1,"sigprocmask");
    sleep(3);
    sigset_t pending;     //定義信號集合pending
    sigemptyset(&pending);
    sigpending(&pending);   //被阻塞的信號集合由參數pending返回
    if(sigismember(&pending,SIGINT))  //判斷阻塞的信號集合中是否存在SIGINT
    {
        printf("SIGINT is pending\n");
    }
    else
    {
        printf("SIGINT is not pending\n");
    }
    ret=sigprocmask(SIG_UNBLOCK,&procMask,NULL);  //解除設定的阻塞集合proMask
    ERROR_CHECK(ret,-1,"sigprocmask");
    return 0;
}

sigprocmask_pending_ignore.c

若是在全局阻塞該信號之前,將該信號忽略了,這時會顯示pending?
1、考慮阻塞的優先級高,還是忽略的優先級高,若是先對該信號忽略,在內核中將標誌符從1改爲0,則不會顯示pending。
2、若是阻塞的優先級更高,則會顯示出pending,

#include <func.h>

int main(int argc, char* argv[])
{
    signal(SIGINT,SIG_IGN);     //忽略SIGINT
    sigset_t procMask;
    sigemptyset(&procMask);
    sigaddset(&procMask,SIGINT);
    int ret;
    ret=sigprocmask(SIG_BLOCK,&procMask,NULL);  //阻塞SIGINT
    ERROR_CHECK(ret,-1,"sigprocmask");
    sleep(3);
    sigset_t pending;
    sigemptyset(&pending);
    sigpending(&pending);
    if(sigismember(&pending,SIGINT))
    {
        printf("SIGINT is pending\n");
    }
    else
    {
        printf("SIGINT is not pending\n");
    }
    ret=sigprocmask(SIG_UNBLOCK,&procMask,NULL);
    ERROR_CHECK(ret,-1,"sigprocmask");
    return 0;
}

當解除阻塞以後,會立刻進行忽略,所以返回值爲0。若解除阻塞以後,沒有設置忽略,返回值則是130。內核中阻塞的優先級高於忽略的優先級。

^CSIGINT is pending
terminate
echo $? 返回值爲0,不是130
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章