【问题标题】:catching signals while reading from pipe with select()使用 select() 从管道读取时捕获信号
【发布时间】:2011-10-21 04:29:33
【问题描述】:

using select() with pipe - 这就是我正在做的事情,现在我需要抓住SIGTERM。我该怎么做?当select() 返回错误(

【问题讨论】:

    标签: c signals pipe signal-handling


    【解决方案1】:

    答案部分在您指向的问答中的一条评论中;

    > 中断将导致 select() 返回一个 -1 且 errno 设置为 EINTR

    也就是说;对于捕获的任何中断(信号),选择将返回,并且 errno 将设置为 EINTR。

    现在如果你特别想捕捉 SIGTERM,那么你需要通过调用 signal 来设置它,就像这样;

    signal(SIGTERM,yourcatchfunction);
    

    你的 catch 函数应该在哪里定义

    void yourcatchfunction(int signaleNumber) { .... }
    

    总而言之,您已经设置了一个信号处理程序yourcatchfunction,并且您的程序当前处于等待 IO 的select() 调用中——当信号到达时,您的 catch 函数将被调用,当您从该函数返回时选择调用将返回设置为 EINTR 的 errno。

    但是请注意,SIGTERM 可能随时发生,因此当它发生时您可能在 select 调用中,在这种情况下您将永远看不到 EINTR但只能定期拨打yourcatchfunction

    因此返回 err 和 errno EINTR 的 select() 只是为了让您可以采取非阻塞操作——它不会捕获信号。

    【讨论】:

    • 好点,Soren -- 重读时,OP 询问如何捕捉信号。必须在信号到达进程之前,在 select() 调用之前设置信号捕获。
    • @Soren:谢谢。我应该将signal(SIGTERM,yourcatchfunction); 放在代码的什么位置??
    • 在大多数情况下,您应该调用signal() 作为您初始化过程的一部分。
    【解决方案2】:

    您可以循环调用select()。这称为重新启动系统调用。这是一些伪 C。

    int retval = -1;
    int select_errno = 0;
    
    do {
       retval = select(...);
    
       if (retval < 0)
       {
           /* Cache the value of errno in case a system call is later
            * added prior to the loop guard (i.e., the while expression). */
           select_errno = errno;
       }
    
       /* Other system calls might be added here.  These could change the
        * value of errno, losing track of the error during the select(),
        * again this is the reason we cached the value.  (E.g, you might call
        * a log method which calls gettimeofday().) */
    
    /* Automatically restart the system call if it was interrupted by
     * a signal -- with a while loop. */
    } while ((retval < 0) && (select_errno == EINTR));
    
    if (retval < 0) {
       /* Handle other errors here. See select man page. */
    } else {
       /* Successful invocation of select(). */
    }
    

    【讨论】:

    • 感谢您的解释。我有一个问题:评论“自动重启 ....”是什么意思
    • 信号表面上中断了对select() 的调用,然后才有机会返回给调用者(你)。在这种情况下(如果 errno 设置为 EINTR),您只需返回循环并再次调用 select 从而“重新启动”调用。
    • @unluddite:谢谢,我试图理解do while() 条件组合:当两个子条件都为真时,这意味着select() 因为信号而返回,对吗?但如果retval is not &lt; 0select_errno == EINTR 意味着其他呼叫(select() 除外)被中断了?
    • 不,如果select_errno == EINTR 那么retval 必须&lt; 0(实际上是-1)。 A -1 作为返回意味着发生了“非正常情况”。我们检查errno 的值,如果它设置为EINTR,那么“某事”就是中断呼叫的信号。
    • 我建议您不要将此技术称为“重新启动系统调用”吗?这个短语很容易(错误地)理解为 SA_RESTART 的语义和一些历史上的 signal() 实现,两者都与信号和系统调用的讨论密切相关。
    【解决方案3】:

    首先,SIGTERM杀死您的进程,如果没有被捕获,select()不会返回。因此,您必须为SIGTERM 安装信号处理程序。使用sigaction() 执行此操作。

    但是,SIGTERM 信号可能会在您的线程select() 阻塞的时刻到达。如果您的进程主要在文件描述符上休眠,这将是一种罕见的情况,但否则可能会发生。这意味着您的信号处理程序必须做一些事情来通知主程序中断,即设置一些标志变量(sig_atomic_t 类型),或者您必须保证 SIGTERM 仅在进程休眠时传递select().

    我将采用后一种方法,因为它更简单,但不太灵活(见文末)。

    因此,您在调用select() 之前阻塞SIGTERM,并在函数返回后立即重新阻塞它,以便您的进程仅在select() 内休眠时接收信号。但请注意,这实际上会产生竞争条件。如果信号在解除阻塞之后到达,但在调用select()之前,系统调用还没有被调用,因此它不会返回-1。如果信号是在select()成功返回之后刚刚到达,但在重新阻塞之前,你也失去了信号。

    因此,您必须为此使用pselect()。它以原子方式在select() 周围进行阻塞/解除阻塞。

    首先,在进入pselect() 循环之前,使用sigprocmask() 阻止SIGTERM。之后,只需使用sigprocmask() 返回的原始掩码调用pselect()。这样你就可以保证你的进程只会在select()睡觉时被打断。

    总结:

    1. SIGTERM 安装一个处理程序(什么都不做);
    2. 在进入pselect()循环之前,使用sigprocmask()阻止SIGTERM
    3. 使用sigprocmask() 返回的旧信号掩码调用pselect()
    4. pselect() 循环内,现在您可以安全地检查pselect() 是否返回-1 并且 errnoEINTR

    请注意,如果pselect()成功返回后,你做了很多工作,你在回复SIGTERM时可能会遇到更大的延迟(因为进程必须完成所有处理并在实际处理之前返回pselect()信号)。如果这是一个问题,您必须在信号处理程序中使用标志变量,以便您可以在代码中的多个特定点检查此变量。但是,使用标志变量并不能消除竞争条件,也不能消除对pselect() 的需求。

    记住:每当您需要等待某些文件描述符来传递信号时,您必须使用pselect()(或ppoll(),对于支持它的系统)。

    编辑:最好用一个代码示例来说明用法。

    #define _POSIX_C_SOURCE 200809L
    #include <errno.h>
    #include <signal.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/select.h>
    #include <unistd.h>
    
    // Signal handler to catch SIGTERM.
    void sigterm(int signo) {
        (void)signo;
    }
    
    int main(void) {
        // Install the signal handler for SIGTERM.
        struct sigaction s;
        s.sa_handler = sigterm;
        sigemptyset(&s.sa_mask);
        s.sa_flags = 0;
        sigaction(SIGTERM, &s, NULL);
    
        // Block SIGTERM.
        sigset_t sigset, oldset;
        sigemptyset(&sigset);
        sigaddset(&sigset, SIGTERM);
        sigprocmask(SIG_BLOCK, &sigset, &oldset);
    
        // Enter the pselect() loop, using the original mask as argument.
        fd_set set;
        FD_ZERO(&set);
        FD_SET(0, &set);
        while (pselect(1, &set, NULL, NULL, NULL, &oldset) >= 0) {
            // Do some processing. Note that the process will not be
            // interrupted while inside this loop.
            sleep(5);
        }
    
        // See why pselect() has failed.
        if (errno == EINTR)
            puts("Interrupted by SIGTERM.");
        else
            perror("pselect()");
        return EXIT_SUCCESS;
    }
    

    【讨论】:

    • @Akek:首先,感谢您的详尽解释。不过我有几个问题: SIGTERM 信号可能会在您的线程未在 select() 处被阻塞的时刻到达——如何处理该 SIGTERM?从 fds 寻求输入是我的流程所做的事情之一(不是唯一的事情)。
    • @hari:如果您执行上述操作,则信号将永远在该条件下传递。如果它到达pselect() 之外,则信号会延迟并仅在您调用pselect() 的那一刻传递。此外,将信号推迟到以后处理也没有什么不好。如果您想尽快处理信号,则必须使用我简要描述的标志方法。而不是仅仅设置一个标志,你实际上可以在信号处理程序中做更多的处理,但这很危险:你仅限于调用异步安全函数,并且全局状态可能不一致。
    • 谢谢。顺便说一句,在某个时间点,我也确实需要SIG_UNBLOCK 被阻止的信号,对吧?
    • 上面的例子是完整的(关于SIGTERM)。如果您在某处解除对SIGTERM 的阻止,则可能会在pselect() 之外接收到信号。如果您使用标志方法,SIGTERM 将保持未阻塞,并且仅在检查标志和调用pselect() 之前被阻塞,并在pselect() 返回后被解除阻塞。所有其他信号均未触及。
    • 感谢您对我的包容。在我的程序中,有一个函数可以在pipe 上执行所有read。那么我应该把所有这些代码放在哪里呢?在main() 或在管道上读取的那个函数中?
    猜你喜欢
    • 1970-01-01
    • 2015-01-28
    • 2013-04-07
    • 2013-04-27
    • 1970-01-01
    • 1970-01-01
    • 2012-09-22
    • 2012-11-25
    相关资源
    最近更新 更多