用select()从管道读取信号

使用带管道的select() – 这就是我正在做的事情,现在我需要抓住SIGTERM 。 我该怎么做? 当select()返回错误(<0)时,我是否必须这样做?

首先, SIGTERM将在未捕获的情况下SIGTERM您的进程,并且select()不会返回。 因此,您必须为SIGTERM安装信号处理程序。 使用sigaction()来做到这一点。

但是, SIGTERM信号可以在您的线程未被 select()阻塞的时刻到达。 如果您的进程主要是在文件描述符上hibernate,那么这将是一种罕见的情况,但否则就会发生。 这意味着您的信号处理程序必须执行某些操作以通知主例程中断,即设置一些标志变量(类型为sig_atomic_t ),或者您必须保证SIGTERM仅在进程在select()上hibernate时传递。

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

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

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

首先,在进入pselect()循环之前使用sigprocmask()阻止SIGTERM 。 之后,只需使用sigprocmask()返回的原始掩码调用pselect() sigprocmask() 。 这样您就可以保证只有在select()上hibernate时才会中断您的进程。

综上所述:

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

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

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

编辑:没有比代码示例更好地说明用法。

 #define _POSIX_C_SOURCE 200809L #include  #include  #include  #include  #include  #include  // 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; } 

答案部分在于您指出的问答中的一个评论;

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

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

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

 signal(SIGTERM,yourcatchfunction); 

你的catch函数应该被定义为类似的东西

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

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

但请注意,SIGTERM可以随时发生因此您可能不会在选择调用中出现,在这种情况下,您将永远不会看到EINTR而只会定期调用您的yourcatchfunction

因此,使用err和errno EINTR返回的select()只是为了你可以采取非阻塞动作 – 它不会捕获信号。

您可以在循环中调用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 (ie, 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. (Eg, 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(). */ }