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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9774986/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 05:16:17  来源:igfitidea点击:

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

clinuxnetwork-programming

提问by Jimm

In my application, there is a io-thread, that is dedicated for

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

  1. Wrapping data received from the application in a custom protocol
  2. Sending the data+custom protocol packet over tcp/ip
  3. Receiving data+custom protocol packet over tcp/ip
  4. Unwrapping the custom protocol and handing the data to the application.
  1. 用自定义协议包装从应用程序接收的数据
  2. 通过tcp/ip发送数据+自定义协议包
  3. 通过tcp/ip接收数据+自定义协议包
  4. 解开自定义协议并将数据传递给应用程序。

Application processes the data over a different thread. Additionally, the requirements dictate that the unacknowledged window size should be 1, i.e. there should be only one pending unacknowledged message at anytime. This implies that if io-thread has dispatched a message over the socket, it will not send any more messages, till it hears an ack from the receiver. Application's processing thread communicates to io-thread via pipe. Application needs to shut gracefully if someone from linux CLI types ctrl+C. Thus, given these requirements, i have following options

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

  1. Use PPoll() on socket and pipe descriptors
  2. Use Select()
  3. Use PSelect()
  1. 在套接字和管道描述符上使用 PPoll()
  2. 使用 Select()
  3. 使用 PSelect()

I have following questions

我有以下问题

  1. The decision between select() and poll(). My application only deals with less than 50 file descriptors. Is it okay to assume there would be no difference whether i choose select or poll ?

    1. Decision between select() and pselect(). I read the linux documentation and it states about race condition between signals and select(). I dont have experience with signals, so can someone explain more clearly about the race condition and select() ? Does it have something to do with someone pressing ctrl+C on CLI and application not stopping?

    2. Decision between pselect and ppoll() ? Any thoughts on one vs the other

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

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

    2. pselect 和 ppoll() 之间的决定?关于一个与另一个的任何想法

采纳答案by torek

I'd suggest by starting the comparison with select()vs poll(). Linux also provides both pselect()and ppoll(); and the extra const sigset_t *argument to pselect()and ppoll()(vs select()and poll()) has the same effect on each "p-variant", as it were. If you are not using signals, you have no race to protect against, so the base question is really about efficiency and ease of programming.

我建议通过与select()vs开始比较poll()。Linux 还提供pselect()ppoll(); 和额外的const sigset_t *参数pselect()ppoll()(VSselect()poll())对每个“P-变体”相同的效果,因为它是。如果你不使用信号,你就没有竞争来保护,所以基本问题实际上是关于效率和编程的简易性。

Meanwhile there's already a stackoverflow.com answer here: what are the differences between poll and select.

同时这里已经有一个 stackoverflow.com 的答案:poll 和 select 之间有什么区别

As for the race: once you start using signals (for whatever reason), you will learn that in general, a signal handler should just set a variable of type volatile sig_atomic_tto indicate that the signal has been detected. The fundamental reason for this is that many library calls are not re-entrant, and a signal can be delivered while you're "in the middle of" such a routine. For instance, simply printing a message to a stream-style data structure such as stdout(C) or cout(C++) can lead to re-entrancy issues.

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

