【问题标题】:Difference between a process signal mask, blocked signal set, and a blocked signal?进程信号掩码、阻塞信号集和阻塞信号之间的区别?
【发布时间】:2018-01-23 19:07:31
【问题描述】:

了解信号时,我想知道进程信号掩码、阻塞信号集、信号处理程序和阻塞信号之间的细微差别。

问题涉及(在 Debian 上):

  • sigprocmask(2)
  • sigstops(3) 相关函数

每个进程都有自己的信号掩码(一个长整数,其中包含被阻塞的信号)。并且可以通过为 *set 变量调用带有 NULL 参数的 sigprocmask(2) 来获得信号集,这将导致旧的进程掩码被放入 *oldset,不变:

#include <string.h>
#include <signal.h>

void show_signals(const sigset_t exmask)
{

    int exsignals[43];

    exsignals[0] = SIGABRT;
    exsignals[1] = SIGALRM;
    exsignals[2] = SIGBUS;
    exsignals[3] = SIGCHLD;
    exsignals[4] = SIGCONT;
#ifdef SIGEMT
    exsignals[5] = SIGEMT;
#else
    exsignals[5] = -1;
#endif

    exsignals[6] = SIGFPE;

#ifdef SIGFREEZE
    exsignals[7] = SIGFREEZE;
#else
    exsignals[7] = -1;
#endif

    exsignals[8] = SIGHUP;
    exsignals[9] = SIGILL;
#ifdef SIGINFO
    exsignals[10] = SIGINFO;
#else
    exsignals[10] = -1;
#endif

    exsignals[11] = SIGINT;
    exsignals[12] = SIGIO;
    exsignals[13] = SIGIOT;

#ifdef SIGJVM1
    exsignals[14] = SIGJVM1;
#else
    exsignals[14] = -1;
#endif
#ifdef SIGJVM2
    exsignals[15] = SIGJVM2;
#else
    exsignals[15] = -1;
#endif

    exsignals[16] = SIGKILL;
#ifdef SIGLOST
    exsignals[17] = SIGLOST;
#else
    exsignals[17] = -1;
#endif

#ifdef SIGLWP
    exsignals[18] = SIGLWP;
#else
    exsignals[18] = -1;
#endif

    exsignals[19] = SIGPIPE;
    exsignals[20] = SIGPOLL;
    exsignals[21] = SIGPROF;
    exsignals[22] = SIGPWR;
    exsignals[23] = SIGQUIT;
    exsignals[24] = SIGSEGV;
    exsignals[25] = SIGSTKFLT;
    exsignals[26] = SIGSTOP;
    exsignals[27] = SIGSYS;
    exsignals[28] = SIGTERM;
#ifdef SIGTHAW
    exsignals[29] = SIGTHAW;
#else
    exsignals[29] = -1;
#endif
#ifdef SIGTHR
    exsignals[30] = SIGTHR;
#else
    exsignals[30] = -1;
#endif
    exsignals[31] = SIGTRAP;
    exsignals[32] = SIGTSTP;
    exsignals[33] = SIGTTIN;
    exsignals[34] = SIGTTOU;
    exsignals[35] = SIGURG;
    exsignals[36] = SIGUSR1;
    exsignals[37] = SIGUSR2;
    exsignals[38] = SIGVTALRM;
#ifdef SIGWAITING
    exsignals[39] = SIGWAITING;
#else
    exsignals[39] = -1;
#endif

    exsignals[40] = SIGWINCH;
    exsignals[41] = SIGXCPU;
    exsignals[42] = SIGXFSZ;
#ifdef SIGXRES
    exsignals[43] = SIGXRES;
#else
    exsignals[43] = -1;
#endif

    int exsignals_n = 0;

    for (;exsignals_n < 43; exsignals_n++) {
        if (exsignals[exsignals_n] == -1) continue;
        static char *exsignal_name;
        exsignal_name = strsignal(exsignals[exsignals_n]);
        switch(sigismember(&exmask, exsignals[exsignals_n]))
        {
        case 0: break;
        case 1: printf("YES %s\n", exsignal_name); break;
        case -1: printf("could not obtain signal\n"); break;
        default: printf("UNEXPECTED for %s return\n", exsignal_name); break;
        }
    }
}
const sigset_t getmask(void)
{
        static sigset_t retmask;
        if ((sigprocmask(SIG_SETMASK, NULL, &retmask)) == -1)
                printf("could not obtain process signal mask\n");

        return retmask;
}

