zoukankan      html  css  js  c++  java
  • 信号集 / 信号掩码(阻塞信号传递)

    【摘自《Linux/Unix系统编程手册》】

    信号集

    sigemptyset() 函数初始化一个未包含任何成员的信号集。sigfillset() 函数则初始化一个信号集,使其包含所有信号(包括所有实时信号)。

    #include <signal.h>
    int sigemptyset(sigset_t* set);
    int sigfillset(sigset_t* set);
            Both return 0 on success, or -1 on error

    必须使用 sigemptyset() 或者 sigfillset() 来初始化信号集。这是因为 C 语言不会对自动变量进行初始化,并且,借助于将静态变量初始化为 0 的机制来表示空信号集的作法在可移植性上存在问题,因为有可能使用位掩码之外的结构来实现信号集。(出于同一原因,为将信号集标记为空而使用 memset 函数将其内容清零的做法也不正确)

    信号集初始化后,可以分别使用 sigaddset() 和 sigdelset() 函数向一个集合中添加或者移除单个信号。

    #include <signal.h>
    int sigaddset(sigset_t* set, int sig);
    int sigdelset(sigset_t* set, int sig);
            Both return 0 on success, or -1 on error

    sigismember() 函数用来测试信号 sig 是否是信号集 set 的成员

    sigismember(const sigset_t* set, int sig);
            Return 1 if sig is a member of set, otherwise 0

    GNU C 库还实现了 3 个非标准函数,是对上述信号集标准函数的补充。

    #define _GNU_SOURCE
    #include <signal.h>
    
    int sigandset(sigset_t* dest, sigset_t* left, sigset_t* right);
    int sigorset(sigset_t* dest, sigset_t* left, sigset_t* right);
                Both return 0 on success, or -1 on error
    
    int sigisemptyset(const sigset_t* set);
                Return 1 if sig is empty, otherwise 0

    sigandset() 将 left 集和 right 集的交集置于 dest 集。

    sigorset() 将 left 集和 right 集的并集置于 dest 集。

    若 set 集内未包含信号,则 sigisemptyset() 返回 true。

    信号掩码(阻塞信号传递)

    内核会为每个进程维护一个信号掩码,即一组信号,并将阻塞其针对该进程的传递。如果将遭阻塞的信号发给某进程,那么对该信号的传递将延后,直至从进程信号掩码中移除该信号,从而解除阻塞为止。(信号掩码实际属于线程属性,在多线程进程中,每个线程都可使用 pthread_sigmask() 函数来独立检查和修改其信号掩码。)

    向信号掩码中添加一个信号,有如下几种方式:

    • 当调用信号处理器程序时,可将引发调用的信号自动添加到信号掩码中。是否发生这一情况,要视 sigaction() 函数在安装信号处理器程序时所使用的标志而定
    • 使用 sigaction() 函数建立信号处理器程序时,可以指定一组额外信号,当调用该处理器程序时会将其阻塞
    • 使用 sigprocmask() 系统调用,随时可以显式地向信号掩码中添加或移除信号
    #include <signal.h>
    int sigprocmask(int how, const sigset_t* set, sigset_t* oldset);
            Returns 0 on success, or -1 on error

    使用 sigprocmask() 函数即可修改进程的信号掩码,又可获取现有掩码,或者两重功效兼具。how 参数指定了 sigprocmask() 函数想给信号掩码带来的变化:

    • SIG_BLOCK:将 set 指向信号集内的指定信号添加到信号掩码中。换言之,将信号掩码设置为其当前值和 set 的并集
    • SIG_UNBLOCK:将 set 指向信号集中的信号从信号掩码中移除。即使要解除阻塞的信号当前并未处于阻塞状态,也不会返回错误。
    • SIG_SETMASK:将 set 指向的信号集赋给信号掩码

    如果想获取信号掩码而又对其不做改动,那么可将 set 参数指定为空,这时将忽略 how 参数。

    要想暂时阻止信号的传递,可以使用下面的调用来阻塞信号,然后再将信号掩码重置为先前的状态以解除对信号的锁定:

     1 sigset_t blockSet, prevMask;
     2 
     3 /* Initialize a signal set to contain SIGINT */
     4 sigemptyset(&blockSet);
     5 sigaddset(&blockSet, SIGINT);
     6 
     7 /* Block SIGINT, save previous signal mask */
     8 if (sigprocmask(SIG_BLOCK, &blockSet, &prevMask) == -1)
     9     errExit("sigprocmask1");
    10 
    11 /* ... Code that should not be interrupted by SIGINT */
    12 
    13 /* Restore previous signal mask, unblocking SIGINT */
    14 if (sigprocmask(SIG_SETMASK, &prevMask, NULL) == -1)
    15     errExit("sigprocmask2");

    SUSv3 规定,如果有任何等待信号因对 sigprocmask() 的调用而解除了锁定,那么在此调用返回前至少会传递一个信号。换言之,如果解除了对某个等待信号的锁定,那么会立即将该信号传递给进程。

    系统将忽略试图阻塞 SIGKILL 和 SIGSTOP 信号的请求。如果试图阻塞这些信号,sigprocmask() 函数既不会予以关注,也不会产生错误。这意味着,可以使用如下代码来阻塞除 SIGKILL 和 SIGSTOP 之外的所有信号:

    1 sigfillset(&blockSet);
    2 if (sigprocmask(SIG_BLOCK, &blockSet, NULL) == -1)
    3     errExit("sigprocmask");


    处于等待状态的信号

    如果某进程接受了一个该进程正在阻塞的信号,那么会将该信号添加到进程的等待信号集中。当之后解除了对该信号的锁定时,会随之将信号传递给此进程。为了确定进程中处于等待状态的是哪些信号,可以使用 sigpending()

    #include <signal.h>
    int sigpending(sigset_t* set);
            Returns 0 on success, or -1 on error

    sigpending() 系统调用为调用进程返回处于等待状态的信号集,并将其置于 set 指向的 sigset_t 结构中。随后可以使用 sigismember() 函数检查 set。

    如果更改了对等待信号的处理器程序,那么当后来解除对信号的锁定时,将根据新的处理器程序来处理信号。这项技术虽然不经常使用,但是还存在一个应用场景,即将对信号的处置置为 SIG_IGN,或者 SIG_DEL(如果信号的默认行为是忽略),从而阻止传递处于等待状态的信号。因此,会将信号从进程的等待信号集中移除,从而不传递该信号。

    等待信号:pause()

    调用 pause() 将暂停进程的执行,直至信号处理器函数中断该调用为止(或者直至一个未处理信号终止进程为止)。

    #include <unistd.h>
    int pause(void);
            Always returns -1 with errno set to EINTR

    处理信号时,pause() 遭到中断,并总是返回 -1,并将 errno 置为 EINTR。

    不对信号进行排队处理

    等待信号集只是一个掩码,仅表明一个信号是否发生,而未表明发生的次数。换言之,如果同一信号在阻塞状态下产生多次,那么会将该信号记录在等待信号集中,并在稍后仅传递一次。(标准信号和实时信号之间的差异之一在于,对实时信号进行了排队处理)

    signal_functions.h

    #ifndef SIGNAL_FUNCTIONS_H
    #define SIGNAL_FUNCTIONS_H
    
    #include <signal.h>
    #include "tlpi_hdr.h"
    
    int printSigMask(FILE *of, const char *msg);
    
    int printPendingSigs(FILE *of, const char *msg);
    
    void printSigset(FILE* of, const char* prefix, const sigset_t* sigset);
    #endif

    signal_functions.c

     1 #define _GNU_SOURCE
     2 #include <string.h>
     3 #include <signal.h>
     4 #include <signal_functions.h> /* Declares functions defined here */
     5 #include "tlpi_hdr.h"
     6 
     7 /* NOTE: All of the following functions employ fprintf(), which is not async-signal-safe.
     8          As sunch, these functions are also not async-signal-safe (i.e., beware of 
     9          indiscriminately calling them from signal handlers).*/
    10 
    11 void printSigset(FILE* of, const char* prefix, const sigset_t* sigset)
    12 {
    13     int sig, cnt;
    14     cnt = 0;
    15     for (sig = 1; sig < NSIG; sig++) {
    16         if (sigismember(sigset, sig)) {
    17             cnt++;
    18             fprintf(of, "%s%d (%s)
    ", prefix, sig, strsignal(sig));
    19         }
    20     }
    21 
    22     if (cnt == 0)
    23         fprintf(of, "%s<empty signal set>
    ", prefix);
    24 }
    25 
    26 int printSigMask(FILE* of, const char* msg)
    27 {
    28     sigset_t currMask;
    29     if (msg != NULL)
    30         fprintf(of, "%s", msg);
    31 
    32     if (sigprocmask(SIG_BLOCK, NULL, &currMask) == -1)
    33         return -1;
    34 
    35     printSigset(of, "		", &currMask);
    36     return 0;
    37 }
    38 
    39 int printPendingSigs(FILE* of, const char* msg)
    40 {
    41     sigset_t pendingSigs;
    42     if (msg != NULL)
    43         fprintf(of, "%s", msg);
    44 
    45     if (sigpending(&pendingSigs) == -1)
    46         return -1;
    47 
    48     printSigset(of, "		", &pendingSigs);
    49     return 0;
    50 }

    sig_sender.c

     1 #include <signal.h>
     2 #include "tlpi_hdr.h"
     3 
     4 int main(int argc, char* argv[])
     5 {
     6     int numSigs, sig, j;
     7     pid_t pid;
     8 
     9     if (argc < 4 || strcmp(argv[1], "--help") == 0)
    10         usageErr("%s pid num-sigs sig-num [sig-num-2]
    ", argv[0]);
    11 
    12     pid = getLong(argv[1], 0, "PID");
    13     numSigs = getInt(argv[2], GN_GT_O, "num-sigs");
    14     sig = getInt(argv[3], 0, "sig-num");
    15 
    16     /* Send signals to receiver */
    17     printf("%s: sending signal %d to process %ld %d times
    ", argv[0], sig, (long) pid, numSigs);
    18 
    19     for (j = 0; j < numSigs; j++)
    20         if (kill(pid, sig) == -1)
    21             errExit("kill");
    22 
    23     /* If a fourth command-line argument was specified, send that signal */
    24     if (argc > 4)
    25         if (kill(pid, getInt(argv[4], 0, "sig-num-2")) == -1)
    26             errExit("kill");
    27 
    28     printf("%s: exiting
    ", argv[0]);
    29     exit(EXIT_SUCCESS);
    30 }

    sig_receiver.c

     1 #define _GNU_SOURCE
     2 #include <signal.h>
     3 #include "signal_functions.h"
     4 #include "tlpi_hdr.h"
     5 
     6 static int sigCnt[NSIG]; /* Counts deliveries of each signal */
     7 static volatile sig_atomic_t gotSigint = 0; /* Set nonzero if SIGINT is delivered */
     8 
     9 static void handler(int sig)
    10 {
    11     if (sig == SIGINT)
    12         gotSigint = 1;
    13     else
    14         sigCnt[sig]++;
    15 }
    16 
    17 int main(int argc, char* argv[])
    18 {
    19     int n, numSecs;
    20     sigset_t pendingMask, blockingMask, emptyMask;
    21 
    22     printf("%s: PID is %ld
    ", argv[0], (long)getpid());
    23 
    24     for (n = 1; n < NSIG; n++) /* Same handler for all signals */
    25         (void)signal(n, handler); /* Ignore errors*/
    26 
    27     /* If a sleep time was specified, temporarily block all signals,
    28        sleep (while another process sends us signals), and then
    29        display the mask of pending signals and unblock all signals */
    30     if (argc > 1) {
    31         numSecs = getInt(argv[1], GN_GT_O, NULL);
    32 
    33         sigfillset(&blockingMask);
    34         if (sigprocmask(SIG_SETMASK, &blockingMask, NULL) == -1)
    35             errExit("sigprocmask");
    36 
    37         printf("%s: sleeping for %d seconds
    ", argv[0], numSecs);
    38         sleep(numSecs);
    39 
    40         if (sigpending(&pendingMask) == -1)
    41             errExit("sigpending");
    42 
    43         printf("%s: pending signals are: 
    ", argv[0]);
    44         printSigset(stdout, "		", &pendingMask);
    45 
    46         sigemptyset(&emptyMask); /* Unblock all signals */
    47         if (sigprocmask(SIG_SETMASK, &emptyMask, NULL) == -1)
    48             errExit("sigprocmask");
    49     }
    50 
    51     while (!gotSigint) /* Loop until SIGINT caught */
    52         continue;
    53 
    54     for (n = 1; n < NSIG; n++) /* Display number of signals received */
    55         if (sigCnt[n] != 0)
    56             printf("%s: signal %d caught %d time%s
    ", argv[0], n, sigCnt[n], (sigCnt[n] == 1) ? "" : "s");
    57 
    58     exit(EXIT_SUCCESS);
    59 }

    例如:
    在命令行执行

    $ ./sig_receiver 60 &
    [1] 5047
    $ ./sig_receiver: PID is 5047
    ./sig_receiver: sleeping for 60 seconds
    
    $ ./sig_sender 5047 1000000 10 2
    ./sig_sender: sending signal 10 to process 5047 1000000 times
    ./sig_sender: exiting
    ./sig_receiver: pending signals are: 
            2 (Interrupt)
            10 (User defined signal 1)
    ./sig_receiver: signal 10 caught 1 time
    ^C
    [1]+  Done                    ./sig_receiver 60

    可以看到即使一个信号发送了一百万次,但仅会传递一次给接收者

  • 相关阅读:
    SpringBoot EnumValidator验证器实现
    【原创】SpringCloud:基于Spring Cloud netflix全家桶搭建一个完整的微服务架构系统
    Hystrix Dashboard监控报“Unable to connect to Command Metric Stream”?
    Mysql sql_mode的合理设置
    nginx 调优
    函数指针
    进程与线程
    大小端学习
    联合体和结构体
    内存分配
  • 原文地址:https://www.cnblogs.com/jingyg/p/5182001.html
Copyright © 2011-2022 走看看