Suppose you have code that uses a volatile sig_atomic_t flagvariable, perhaps to catch SIGINT, something like this (see also http://pubs.opengroup.org/onlinepubs/007904975/functions/sigaction.html):

假设您有使用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 ...
    ...

Now, in the main body of your code, you might want to "run until interrupted":

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

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

This is fine up until you start needing to make calls that wait for some input/output, such as selector poll. The "wait" action needs to wait for that I/O—but it alsoneeds to wait for a SIGINTinterrupt. If you just write:

这很好,直到您开始需要进行等待某些输入/输出的调用,例如selectpoll。“等待”操作需要等待那个 I/O——但它需要等待一个SIGINT中断。如果你只是写:

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

then it's possible that the interrupt will happen just beforeyou call select()or poll(), rather than afterward. In this case, you did get interrupted—and the variable got_interruptedgets set—but after that, you start waiting. You should have checked the got_interruptedvariable before you started waiting, not after.

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

You can try writing:

你可以试试写:

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

This shrinks the "race window", because now you'll detect the interrupt if it happens while you're in the "do some work" code; but there is still a race, because the interrupt can happen right afteryou test the variable, but right beforethe select-or-poll.

这会缩小“竞争窗口”,因为现在您将在“做一些工作”代码中检测到中断是否发生;但是仍然存在竞争,因为中断可能您测试变量之后立即发生,但恰好在选择或轮询之前发生。

The solution is to make the "test, then wait" sequence "atomic", using the signal-blocking properties of sigprocmask(or, in POSIX threaded code, pthread_sigmask):

解决方案是使用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 */
}

(the above code is actually not that great, it's structured for illustration rather than efficiency -- it's more efficient to do the signal mask manipulation slightly differently, and place the "got interrupted" tests differently).

(上面的代码实际上并不是那么好,它的结构是为了说明而不是为了效率——稍微不同地进行信号掩码操作会更有效,并且以不同的方式放置“被中断”的测试)。

Until you actually start needing to catch SIGINT, though, you need only compare select()and poll()(and if you start needing large numbers of descriptors, some of the event-based stuff like epoll()is more efficient than either one).

但是,在您真正开始需要 catch 之前SIGINT,您只需要比较select()poll()(如果您开始需要大量描述符,一些基于事件的东西epoll()比任何一个都更有效)。

回答by j?rgensen

Between (p)select and (p)poll is a rather subtle difference:

(p)select 和 (p)poll 之间有一个相当微妙的区别:

For select, you have to initialize and populate the ugly fd_set bitmaps everytime before you call select because select modifies them in-place in a "destructive" fashion. (poll distinguishes between the .eventsand .reventsmembers in struct pollfd).

对于 select,您必须在每次调用 select 之前初始化并填充丑陋的 fd_set 位图,因为 select 以“破坏性”方式就地修改它们。(轮询区分 中的.events.revents成员struct pollfd)。

After selecting, the entire bitmap is often scanned (by people/code) for events even if most of the fds are not even watched.

选择后,即使大多数 fds 甚至没有被观看,也会经常扫描整个位图(由人/代码)以查找事件。

Third, the bitmap can only deal with fds whose number is less than a certain limit (contemporary implementations: somewhere between 1024..4096), which rules it out in programs where high fds can be easibly attained (notwithstanding that such programs are likely to already use epoll instead).

第三,位图只能处理数量小于某个限制的 fds(当代实现:介于 1024..4096 之间),这在可以轻松获得高 fds 的程序中排除了它(尽管此类程序可能会已经使用 epoll 代替)。

回答by drlolly

The accepted answer is not correct vis a vis difference between select and pselect. It does describe well how a race condition between sig-handler and select can arise, but it is incorrect in how it uses pselect to solve the problem. It misses the main point about pselect which is that it waits for EITHER the file-descriptor or the signal to become ready. pselect returns when either of these are ready.Select ONLY waits on the file-descriptor. Select ignores signals. See this blog post for a good working example: https://www.linuxprogrammingblog.com/code-examples/using-pselect-to-avoid-a-signal-race

与 select 和 pselect 之间的差异相比,接受的答案不正确。它确实很好地描述了 sig-handler 和 select 之间的竞争条件是如何出现的,但它在如何使用 pselect 解决问题方面是不正确的。它忽略了关于 pselect 的要点,即它等待文件描述符或信号准备就绪。pselect 在其中任何一个准备就绪时返回。Select ONLY 等待文件描述符。选择忽略信号。有关一个很好的工作示例,请参阅此博客文章:https: //www.linuxprogrammingblog.com/code-examples/using-pselect-to-avoid-a-signal-race

