GNU/Linux >> LINUX-Kenntnisse >  >> Linux

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

Zwischen (p)select und (p)poll besteht ein ziemlich subtiler Unterschied:

Für select müssen Sie die hässlichen fd_set-Bitmaps jedes Mal initialisieren und füllen, bevor Sie select aufrufen, da select sie an Ort und Stelle auf "destruktive" Weise modifiziert. (Umfrage unterscheidet zwischen dem .events und .revents Mitglieder in struct pollfd ).

Nach der Auswahl wird oft die gesamte Bitmap (durch Personen/Code) nach Ereignissen durchsucht, selbst wenn die meisten FDS nicht einmal angesehen werden.

Drittens kann die Bitmap nur mit fds umgehen, deren Anzahl kleiner als eine bestimmte Grenze ist (zeitgenössische Implementierungen:irgendwo zwischen 1024..4096), was sie in Programmen ausschließt, in denen hohe fds leicht erreicht werden können (ungeachtet dessen, dass solche Programme dies wahrscheinlich tun verwende stattdessen bereits epoll).


Ich würde vorschlagen, den Vergleich mit select() zu beginnen gegenüber poll() . Linux bietet auch beides pselect() und ppoll(); und die zusätzliche const sigset_t * Argument zu pselect() und ppoll() (gegenüber select() und poll() ) wirkt sich sozusagen auf jede "p-Variante" gleich aus. Wenn Sie keine Signale verwenden, müssen Sie sich vor keinem Rennen schützen, also dreht sich die grundlegende Frage wirklich um Effizienz und einfache Programmierung.

Mittlerweile gibt es hier schon eine Antwort von stackoverflow.com:was sind die Unterschiede zwischen poll und select.

Was das Rennen angeht:Sobald Sie beginnen, Signale zu verwenden (aus welchen Gründen auch immer), werden Sie lernen, dass ein Signal-Handler im Allgemeinen nur eine Variable vom Typ volatile sig_atomic_t setzen sollte um anzuzeigen, dass das Signal erkannt wurde. Der grundlegende Grund dafür ist, dass viele Bibliotheksaufrufe nicht wiedereintrittsfähig sind und ein Signal geliefert werden kann, während Sie „mitten in“ einer solchen Routine sind. Zum Beispiel einfach eine Nachricht in eine Datenstruktur im Stream-Stil wie stdout drucken (C) oder cout (C++) kann zu Wiedereintrittsproblemen führen.

Angenommen, Sie haben Code, der einen volatile sig_atomic_t flag verwendet Variable, vielleicht um SIGINT abzufangen , etwa so (siehe auch 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 ...
    ...

Nun, im Hauptteil Ihres Codes, möchten Sie vielleicht "bis zur Unterbrechung ausführen":

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

Dies ist in Ordnung, bis Sie Aufrufe tätigen müssen, die auf eine Eingabe/Ausgabe warten, wie z. B. select oder poll . Die „Warten“-Aktion muss auf diese E/A warten – aber auch muss auf SIGINT warten unterbrechen. Wenn Sie einfach schreiben:

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

dann ist es möglich, dass der Interrupt kurz davor erfolgt Sie rufen select() an oder poll() , eher als danach. In diesem Fall wurden Sie unterbrochen – und die Variable got_interrupted wird fertig – aber danach fängt man an zu warten. Sie sollten got_interrupted überprüft haben Variable bevor Sie angefangen haben zu warten, nicht danach.

Sie können versuchen, Folgendes zu schreiben:

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

Dadurch wird das "Rennfenster" verkleinert, da Sie jetzt den Interrupt erkennen, wenn er auftritt, während Sie sich im Code "do some work" befinden; aber es gibt immer noch ein Rennen, weil der Interrupt direkt danach passieren kann Sie testen die Variable, aber direkt davor die Auswahl-oder-Abfrage.

Die Lösung besteht darin, die „test, then wait“-Sequenz „atomar“ zu machen, indem die signalblockierenden Eigenschaften von sigprocmask verwendet werden (oder in POSIX-Thread-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 */
}

(Der obige Code ist eigentlich nicht so toll, er ist eher zur Veranschaulichung als zur Effizienz strukturiert - es ist effizienter, die Manipulation der Signalmaske etwas anders durchzuführen und die "wurde unterbrochen"-Tests anders zu platzieren).

Bis Sie tatsächlich SIGINT fangen müssen , Sie müssen jedoch nur select() vergleichen und poll() (und wenn Sie anfangen, eine große Anzahl von Deskriptoren zu benötigen, einige der ereignisbasierten Dinge wie epoll() ist effizienter als beide).


Linux
  1. So installieren Sie Erlang auf Rocky Linux/Alma Linux/CentOS 8

  2. Kann unter Linux über Pppoe nicht auf ausgewählte HTTPS-Sites zugreifen?

  3. Linux – Sind verschiedene Linux/Unix-Kernel austauschbar?

  4. Linux-mv-Befehl

  5. Linux-Du-Befehl

W-Befehl unter Linux

Bei Befehl unter Linux

So richten Sie den Nylas N1 E-Mail-Client unter Linux ein und verwenden ihn

So erstellen Sie ein E-Book mit Calibre unter Linux [Vollständige Anleitung]

Linux gegen Unix

WPS Office CSV-Trennzeichen – Linux