C:select()-信号中断

时间:2019-01-31 22:00:07

标签: c sockets signals multiplexing

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

  • 主线程初始化数据结构,并分配“工作者”线程池。
  • 工作线程开始在空的线程安全队列上等待新请求
  • 主线程通过select()调用侦听各种套接字(新连接和已连接的客户端)。
    • select()揭示了可能在连接套接字上进行的读取:主线程调用accept(),并将返回的文件描述符放入fd_set(读取集)中。
    • select()揭示可能在已连接的套接字上读取的内容:主线程从fd_set(读取集)中删除就绪文件描述符,并将它们放入线程安全队列中。
  • 工作线程从队列中提取文件描述符,并开始与链接的客户端通信以处理请求。在服务工作程序线程的末尾,将套接字文件描述符放回fd_set(我编写了使该操作成为线程安全的函数),并再次在队列中等待新请求。

此例程以无限周期重复,直到引发SIGINT。 必须在SIGUSR1上执行另一个功能,而不能退出循环。

我对此表示怀疑,因为如果引发SIGINT,则我的程序将以EINTR = Interrupted system call退出。 我知道pselect()调用和“自管”技巧,但我不知道如何使事情在多线程情况下正常工作。

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

我发布了一些代码进行澄清:

我在这里设置信号处理程序(忽略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;
}

希望有所帮助! 谢谢大家。

4 个答案:

答案 0 :(得分:1)

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

main中,在产生任何线程之前,请阻塞所有信号(使用pthread_sigmask),除了SIGILL,SIGABRT,SIGFPE,SIGSEGV和SIGBUS之外。

然后,生成信号处理程序线程。该线程循环调用sigwaitinfo以获取您关心的信号。它会采取适合每个人的任何操作;这可能包括向主线程发送消息以触发干净关闭(SIGINT),将“另一个功能”排队在工作池中(SIGUSR1)等,等等。您要做安装处理程序这些信号。

然后您产生线程池,根本不必关心信号。

答案 1 :(得分:1)

我建议采取以下策略:

  • 在初始化期间,请像您一样设置信号处理程序。
  • 在初始化期间,阻止所有(可阻止的)信号。例如,请参见Is it possible to ignore all signals?
  • 再次在调用过程中使用pselect在主线程中解除阻塞线程。

这样做的好处是,系统调用的 all (包括所有工作线程中的所有调用)将从不返回EINTR,除了单个{{1}在主线程中。例如,请参见Am I over-engineering per-thread signal blocking?pselect does not return on signal when called from a separate thread but works fine in single thread program的答案。

此策略也可以与pselect一起使用:只需在调用select之前立即取消阻塞主线程中的信号,然后再重新阻塞它们。如果select超时时间长或无限长,并且文件描述符大部分处于非活动状态,则只需要pselect来防止挂起。 (我自己从未使用过select,因为我主要与不具备此功能的旧版Unix一起使用。)

我认为您的信号处理程序合适:例如,它们只是原子地设置了一个全局变量。

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

答案 2 :(得分:1)

好,终于我找到了解决方法。

我的问题的核心是关于服务器的多线程性质。 经过长时间的搜索,我发现在从其他进程中发出信号(以异步方式)的情况下,哪个线程捕获信号都没有关系,因为行为保持不变:捕获了该信号,并且先前注册的处理程序为被执行。 也许这对于其他人可能是显而易见的,但这使我发疯,因为我不知道如何解释执行期间出现的错误。

此后,我发现我解决的另一个问题是关于过时的signal()调用。 在执行期间,我第一次升起SIGUSR1时,程序将按预期进行捕获和管理,但是第二次以User defined signal 1退出。

我发现signal()调用为特定信号设置了“一次”处理程序,在第一次处理该信号后,该信号的行为将返回默认信号。

这就是我所做的:

这里是信号处理程序:

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");
            }
         }
    }
}

答案 3 :(得分:0)

先阅读signal(7)signal-safety(7);您可能要使用特定于Linux的signalfd(2),因为它非常适合SIGTERMSIGQUITSIGINT)在poll(2)或旧的{ {3}}(或更新的pselectppoll

另请参阅select(2)答案(以及此处提到的this自谋技巧,与POSIX兼容)。

还有pipe(7)个文档:

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

因此您确实应该使用signal(2)(即POSIX)。