在我的程序开始时,我意识到进程信号掩码,并没有阻止任何信号。然后我将一个信号处理程序放入程序中。

static void sig_abrt(int signo)
{
    printf("Caught SIGABRT\n");
}

int main(void)
{
    show_signals(getmask());

    signal(SIGABRT, sig_abrt);

    show_signals(getmask());

    return 0;
}

所以现在有一个 SIGABRT 的信号处理程序,但是如果我再次调用 sigprocmask(2),如上所述,SIGABRT 将不在进程信号掩码中。我尝试使用 sigismember(3) 进行检查,但只有在调用 sigaddset(3) 或其他修改信号掩码的函数后,才会修改进程信号掩码。

如果我用 sigaddset(3) 阻止 SIGABRT,信号处理程序 sig_abrt 会不会在 SIGABRT 交付时接收到调用?这是否意味着信号掩码会影响传递的信号?有什么区别?

另外,有没有办法在不使用 sigsetops(3) 和 sigprocmask(2) 函数的情况下阻止进程中的信号?

【问题讨论】:

  • 我记得找到了一篇很好的维基百科文章,其中包含有关信号类型的信息。

标签: c linux unix signals


【解决方案1】:

每个进程都有它自己的 [原文如此] 信号掩码(一个长整数,其中包含被阻塞的信号)

嗯,不。信号掩码实际上是特定于线程的。 (在多线程程序中,必须使用pthread_sigmask()来操作当前线程的信号掩码;在单线程程序中,可以使用sigprocmask()。)

而且,它不是“很长”。它的类型为sigset_t,可能是数组、结构或联合类型。在任何情况下,都应该将其简单地视为一个无序位集,每个信号一个位。

所以现在有一个SIGABRT的信号处理程序,但SIGABRT不会在进程信号掩码中。

正确。无论您是否分配了信号处理程序,都不会影响信号掩码。

如果我用 sigaddset(3) 阻止 SIGABRT,信号处理程序 sig_abrt 会不会在 SIGABRT 被传递时接收不到调用?这是否意味着信号掩码会影响传递的信号?有什么区别?

如果您的所有线程都阻塞了 SIGABRT,则在任何一个信号被解除阻塞(从信号掩码中删除)之前,它都不会被传递。如果使用sigwait()sigwaitinfo()sigtimedwait() 消费信号,则根本不会调用信号处理程序。

简短的总结:

  • 可以将信号定向到进程组(kill()pid == 0pid == -pgid)、特定进程(pid)或特定进程中的特定线程(pthread_kill() 内相同的进程,一般是tgkill Linux 系统调用)。

  • 如果信号被定向到一个进程组,该组中的每个进程都会收到该信号的“副本”。

  • 信号掩码定义信号是被阻止还是立即传递。

  • 在每个进程中,每个信号

    • 可以有一个信号处理程序,或者

    • 被忽略(SIG_IGN“handler”),或者

    • 具有默认的处置(忽略(Ign),使用(Core)或不使用(术语)核心转储;或者它可以停止(Stop)或继续(Cont)目标线程或进程的执行)。详情请见man 7 signal

  • 如果某些线程(但不是所有线程)阻塞了一个信号,并且该信号不是针对特定线程的,内核会将信号定向到没有阻塞信号的线程之一(随机)。

  • 捕捉信号有两种方式:

    1. 使用信号处理程序。仅当信号阻塞时,信号才会传递给信号处理程序。如果信号被阻塞,则信号的传递待处理,直到未被阻塞(或被下面的其他选项捕获)。

    2. sigwait()sigwaitinfo()sigtimedwait()。这些函数检查是否有任何信号未决,如果是,则“捕获”它。他们检查的信号集由sigset_t 类型的函数参数定义。

当内核向一个进程发送/转发一个信号时,它首先检查该进程是否有一个线程没有阻塞该信号。如果有这样的线程,它会通过该线程传递它。 (如果信号有信号处理程序,则在该线程中调用该信号处理程序;否则,效果由信号 disposition 决定。)

如果信号被阻塞,内核会将其等待留给进程。

如果进程调用sigwait()sigwaitinfo()sigtimedwait() 并带有指定信号集中的未决信号,它会接收有关该信号的信息,并捕获该信号。 (它将不再处于挂起状态,也不会导致调用信号处理程序;它已被“消耗”。)

