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

In my application there is io-thread dedicated to

  • Flow around data received from an application in a user protocol
  • Sending data + packet of user protocols via tcp / ip
  • Receiving data + user protocol packet through tcp / ip
  • Deploying a user protocol and transferring data to the application.

The application processes data on a different thread. In addition, the requirements dictate that the size of the unconfirmed window should be 1, that is, at any time there should be only one pending unrecognized message. This means that if io-thread sent a message through a socket, it will not send any more messages until it receives a response from the recipient. Application processing thread communicates with io-thread through a pipe. The application should be gracefully closed if one of the CLI types is linux ctrl + C. Thus, given these requirements, I have the following options

  • Use PPoll () for socket and pipe descriptors
  • Use Select ()
  • Use PSelect ()

I have the following questions

  • The solution is between select () and poll (). There are less than 50 file descriptors running in my application. Can it be assumed that there will be no difference if I choose a choice or a survey?

    • Solution between select () and pselect (). I read the Linux documentation and it talks about the race condition between signals and select (). I have no experience with signals, so can someone explain more clearly about the state of the race and choose ()? Is this because someone pressed ctrl + C on the CLI and the application did not stop?

    • Solution pselect and ppoll ()? Any thoughts one against the other

+18
c linux network-programming
Mar 19 2018-12-12T00:
source share
3 answers

I would suggest starting a comparison with select() vs poll() . Linux also provides both pselect() and ppoll() ; and the additional argument const sigset_t * for pselect() and ppoll() (vs select() and poll() ) has the same effect on each "p-variant". If you do not use signals, you do not have races for protection, so the basic question is really about the effectiveness and ease of programming.

Meanwhile, stackoverflow.com already has an answer here: what is the difference between a poll and a choice .

As for the race: as soon as you start using signals (for some reason), you will find out that in the general case, the signal handler should just set a variable like volatile sig_atomic_t to indicate that the signal has been detected. The main reason for this is that many library calls are not re-entrant , and the signal can be delivered while you are “in the middle” of such a procedure. For example, simply printing the message into a stream-style data structure such as stdout (C) or cout (C ++) can lead to reconnection issues.

Suppose you have code that uses the volatile sig_atomic_t flag variable, perhaps to catch a SIGINT , something like this (see also 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 part of your code, you can "run until interrupted":

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

This is normal until you start making calls waiting for I / O, such as select or poll . The wait action must wait for this I / O, but it also needs to wait for the SIGINT interrupt. If you just write:

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

then it is possible that the interrupt will happen just before you call select() or poll() , and not later. In this case, you got an interrupt - and the got_interrupted variable got_interrupted set, but after that you start to wait. You should have checked the got_interrupted variable before you started to wait, not after.

You can try to write:

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

This shortens the "race window" because now you will find an interrupt if this happens when you are in the "do some work" code; but there is still a race, because interruption can occur immediately after checking the variable, but right before the choice or poll.

The solution is to make the sequence "test, then wait" "atomic" using the signal blocking properties of sigprocmask (or in the POSIX stream code, 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 so large, it is structured to illustrate rather than efficiency - it’s more efficient to manipulate the signal masses a little differently, and differently place the tests with "interrupted").

Until you actually start catching SIGINT , you only need to compare select() and poll() (and if you start to need a lot of descriptors, some of the events like epoll() are more efficient than one).

+22
Mar 19 '12 at 18:30
source share

Between (p) select and (p), polling is a pretty subtle difference:

To select, you must initialize and populate the ugly fd_set bitmaps each time you select select, because select modifies them in place with a "destructive" way. (The poll distinguishes between .events and .revents in struct pollfd ).

After selection, all raster images are often checked (by people / code) for events, even if most of the FSDs are not even viewed.

Thirdly, the bitmap can only deal with fds, the number of which is less than a certain limit (modern implementations: somewhere between 1024..4096), which excludes it in programs where you can achieve a high level of fds (despite the fact that such programs most likely already use epoll).

+5
Mar 19 '12 at 18:17
source share

The accepted answer is incorrect regarding the difference between select and pselect. It describes well how a race condition between sig-handler and select can occur, but is incorrect in how it uses pselect to solve the problem. He does not notice the main question about pselect, which is that he expects the file descriptor or signal to become ready. pselect returns when any of them are ready. Select ONLY waiting for the file descriptor. Choose to ignore signals. See this blog post for a good working example: https://www.linuxprogrammingblog.com/code-examples/using-pselect-to-avoid-a-signal-race

+2
Jun 02 '17 at 15:44
source share



All Articles