C: select() - 信号中断

C: select() - Signal interrupt

我正在用 C 语言编写一个使用 AF_UNIX 套接字的多线程服务器程序。 服务器的基本结构是:

此例程无限循环重复,直到发出 SIGINT。 必须在不退出循环的情况下对 SIGUSR1 执行另一个功能。

我对此表示怀疑,因为如果我发出 SIGINT,我的程序会以 EINTR = Interrupted system call 退出。 我知道 pselect() 调用和 "self pipe" 技巧,但我不知道如何让它们在多线程情况下工作。

我正在寻找一种(POSIX 兼容)信号管理,它可以在主线程等待 pselect() 时防止 EINTR 错误。

我post一些代码来说明:

这里我设置了信号处理程序(忽略errorConsolePrint函数)

if(signal(SIGINT, &on_SIGINT) == SIG_ERR)
{
    errorConsolePrint("File: %s; Line: %d; ", "Setting SIGINT handler", __FILE__, __LINE__);
    exit(EXIT_FAILURE);
}
if(signal(SIGTERM, &on_SIGINT) == SIG_ERR)
{
    errorConsolePrint("File: %s; Line: %d; ", "Setting SIGINT handler", __FILE__, __LINE__);
    exit(EXIT_FAILURE);
}
if(signal(SIGUSR1, &on_SIGUSR1) == SIG_ERR)
{
    errorConsolePrint("File: %s; Line: %d; ", "Setting to SIGUSR1 handler", __FILE__, __LINE__);
    exit(EXIT_FAILURE);
}

if(signal(SIGPIPE, SIG_IGN) == SIG_ERR)
{
    errorConsolePrint("File: %s; Line: %d; ", "Setting to ignore SIGPIPE", __FILE__, __LINE__);
    exit(EXIT_FAILURE);
}

这里我为pselect

设置了信号掩码
sigemptyset(&mask);
sigemptyset(&saveMask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGUSR1);
sigaddset(&mask, SIGPIPE);

这里我叫pselect

test = saveSet(masterSet, &backUpSet, &saveMaxFd);
CHECK_MINUS1(test, "Server: creating master set's backup ");

int test = pselect(saveMaxFd+1, &backUpSet, NULL, NULL, &waiting, &mask);
if(test == -1 && errno != EINTR)
{
    ...error handling...
    continue;
}

希望得到一些帮助! 提前谢谢大家。

您可能应该做的是将一个线程专用于信号处理。这是一个草图:

main 中,在生成任何线程之前,阻止除 SIGILL、SIGABRT、SIGFPE、SIGSEGV 和 SIGBUS 之外的所有信号(使用 pthread_sigmask)。

然后,生成您的信号处理程序线程。此线程循环调用 sigwaitinfo 以获取您关心的信号。它采取适合每个人的任何行动;这可能包括向主线程发送消息以触发干净关闭(SIGINT),将 "another function" 排队到工作池(SIGUSR1)中进行处理,等等。你做 not 为这些信号安装处理程序。

然后你生成你的线程池,它根本不需要关心信号。

我建议采用以下策略:

  • 在初始化期间,像您一样设置信号处理程序。
  • 在初始化期间,阻止所有(可阻止的)信号。例如参见 [​​=19=].
  • 在您的主线程中使用 pselect 以在调用期间解除线程阻塞,同样如此。

这样做的好处是 所有 系统调用,包括所有工作线程中的所有系统调用,永远不会 return EINTR,主线程中的单个 pselect 除外。例如,参见 Am I over-engineering per-thread signal blocking? and pselect does not return on signal when called from a separate thread but works fine in single thread program.

的答案

此策略也适用于 select:只需在调用 select 之前立即取消阻塞主线程中的信号,然后再重新阻塞它们。如果您的 select 超时很长或无限大,并且您的文件描述符大部分处于非活动状态,那么您真正需要 pselect 来防止挂起。 (我自己从未使用过 pselect,主要使用没有它的旧 Unix。)