回答by Varyag

To make the picture presented by the accepted answer complete following basic fact should be mentioned: both select() and pselect() may return EINTR as stated in their man pages:

为了使接受的答案呈现的图片完整,应提及以下基本事实: select() 和 pselect() 都可能返回 EINTR,如其手册页中所述:

EINTR A signal was caught; see signal(7).

EINTR 一个信号被捕获;见信号(7)。

This "caught" means that the signal should be recognized as "occurred during the system call execution":
1. If non-masked signal occurs duringselect/pselect execution then select/pselect will exit.
2. If non-masked signal occurs beforeselect/pselect has been called this will not have any effect and select/pselect will continue waiting, potentially forever.

这个“捕获”意味着信号应该被识别为“在系统调用执行期间发生”:
1. 如果select/pselect 执行期间出现非屏蔽信号,则 select/pselect将退出
2. 如果select/pselect 被调用之前出现非屏蔽信号,这不会有任何影响,select/pselect 将继续等待,可能永远等待

So if a signal occurs during select/pselect execution we are ok - the execution of select/pselect will be interrupted and then we can test the reason for the exit and discover that is was EINTR and then we can exit the loop.
The real threat that we faceis a possibility of signal occurrence outside of select/pselect execution, then we may hang in the system call forever. Any attempt to discover this "outsider" signal by naive means:

因此,如果在 select/pselect 执行期间出现信号,我们就可以了 - select/pselect 的执行将被中断,然后我们可以测试退出的原因并发现是 EINTR,然后我们可以退出循环。
我们面临的真正威胁是可能在 select/pselect 执行之外发生信号,然后我们可能会永远挂在系统调用中。任何试图通过幼稚的方式发现这个“局外人”信号的尝试:

if (was_a_signal) {
...
}

如果(was_a_signal){
...
}

will fail since no matter how close this test will be to the call of select/pselect there is always a possibility that the signal will occur just after the test and before the call to select/pselect.
Then, if the only place to catch the signal is during select/pselect execution we should invent some kind of "wine funnel"so all "wine splashes" (signals), even outside of "bottle neck" (select/pselect execution period) will eventually come to the "bottle neck".
But how can you deceive system call and make it "think" that the signal has occurred during this system call execution when in reality it has occurred before?
Easy. Here is our "wine funnel": you just block the signal of interest and by that cause it (if it has occurred at all) waiting outsideof the process "for the door to be opened" and you "open the door" (unmask the signal) only when you're prepared "to welcome the guest" (select/pselect is running). Then the "arrived" signal will be recognized as "just occurred" and will interrupt the execution of the system call.
Of course, "opening the door" is the most critical part of the plan - it cannot be done by the usual means (first unmask, then call to select/pselect), the only possibility is to do the both actions (unmask and system call) at once (atomically) - this is what pselect() is capable of but select() is not.

将失败,因为无论此测试与 select/pselect 的调用有多接近,总是有可能在测试之后和调用 select/pselect 之前出现信号。
然后,如果唯一捕获信号的地方是在 select/pselect 执行期间,我们应该发明某种“酒漏斗”,以便所有“酒溅”(信号),甚至在“瓶颈”之外(select/pselect 执行期间)最终会来到“瓶颈”。
但是你怎么能欺骗系统调用并让它“认为”在这个系统调用执行期间发生了信号,而实际上它之前已经发生过呢?
简单。这是我们的“葡萄酒漏斗”“开门”过程之外,只有当您准备好“欢迎客人”(select/pselect 正在运行)时,您才“开门”(取消屏蔽信号)。然后“到达”信号将被识别为“刚刚发生”并中断系统调用的执行。
当然,“开门”是计划中最关键的部分——它不能通过通常的方式(先取消屏蔽,然后调用select/pselect)来完成,唯一的可能是同时做这两个动作(取消屏蔽和系统call) 一次(原子地) -这是 pselect() 能够做到的,但 select() 不是