用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时才会中断您的进程。
综上所述:
- 为
SIGTERM
安装一个处理程序(什么都不做); - 在进入
pselect()
循环之前,使用sigprocmask()
阻止SIGTERM
; - 使用
sigprocmask()
返回的旧信号掩码调用pselect()
sigprocmask()
; - 在
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(). */ }