我假设您的信号处理程序是合适的:例如,它们只是自动设置一个全局变量。

顺便说一句,在您的示例代码中,您是否需要 sigaddset(&mask, SIGPIPE),因为 SIGPIPE 已被忽略?

好的,终于找到解决办法了。

我的问题的核心是我的服务器的多线程特性。 经过长时间的搜索,我发现如果我们有其他进程发出的信号(以异步方式),哪个线程捕获信号并不重要,因为行为保持不变:信号被捕获并且之前注册的处理程序是执行。 也许这对其他人来说是显而易见的,但这让我发疯,因为我不知道如何解释执行过程中出现的错误。

在那之后我发现了另一个我解决的问题,是关于过时的 signal() 调用。 在执行过程中,我第一次上升 SIGUSR1,程序按预期捕获并管理它,但第二次它以 User defined signal 1.

退出

我发现 signal() 为特定信号调用设置 "one time" 处理程序,在第一次处理该信号后,该信号的行为 return 为默认行为。

所以这就是我所做的:

这里是信号处理程序:

N.B.: 我在处理程序本身

中重置了 SIGUSR1 的处理程序
static void on_SIGINT(int signum)
{
    if(signum == SIGINT || signum == SIGTERM)
        serverStop = TRUE;
}

static void on_SIGUSR1(int signum)
{
    if(signum == SIGUSR1)
        pendingSIGUSR1 = TRUE;

    if(signal(SIGUSR1, &on_SIGUSR1) == SIG_ERR)
        exit(EXIT_FAILURE);
}

这里我在服务器初始化期间设置了处理程序:

 if(signal(SIGINT, &on_SIGINT) == SIG_ERR)
    exit(EXIT_FAILURE);
if(signal(SIGTERM, &on_SIGINT) == SIG_ERR)
    exit(EXIT_FAILURE);
if(signal(SIGUSR1, &on_SIGUSR1) == SIG_ERR)
    exit(EXIT_FAILURE);
if(signal(SIGPIPE, SIG_IGN) == SIG_ERR)
    exit(EXIT_FAILURE);

这里是服务器的监听周期:

while(!serverStop)
{
    if (pendingSIGUSR1)
    {
        ... things i have to do on SIGUSR1...
        pendingSIGUSR1 = FALSE;
    }


    test = saveSet(masterSet, &backUpSet, &saveMaxFd);
    CHECK_MINUS1(test, "Server: creating master set's backup ");

    int test = select(saveMaxFd+1, &backUpSet, NULL, NULL, &waiting);
    if((test == -1 && errno == EINTR) || test == 0)
        continue;
    if (test == -1 && errno != EINTR)
    {
        perror("Server: Monitoring sockets: ");
        exit(EXIT_FAILURE);
    }

    for(int sock=3; sock <= saveMaxFd; sock++)
    {
        if (FD_ISSET(sock, &backUpSet))
        {
            if(sock == ConnectionSocket)
            {
                ClientSocket = accept(ConnectionSocket, NULL, 0);
                CHECK_MINUS1(ClientSocket, "Server: Accepting connection");

                test = INset(masterSet, ClientSocket);
                CHECK_MINUS1(test, "Server: Inserting new connection in master set: ");
            }
            else
            {
                test = OUTset(masterSet, sock);
                CHECK_MINUS1(test, "Server: Removing file descriptor from select ");
                test = insertRequest(chain, sock);
                CHECK_MINUS1(test, "Server: Inserting request in chain");
            }
         }
    }
}

先阅读 signal(7) and signal-safety(7); you might want to use the Linux specific signalfd(2) since it fits nicely (for SIGTERM & SIGQUIT and SIGINT) into event loops around poll(2) or the old select(2)(或较新的 pselectppoll

另请参阅 this answer (and the pipe(7) 那里提到的自我欺骗,它 POSIX 兼容)到一个非常相似的问题。

此外,signal(2) 文档:

  The effects of signal() in a multithreaded process are unspecified.

所以你真的应该使用 sigaction(2)(即 POSIX)。