如果进程改变了它的信号掩码,使得挂起的信号变得畅通无阻,它就会被内核传递(就像它是在那个时间点发送的一样)。

另外,有没有办法在不使用 sigsetops(3) 和 sigprocmask(2) 函数的情况下阻止进程中的信号?

没有。 (您可以实现自己的sigsetops()sigprocmask() 的系统调用包装器,仅此而已。)


这是一个示例程序,example.c,您可以在单线程进程中使用它来探索信号处理程序、捕获信号和信号掩码:

#define  _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <errno.h>

/* Async-signal safe write-to-standard error function.
   Keeps errno unchanged. Do not use stderr otherwise!
*/
static int wrerrpp(const char *ptr, const char *end)
{
    const int  saved_errno = errno;
    ssize_t    chars;

    while (ptr < end) {
        chars = write(STDERR_FILENO, ptr, (size_t)(end - ptr));
        if (chars > 0)
            ptr += chars;
        else
        if (chars != -1) {
            errno = saved_errno;
            return EIO;
        } else
        if (errno != EINTR) {
            const int  retval = errno;
            errno = saved_errno;
            return retval;
        }
    }

    errno = saved_errno;
    return 0;
}

/* Write the supplied string to standard error.
   Async-signal safe. Keeps errno unchanged.
   Do not mix with stderr!
*/
static int wrerr(const char *ptr)
{
    if (!ptr)
        return 0;
    else {
        const char *end = ptr;
        /* strlen() is not async-signal safe, so
           find the end of the string the hard way. */
        while (*end)
            end++;
        return wrerrpp(ptr, end);
    }
}

/* Write the supplied long to standard error.
   Async-signal safe. Keeps errno unchanged.
   Do not mix with stderr!
*/
static int wrerrnum(const long  value)
{
    unsigned long  u = (value < 0) ? (unsigned long)-value : (unsigned long)value;
    char           buf[40];
    char          *ptr = buf + sizeof buf;
    char *const    end = buf + sizeof buf;

    do {
        *(--ptr) = '0' + (u % 10uL);
        u /= 10uL;
    } while (u > 0uL);

    if (value < 0)
        *(--ptr) = '-';

    return wrerrpp(ptr, end);
}

/* Async-signal safe variant of strsignal().
   Only covers a small subset of all signals.
   Returns NULL if the signal name is not known. */
static const char *signal_name(const int signum)
{
    switch (signum) {
    case SIGHUP:    return "HUP";
    case SIGINT:    return "INT";
    case SIGQUIT:   return "QUIT";
    case SIGKILL:   return "KILL";
    case SIGSEGV:   return "SEGV";
    case SIGTERM:   return "TERM";
    case SIGUSR1:   return "USR1";
    case SIGUSR2:   return "USR2";
    case SIGCHLD:   return "CHLD";
    case SIGCONT:   return "CONT";
    case SIGSTOP:   return "STOP";
    default:        return NULL;
    }
}

/* Signal handler that reports its delivery immediately,
   but does nothing else.
*/
static void report_signal(int signum, siginfo_t *info, void *ctx)
{
    const char *sname = signal_name(signum);

    wrerr("report_signal(): Received signal ");
    if (sname)
        wrerr(sname);
    else
        wrerrnum(signum);

    if (info->si_pid) {
        wrerr(" from process ");
        wrerrnum(info->si_pid);
        wrerr(".\n");
    } else
        wrerr(" from kernel or terminal.\n");

}

/* Install report_signal() handler.
*/
static int install_report_signal(const int signum)
{
    struct sigaction  act;

    memset(&act, 0, sizeof act);

    sigemptyset(&act.sa_mask);

    act.sa_sigaction = report_signal;
    act.sa_flags = SA_SIGINFO;

    if (sigaction(signum, &act, NULL) == -1)
        return errno;

    return 0;
}


