5

将 select() 与管道一起使用- 这就是我正在做的事情,现在我需要抓住SIGTERM它。我该怎么做?select()返回错误(<0)时我必须这样做吗?

4

3 回答 3

15

首先,如果没有SIGTERM被捕获,将杀死你的进程,并且select()不会返回。因此,您必须为. 使用.SIGTERMsigaction()

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

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

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

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

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

总之:

  1. 安装一个处理程序SIGTERM(什么都不做);
  2. 在进入循环之前,使用;pselect()阻止SIGTERMsigprocmask()
  3. pselect()使用返回的旧信号掩码调用sigprocmask()
  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;
}
于 2011-08-05T20:58:32.957 回答
4

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

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

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

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

signal(SIGTERM,yourcatchfunction);

你的 catch 函数应该被定义为

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

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

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

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

于 2011-08-05T20:27:21.347 回答
1

你可以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(). */
}

于 2011-08-05T20:28:36.653 回答