一尘不染

Linux select()vs ppoll()vs pselect()

linux

在我的应用程序中,有一个io线程,专用于

  1. 用自定义协议包装从应用程序收到的数据
  2. 通过TCP / IP发送数据+自定义协议数据包
  3. 通过TCP / IP接收数据+自定义协议数据包
  4. 解包自定义协议并将数据传递给应用程序。

应用程序通过不同的线程处理数据。此外,要求还规定未确认的窗口大小应为1,即随时都应只有一条待处理的未确认消息。这意味着,如果io-
thread在套接字上调度了一条消息,它将不会再发送任何消息,直到它听到来自接收方的确认为止。应用程序的处理线程通过管道与io线程通信。如果有人从Linux
CLI输入ctrl + C,则应用程序需要正常关闭。因此,鉴于这些要求,我有以下选择

  1. 在套接字和管道描述符上使用PPoll()
  2. 使用Select()
  3. 使用PSelect()

我有以下问题

  1. select()和poll()之间的决定。我的应用程序仅处理少于50个文件描述符。可以假设我选择select或poll都没有区别吗?

    1. 在select()和pselect()之间进行决策。我阅读了Linux文档,并说明了信号和select()之间的竞争状态。我没有信号的经验,所以有人可以更清楚地说明比赛条件和select()吗?这与有人在CLI上按ctrl + C且应用程序没有停止有关吗?

    2. pselect和ppoll()之间的决定?对一个人的任何想法


阅读 265

收藏
2020-06-03

共1个答案

一尘不染

我建议通过与select()vs 开始比较poll()。Linux还提供pselect()ppoll();和和(vs
和)的额外const sigset_t *参数对每个“
p变量”具有相同的作用。如果您不使用信号,那么您就没有竞争的机会,因此,基本问题实际上是关于效率和易于编程的问题。pselect()``ppoll()``select()``poll()

至于比赛:一旦开始使用信号(无论出于何种原因),您将了解到,信号处理程序通常应设置一个类型变量,volatile sig_atomic_t以指示已检测到信号。造成这种情况的根本原因是,许多库调用都不是可重入的,并且可以在您处于此类例程“中间”时传递信号。例如,仅将消息打印到流样式的数据结构(例如stdout(C)或cout(C
++))会导致重新进入问题。

假设您有使用volatile sig_atomic_t flag变量(可能是catch)的代码,SIGINT如下所示(另请参见http://pubs.opengroup.org/onlinepubs/007904975/functions/sigaction.html):

volatile sig_atomic_t got_interrupted = 0;
void caught_signal(int unused) {
    got_interrupted = 1;
}
...
    struct sigaction sa;
    sa.sa_handler = caught_signal;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    if (sigaction(SIGINT, &sa, NULL) == -1) ... handle error ...
    ...

现在,在代码主体中,您可能希望“运行直到被中断”:

    while (!got_interrupted) {
         ... do some work ...
    }

这很好,直到您开始需要进行等待某些输入/输出的调用(例如select或)为止poll。“等待”操作需要等待该I / O,但
需要等待SIGINT中断。如果您只写:

    while (!got_interrupted) {
        ... do some work ...
        result = select(...); /* or result = poll(...) */
    }

那么可能会 您调用select()
之前poll()而不是之后发生中断。在这种情况下,您确实被打断了,并且变量got_interrupted被设置了,但是在那之后,您开始等待。您应该got_interrupted在开始等待之前而不是之后检查变量。

您可以尝试编写:

    while (!got_interrupted) {
        ... do some work ...
        if (!got_interrupted)
            result = select(...); /* or result = poll(...) */
    }

这会缩小“竞赛窗口”,因为现在您可以在“执行一些工作”代码时检测到中断(如果发生);但是仍然存在竞争,因为中断可能 您测试变量 后立即 发生,而
选择或轮询 之前 发生。

解决方案是使用sigprocmask(或在POSIX线程代码中pthread_sigmask)的信号阻止属性使“测试,然后等待”序列“原子”化:

sigset_t mask, omask;
...
while (!got_interrupted) {
    ... do some work ...
    /* begin critical section, test got_interrupted atomically */
    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    if (sigprocmask(SIG_BLOCK, &mask, &omask))
        ... handle error ...
    if (got_interrupted) {
        sigprocmask(SIG_SETMASK, &omask, NULL); /* restore old signal mask */
        break;
    }
    result = pselect(..., &omask); /* or ppoll() etc */
    sigprocmask(SIG_SETMASK, &omask, NULL);
    /* end critical section */
}

(上面的代码实际上并不是那么好,它只是为了说明而不是为了效率而设计的-稍微不同地进行信号屏蔽操作,并以不同的方式放置“中断”测试会更加有效)。

但是,直到您真正开始需要捕获之前SIGINT,您只需要比较select()poll()(并且如果您开始需要大量的描述符,则某些基于事件的东西(例如,epoll()其中之一)要比任何一个都更有效)。

2020-06-03