int main(void)
{
    sigset_t    mask;
    siginfo_t   info;
    const char *name;
    int         signum;

    if (install_report_signal(SIGINT) ||
        install_report_signal(SIGCONT)) {
        const char *errmsg = strerror(errno);
        wrerr("Cannot install signal handlers: ");
        wrerr(errmsg);
        wrerr(".\n");
        return EXIT_FAILURE;
    }

    sigemptyset(&mask);
    sigaddset(&mask, SIGUSR1);
    sigaddset(&mask, SIGUSR2);
    sigaddset(&mask, SIGHUP);
    sigaddset(&mask, SIGTERM);
    sigprocmask(SIG_SETMASK, &mask, NULL);

    printf("Process %ld is ready to receive signals! Run\n", (long)getpid());
    printf("\tkill -USR1 %ld\n", (long)getpid());
    printf("\tkill -USR2 %ld\n", (long)getpid());
    printf("\tkill -HUP  %ld\n", (long)getpid());
    printf("\tkill -TERM %ld\n", (long)getpid());
    printf("in another terminal; press Ctrl+C in this terminal; or press Ctrl+Z and run\n");
    printf("\tfg\n");
    printf("in this terminal.\n");
    fflush(stdout);

    /* Almost same as blocked mask, just without SIGUSR1 and SIGUSR2. */
    sigemptyset(&mask);
    sigaddset(&mask, SIGHUP);
    sigaddset(&mask, SIGTERM);

    do {
        do {
            signum = sigwaitinfo(&mask, &info);
        } while (signum == -1 && errno == EINTR);
        if (signum == -1) {
            const char *errmsg = strerror(errno);
            wrerr("sigwaitinfo(): ");
            wrerr(errmsg);
            wrerr(".\n");
            return EXIT_FAILURE;
        }

        name = signal_name(signum);
        if (name)
            printf("main(): Received signal %s from ", name);
        else
            printf("main(): Received signal %d from ", signum);

        if (info.si_pid == 0)
            printf("kernel or terminal.\n");
        else
            printf("process %ld.\n", (long)info.si_pid);
        fflush(stdout);

    } while (signum != SIGTERM);

    return EXIT_SUCCESS;
}

使用示例编译它

gcc -Wall -O2 example.c -o example

我建议你准备两个终端。在一个终端中,运行编译的程序,使用

./example

并观察其输出。会是这样的

进程 843 已准备好接收信号!跑
   杀死 -USR1 843
   杀死 -USR2 843
   杀死 -HUP 843
   杀死 -TERM 843
在另一个终端;在此终端中按 Ctrl+C;或按 Ctrl+Z 并运行
   fg
在这个终端中。

无法捕捉到 KILL 和 STOP 信号。 KILL 将始终终止进程,STOP 将始终停止(“暂停”)进程。

如果您在该终端按 Ctrl+C,内核将向进程发送一个 INT 信号。 (这将通过report_signal() 信号处理程序传递。)

如果您在该终端中按 Ctrl+Z,内核将向进程发送一个 STOP 信号。 shell 检测到这一点,将./example 推送到作业控制下,并让您输入新的 shell 命令。 fg 命令将./example 带回前台,shell 向其发送 CONT 信号,以便 ./example 将继续执行。

USR1 和 USR2 信号被阻塞,因此它们永远不会传递给report_signal() 信号处理程序。

HUP 和 TERM 信号也被阻塞了,但是它们被主线程通过sigwaitinfo() 接收。

程序在收到 TERM 信号时退出。

【讨论】:

  • 我相信 sigset_t 是一个 'unsigned long' /usr/include/x86_64-linux-gnu/asm/signal.h:typedef unsigned long sigset_t 而且,非常感谢你的解释。非常详细和彻底。
  • @Zub:感谢您指出格式错误。我应用了我制作的那些,但由 OP 保留了一个(标记为 [原文如此])。我跳过的不是错误:它确实应该是 "a long" 而不是 "along",因为 long 是 OP 隐含的类型的名称.
  • @seanlum:在 x86_64 上。 Linux 在大量架构上运行。假设sigset_tunsigned long 类型将使您的代码严格限于x86-64 和x86-64 类架构。也就是说,类型是sigset_t,你必须把它当作不透明的位集类型,而不是整数类型。我的观点是,声称它是一个 long 是不正确的,即使 它在某些架构上恰好是 long
  • @seanlum:您的示例程序还有其他问题——尤其是在信号处理程序中使用printf(),这是一个禁忌:在某些情况下它可能根本不起作用,而它可能只是起作用在大多数情况下都很好,因为printf() 不是man 7 signal 中列出的异步信号安全功能——所以我添加了一个使用安全可靠方法的示例程序,供您进行试验。调查愉快!
  • 感谢您的澄清,这改变了我现在查看变量的方式。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-29
  • 1970-01-01
  • 1970-01-01
  • 2011-08-24
相关资源
最近更新 更多