简体   繁体   English

过程信号掩码,阻塞信号集和阻塞信号之间的差异?

[英]Difference between a process signal mask, blocked signal set, and a blocked signal?

Learning about signals, and I was wondering about the subtle differences between the process signal mask, a blocked signal set, a signal handler, and a blocked signal. 了解信号,我想知道过程信号掩码,阻塞信号集,信号处理程序和阻塞信号之间的细微差别。

The questions involve (on Debian): 问题涉及(在Debian上):

  • sigprocmask(2) sigprocmask(2)
  • sigsetops(3) related functions sigsetops(3)相关功能

Each process has it's own signal mask (a long which contains the signals being blocked). 每个进程都有自己的信号掩码(包含被阻塞信号的long)。 And a signal set can be obtained by calling sigprocmask(2) with a NULL argument for the *set variable, will result in the old process mask to be put into *oldset, unchanged: 并且可以通过使用* 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;
}

At the beginning of my program, I realize that the process signal mask, has not blocked any signals. 在我的程序开始时,我意识到过程信号掩码,没有阻止任何信号。 I then place a signal handler into the program. 然后我将信号处理程序放入程序中。

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;
}

So now there is a signal handler for SIGABRT, but if I were to call sigprocmask(2) again, as above, SIGABRT will not be in the process signal mask. 所以现在有一个SIGABRT的信号处理程序,但如果我再次调用sigprocmask(2),如上所述,SIGABRT将不在进程信号掩码中。 I tried checking with sigismember(3), but the process signal mask will only be modified once I have called sigaddset(3) or another function which modifies the signal mask. 我尝试使用sigismember(3)进行检查,但只有在调用sigaddset(3)或修改信号掩码的其他函数后才会修改过程信号掩码。

If I block SIGABRT with sigaddset(3), will the signal handler sig_abrt not receive the call when the SIGABRT is delivered? 如果我使用sigaddset(3)阻止SIGABRT,信号处理程序sig_abrt在SIGABRT交付时是否会收到调用? Does it mean that the signal mask affects which signals are delivered? 这是否意味着信号掩码影响传递的信号? What is the difference? 有什么不同?

Also, is there a way to block a signal in a process without using the sigsetops(3) and sigprocmask(2) functions? 另外,有没有办法在不使用sigsetops(3)和sigprocmask(2)函数的情况下阻止进程中的信号?

Each process has it's [sic] own signal mask (a long which contains the signals being blocked) 每个进程都有自己的[sic]信号掩码(包含阻塞信号的long)

Well, no. 好吧,不。 The signal mask is actually thread-specific. 信号掩码实际上是特定于线程的。 (In a multithreaded program, you must use pthread_sigmask() to manipulate the signal mask for the current thread; in a single-threaded program, you can use sigprocmask() .) (在多线程程序中,必须使用pthread_sigmask()来操作当前线程的信号掩码;在单线程程序中,可以使用sigprocmask() 。)

Also, it's not "a long". 而且,它不是“漫长的”。 It is of type sigset_t , which might be an array, structure, or union type. 它的类型为sigset_t ,可能是数组,结构或联合类型。 In any case, one should consider it simply as an unordered bit set, one bit per signal. 在任何情况下,都应该将其简单地视为无序位集,每个信号一位。

So now there is a signal handler for SIGABRT, but SIGABRT will not be in the process signal mask. 所以现在有一个SIGABRT的信号处理程序,但SIGABRT不会在进程信号掩码中。

Correct. 正确。 Whether or not you have assigned a signal handler or not, does not affect the signal mask at all. 无论您是否分配了信号处理程序,都不会影响信号掩码。

If I block SIGABRT with sigaddset(3), will the signal handler sig_abrt not receive the call when the SIGABRT is delivered? 如果我使用sigaddset(3)阻止SIGABRT,信号处理程序sig_abrt在SIGABRT交付时是否会收到调用? Does it mean that the signal mask affects which signals are delivered? 这是否意味着信号掩码影响传递的信号? What is the difference? 有什么不同?

If all your threads block SIGABRT, it will not be delivered until either the signal is unblocked (removed from the signal mask). 如果所有线程都阻塞SIGABRT,则在信号被解除阻塞(从信号掩码中删除)之前不会传递。 If the signal is consumed using sigwait() , sigwaitinfo() , or sigtimedwait() , the signal handler will not be invoked at all. 如果使用sigwait()sigwaitinfo()sigtimedwait()消耗信号,则根本不会调用信号处理程序。

