2011-08-05 36 views

回答

13

首先,SIGTERM杀死你的过程中,如果不抓住,select()回报。因此,您必须安装SIGTERM的信号处理程序。这样做使用sigaction()

然而,SIGTERM信号可以在瞬间到达您的线程受阻于select()。如果你的进程大部分是在文件描述符上休眠的话,这将是一种罕见的情况,但它可能会发生。这意味着您的信号处理器必须做一些事情来通知中断的主程序,即设置一些标志变量(类型sig_atomic_t),或者您必须保证仅在select()处于睡眠状态时才会传送。

我会采用后一种方法,因为它更简单,尽管不够灵活(请参阅帖子末尾)。

所以,你只是打电话select()之前阻止SIGTERM,并reblock它在函数返回之后马上让你的进程只接收而内select()睡觉的信号。但请注意,这实际上会造成竞争状况。如果信号在解锁后立即到达,但在select()被调用之前,系统调用将不会被调用,因此它不会返回-1。如果信号刚刚在select()返回成功后到达,但在重新阻止之前,您也丢失了信号。

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

首先,块SIGTERM使用sigprocmask()在进入pselect()循环之前。之后,请致电pselect(),并带上sigprocmask()返回的原始面具。这样你保证你的过程只会在select()上睡觉时被打断。

总结:

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

请注意,如果成功后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; 
} 
+0

@Akek:首先,谢谢你的详细解释。我有几个问题:SIGTERM信号可以在你的线程在select()处不被阻塞的时刻到达 - 如何处理SIGTERM?寻求来自fds的输入是我的过程所做的事情之一(不是唯一的事情)。 – hari

+0

@hari:如果你这样做,信号将不会在这种情况下传递。如果它到达'pselect()'之外,那么信号只有在你调用'pselect()'的时候才被延迟和传递。而且,推迟信号以供后续处理没有任何不妥之处。如果您想尽可能快地处理信号,您将不得不使用我简要描述的标记方法。而不是只设置一个标志,你实际上可以在信号处理程序中做更多的处理,但这是危险的:你仅限于调用异步安全函数,并且全局状态可能不一致。 – alecov

+0

谢谢。顺便说一句,在某些时候,我确实需要'SIG_UNBLOCK'阻止的信号,对吧? – hari

4

答案部分在您指出的Q &的评论之一中;

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

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

现在,如果你特别想捕捉SIGTERM,那么你需要设置一个signal的电话,就像这样;

signal(SIGTERM,yourcatchfunction); 

在您捕捉功能应该因此,在总结中定义类似

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

,你必须设置一个信号处理器yourcatchfunction,目前你的程序是在select()呼叫等待IO - 当信号到达,你的catchfunction将被调用,当你从那返回时,select调用将返回errno设置为EINTR。

但是要注意的是,SIGTERM可以随时发生所以你可能在select调用发生时,在这种情况下,你将永远不会看到EINTR但只有yourcatchfunction

的正常通话

因此,select()以err和errno EINTR返回就是这样,您可以采取非阻塞操作 - 它不会捕获信号。

+0

好点,Soren - 在重读时,OP在询问如何捕捉信号。在select()调用之前,信号捕获必须在信号到达该过程之前设置。 –

+0

@Soren:谢谢。我应该在代码中放置'signal(SIGTERM,yourcatchfunction);'? – hari

+0

在大多数情况下,您应该将调用置于'signal()'作为该进程初始化的一部分。 – Soren

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

+0

感谢您的解释。我有一个问题:你是什么意思的评论“自动重启....“ – hari

+0

这个信号在它有机会返回给调用者(你)之前表面上中断了对”select()“的调用,在这种情况下(如果”errno“设置为”EINTR“), –

+0

@unluddite:谢谢,我想了解'do while()'条件组合:当两个子条件都是这意味着由于信号返回'select()',正确吗?但是如果'retval不是<0'和'select_errno == EINTR'意味着某个其他调用(除了'select()')被中断? – hari