用select()从管道读取信号时捕捉信号



使用select() with pipe -这就是我正在做的,现在我需要抓住SIGTERM。我该怎么做呢?当select()返回错误(<0) ?

首先,如果没有捕获,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 errno是否为EINTR

请注意,如果在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;
}

答案部分在您指向的问题&A中的评论中;

> Interrupt将导致select()返回一个-1,errno设置为EINTR

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

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

signal(SIGTERM,yourcatchfunction);

, catch函数应该定义为

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

所以总的来说,你已经设置了一个信号处理器yourcatchfunction,你的程序目前正在select()调用中等待IO——当信号到达时,你的catch函数将被调用,当你从那个select调用返回时,errno设置为EINTR。

然而,要注意SIGTERM可以在任何时间发生,所以当发生时,可能不在中,在这种情况下,您将永远不会看到EINTR,而只看到yourcatchfunction的常规调用

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

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

相关内容

  • 没有找到相关文章

最新更新