A short summary: 简短摘要:

  • Signals can be directed to a process group ( kill() with pid == 0 or pid == -pgid ), a specific process ( pid ), or a specific thread in a specific process ( pthread_kill() within the same process, tgkill system call in Linux in general). 可以将信号定向到进程组(使用pid == 0pid == -pgid kill() ),特定进程( pid )或特定进程中的特定线程pthread_kill()同一进程中的pthread_kill()tgkill Linux中的系统调用一般)。

  • If a signal is directed to a process group, each process in that group receives "a copy" of the signal. 如果信号被引导到进程组,则该组中的每个进程都接收信号的“副本”。

  • The signal mask defines whether signals are blocked, or delivered immediately. 信号掩码定义信号是被阻止还是立即传送。

  • In each process, each signal 在每个过程中,每个信号

    • can have a signal handler, or 可以有一个信号处理程序,或

    • be ignored ( SIG_IGN "handler"), or 被忽略( SIG_IGN “处理程序”),或

    • have the default disposition (ignored ( Ign ), terminates the process with ( Core ) or without ( Term ) a core dump; or it can stop ( Stop ) or continue ( Cont ) the execution of the target thread or process). 具有默认配置 (忽略(IGN),终止进程与( 核心 )或无( 期限 )一个核心转储;或者它可以停止( 停止 )或继续( )目标线程或进程的执行)。 See man 7 signal for details. 有关详细信息,请参阅man 7 signal

  • If some, but not all threads, block a signal, and the signal is not targeted to a specific thread, the kernel directs the signal to one of the threads that are not blocking the signal (at random). 如果某些线程(但不是所有线程)阻塞信号,并且信号不是针对特定线程,则内核会将信号指向其中一个未阻塞信号的线程(随机)。

  • There are two ways of catching a signal: 捕获信号有两种方法:

    1. Using a signal handler. 使用信号处理程序。 The signal gets delivered to a signal handler only when the signal is not blocked. 仅当信号未被阻止时,信号才被传送到信号处理器。 If the signal is blocked, the delivery of the signal is pending until not blocked (or caught by the other option below). 如果信号被阻止,则信号的传递将被暂停,直到未被阻止(或被下面的其他选项捕获)。

    2. sigwait() , sigwaitinfo() , or sigtimedwait() . sigwait()sigwaitinfo()sigtimedwait() These functions check if any signals are pending, and if so, "catch" it. 这些函数检查是否有任何信号未决,如果是,则“捕获”它。 The set of signals they check is defined by a function parameter of sigset_t type. 它们检查的信号集由sigset_t类型的函数参数定义。

When the kernel sends/forwards a signal to a process, it first checks if the process has a thread that is not blocking that signal. 当内核向进程发送/转发信号时,它首先检查进程是否有一个没有阻塞该信号的线程。 If there is such a thread, it delivers it via that thread. 如果有这样的线程,它通过该线程传递它。 (If the signal has a signal handler, that signal handler gets invoked in that thread; otherwise, the effect is dictated by the signal disposition .) (如果信号有一个信号处理程序,那个信号处理程序在该线程中被调用;否则,效果由信号处理决定 。)

If the signal is blocked, the kernel leaves it pending for the process. 如果信号被阻止,则内核会使其等待进程。

If the process calls sigwait() , sigwaitinfo() , or sigtimedwait() with the pending signal in the specified signals set, it receives the information on that signal, and the signal is caught. 如果进程使用指定信号集中的挂起信号调用sigwait()sigwaitinfo()sigtimedwait() ,它将接收有关该信号的信息,并捕获该信号。 (It will no longer be pending, and it will not cause a signal handler to be invoked; it is "consumed".) (它将不再处于挂起状态,并且不会导致调用信号处理程序;它被“消耗”。)

If the process changes its signal mask, so that the pending signal becomes unblocked, it is delivered by the kernel (just as if it was sent at that point in time). 如果进程更改其信号掩码,以便挂起信号被解除阻塞,则由内核传递(就像它在那个时间点发送一样)。

Also, is there a way to block a signal in a process without using the sigsetops(3) and sigprocmask(2) functions? 另外,有没有办法在不使用sigsetops(3)和sigprocmask(2)函数的情况下阻止进程中的信号?

No. (You can implement your own sigsetops() and a syscall wrapper for sigprocmask() , but that's about it.) 不。(您可以为sigprocmask()实现自己的sigsetops()和一个系统调用包装器,但这就是它。)


Here is an example program, example.c , you can use for exploring signal handlers, catching signals, and the signal mask, in a single-threaded process: 下面是一个示例程序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;
}

Compile it using for example 例如,使用它编译它

gcc -Wall -O2 example.c -o example

I suggest you prepare two terminals. 我建议你准备两个终端。 In one terminal, run the compiled program, using 在一个终端中,使用运行已编译的程序

./example

and observe its output. 并观察其输出。 It will be something like 它会是这样的

Process 843 is ready to receive signals! 过程843准备好接收信号! Run
kill -USR1 843 杀死-USR1 843
kill -USR2 843 杀死-USR2 843
kill -HUP 843 杀死-HUP 843
kill -TERM 843 杀死-TERM 843
in another terminal; 在另一个终端; press Ctrl+C in this terminal; 在此终端按Ctrl + C; or press Ctrl+Z and run 或按Ctrl + Z并运行
fg FG
in this terminal. 在这个终端。

The KILL and STOP signals cannot be caught. 无法捕获KILL和STOP信号。 KILL will always kill the process, and STOP will always stop ("pause") the process. KILL将始终终止进程,STOP将始终停止(“暂停”)该进程。

If you press Ctrl + C in that terminal, the kernel will send an INT signal to the process. 如果在该终端中按Ctrl + C ,内核将向进程发送INT信号。 (This will be delivered via the report_signal() signal handler.) (这将通过report_signal()信号处理程序传递。)

If you press Ctrl + Z in that terminal, the kernel will send a STOP signal to the process. 如果在该终端中按Ctrl + Z ,内核将向进程发送STOP信号。 The shell detects this, pushing ./example under job control, and lets you input new shell commands. shell检测到这一点,在作业控制下推送./example ,并允许您输入新的shell命令。 The fg command brings ./example back to foreground, with the shell sending it the CONT signal, so that ./example will continue execution. fg命令将./example带回前台,shell向其发送CONT信号,以便./example将继续执行。

USR1 and USR2 signals are blocked, so they are never delivered to the report_signal() signal handler. USR1和USR2信号被阻止,因此它们永远不会传递给report_signal()信号处理程序。

HUP and TERM signals are also blocked, but they are received by the main thread via sigwaitinfo() . HUP和TERM信号也被阻止,但主线程通过sigwaitinfo()接收它们。

The program exits, when it receives a TERM signal. 程序在收到TERM信号时退出。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM