首页 文章

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

提问于
浏览
3

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

问题涉及(在Debian上):

  • sigprocmask(2)

  • sigsetops(3)相关功能

每个进程都有自己的信号掩码(包含被阻塞信号的long) . 并且可以通过使用* 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)函数的情况下阻止进程中的信号?

1 回答

  • 4

    每个进程都有[sic]自己的信号掩码(包含阻塞信号的long)

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

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

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

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

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

    如果所有线程都阻塞SIGABRT,则在信号被解除阻塞(从信号掩码中删除)之前不会传递 . 如果使用sigwait()sigwaitinfo()sigtimedwait()消耗信号,则根本不会调用信号处理程序 .

    简短摘要:

    • 可以将信号定向到进程组(kill(),带有 pid == 0pid == -pgid ),特定进程( pid )或特定进程中的特定线程(同一进程中的pthread_kill(),通常在Linux中进行tgkill系统调用) .

    • 如果信号指向进程组,则该组中的每个进程都会收到信号的“副本” .

    • 信号掩码定义信号是被阻止还是立即传送 .

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

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

    • 被忽略( SIG_IGN "handler"),或

    • 具有默认处置(忽略(Ign),使用(Core)或不使用(Term)核心转储终止进程;或者它可以停止(停止)或继续(继续)执行目标线程或进程) . 有关详细信息,请参阅man 7 signal .

    • 如果某些线程(但不是所有线程)阻塞信号,并且信号不是针对特定线程,则内核会将信号定向到其中一个未阻塞信号的线程(随机) .

    • 有两种捕获信号的方法:

    • 使用信号处理程序 . 仅当信号未被阻止时,信号才被传送到信号处理器 . 如果信号被阻止,则信号的传递将被暂停,直到未被阻止(或被下面的其他选项捕获) .

    • sigwait()sigwaitinfo()sigtimedwait() . 这些函数检查是否有任何信号未决,如果是,"catch"它 . 它们检查的信号集由 sigset_t 类型的函数参数定义 .

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

    如果信号被阻止,则内核会使其等待进程 .

    如果进程使用指定信号集中的待处理信号调用 sigwait()sigwaitinfo()sigtimedwait() ,它将接收有关该信号的信息,并捕获该信号 . (它将不再处于挂起状态,并且不会导致调用信号处理程序;它是"consumed" . )

    如果进程更改其信号掩码,以便挂起信号被解除阻塞,则由内核传递(就像它在那个时间点发送一样) .

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

    不 . (您可以为 sigprocmask() 实现自己的 sigsetops() 和系统调用包装,但这就是它 . )


    这是一个示例程序 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信号时退出 .

相关问题