epoll
. Hier werden wir darüber sprechen, wie epoll
Ereignisse vom Kernel-Space in den User-Space übertragen werden und wie die Trigger-Modi Edge und Level implementiert werden.
Dieser Artikel wurde später als die anderen geschrieben. Als ich anfing, an dem ersten Material zu arbeiten, war der letzte stabile Linux-Kernel 3.16.1. Und zum Zeitpunkt dieses Schreibens ist dies bereits Version 4.1. Dieser Artikel basiert auf dem Code dieser Kernelversion. Der Code hat sich jedoch nicht sehr geändert, sodass sich die Leser der vorherigen Artikel möglicherweise keine Sorgen machen, dass sich etwas in der Implementierung stark geändert hat.
epoll
Interaktion mit dem Benutzerraum
In den vorherigen Artikeln habe ich viel Zeit damit verbracht, zu erklären, wie das Ereignisbehandlungssystem im Kernel funktioniert. Wie Sie wissen, muss der Kernel Informationen zu Ereignissen an ein Programm übergeben, das im Benutzerbereich ausgeführt wird, damit das Programm diese Informationen verwenden kann. Dies erfolgt hauptsächlich mit dem Systemaufruf epoll_wait (2) .
Der Code für diese Funktion befindet sich in Zeile 1961 der Datei
fs/eventpoll.c
. Die Funktion selbst ist sehr einfach. Nach ganz normalen Überprüfungen erhält es einfach den Zeiger eventpoll
vom Dateideskriptor und ruft die folgende Funktion auf:
error = ep_poll(ep, events, maxevents, timeout);
Ep_poll () Funktion
Die Funktion wird
ep_poll()
in Zeile 1585 derselben Datei deklariert. Zunächst wird überprüft, ob der Benutzer einen Wert festgelegt hat timeout
. In diesem Fall initialisiert die Funktion die Warteschlange und setzt das Zeitlimit auf den vom Benutzer angegebenen Wert. Wenn der Benutzer nicht warten möchte, , timeout = 0
geht die Funktion sofort zum Codeblock mit einer Beschriftung check_events:
, die für das Kopieren des Ereignisses verantwortlich ist.
Wenn der Benutzer einen Wert angegeben
timeout
hat und keine Ereignisse an ihn gemeldet werden können (ihre Anwesenheit wird durch einen Aufruf ermittelt ep_events_available(ep)
), ep_poll()
fügt sich die Funktion selbst in die Warteschlange ein ep->wq
(denken Sie daran, worüber wir im dritten Artikel dieser Serie gesprochen haben). Dort haben wir erwähnt, dass dabei ep_poll_callback()
alle Prozesse aktiviert werden, die in der Warteschlange warten.ep->wq
...
Die Funktion geht dann durch Aufruf in den Standby-Modus
schedule_hrtimeout_range()
. Hier sind die Umstände, unter denen ein "Schlaf" -Prozess "aufwachen" kann:
- Das Timeout ist abgelaufen.
- Der Prozess hat ein Signal empfangen.
- Ein neues Ereignis ist aufgetreten.
- Es ist nichts passiert, und der Planer hat nur beschlossen, den Prozess zu aktivieren.
In den Szenarien 1, 2 und 3 setzt die Funktion die entsprechenden Flags und verlässt die Warteschleife. Im letzteren Fall wechselt die Funktion einfach wieder in den Standby-Modus.
Nachdem dieser Teil der Arbeit erledigt ist, wird
ep_poll()
der Blockcode weiter ausgeführt check_events:
.
In diesem Block wird zuerst das Vorhandensein von Ereignissen überprüft und dann der nächste Anruf getätigt, bei dem das Interessanteste passiert.
ep_send_events(ep, events, maxevents)
In
ep_send_events()
Zeile 1546 deklarierte Funktion. Nach dem Aufruf wird die Funktion aufgerufen und ep_scan_ready_list()
ein Rückruf übergeben ep_send_events_proc()
. Die Funktion ep_scan_ready_list()
durchläuft die Liste der Bereitschaftsdateideskriptoren und ruft ep_send_events_proc()
jedes gefundene Bereitschaftsereignis auf. Im Folgenden wird deutlich, dass ein Mechanismus erforderlich ist, bei dem ein Rückruf verwendet wird, um die Sicherheit und die Wiederverwendung von Code zu gewährleisten.
Die Funktion
ep_send_events()
fügt zunächst Daten aus der Liste der vorgefertigten Dateideskriptoren der Struktur eventpool
in ihre lokale Variable ein. Anschließend wird das ovflist
Strukturfeld eventpool
auf gesetzt NULL
(und die Standardeinstellung ist EP_UNACTIVE_PTR
).
Warum Autoren
epoll
Einsatzovflist
? Dies geschieht, um eine hohe Effizienz zu gewährleisten epoll
! Sie können feststellen , dass , nachdem die Liste des bereit Filedeskriptoren wird von der Struktur genommen wurde eventpool
, wird ep_scan_ready_list()
eingestellt ovflist
auf NULL
. Dies führt dazu, dass ep_poll_callback()
nicht versucht wird, ein Ereignis anzuhängen, das an den Benutzerbereich zurückgegeben wird ep->rdllist
, was zu großen Problemen führen kann. Durch die Verwendung der ovflist
Funktion muss beim Kopieren von Ereignissen in den Benutzerbereich ep_scan_ready_list()
keine Sperre ep->lock
gedrückt werden. Dadurch wird die Gesamtleistung der Lösung verbessert.
Danach
ep_send_events_proc()
wird die Liste der vorhandenen Dateideskriptoren umgangen und ihre Methoden erneut aufgerufen.poll()
um sicherzustellen, dass das Ereignis wirklich passiert ist. Warum epoll
Ereignisse hier noch einmal überprüfen? Dies wird durchgeführt, um sicherzustellen, dass das vom Benutzer registrierte Ereignis (oder die vom Benutzer registrierten Ereignisse) weiterhin verfügbar ist. Stellen Sie sich eine Situation vor, in der ein Dateideskriptor nach Ereignis EPOLLOUT
zur Liste der Dateideskriptoren hinzugefügt wurde, während das Benutzerprogramm in diesen Deskriptor schreibt. Nachdem das Programm den Schreibvorgang abgeschlossen hat, kann der Dateideskriptor möglicherweise nicht mehr geschrieben werden. Epoll
Sie müssen mit solchen Situationen richtig umgehen. Andernfalls erhält der Benutzer EPOLLOUT
in dem Moment, in dem der Schreibvorgang blockiert ist.
Hier ist jedoch ein Detail zu erwähnen. Funktion
ep_send_events_proc()
unternimmt alle Anstrengungen, um sicherzustellen, dass User-Space-Programme genaue Ereignisbenachrichtigungen erhalten. Es ist möglich, aber unwahrscheinlich, dass sich die Verfügbarkeit einer Reihe von Ereignissen nach dem ep_send_events_proc()
Auslöser ändert poll()
. In diesem Fall erhält ein User Space-Programm möglicherweise eine Benachrichtigung über ein Ereignis, das nicht mehr vorhanden ist. Aus diesem Grund wird es als richtig angesehen, bei der Anwendung immer nicht blockierende Sockets zu verwenden epoll
. Dies verhindert, dass Ihre Anwendung unerwartet blockiert wird.
Nach dem Überprüfen der Ereignismaske wird die Ereignisstruktur
ep_send_events_proc()
einfach in den vom User-Space-Programm bereitgestellten Puffer kopiert.
Flankengetriggert und pegelgetriggert
Jetzt können wir endlich den Unterschied zwischen Edge Triggering (ET) und Level Triggering (LT) hinsichtlich ihrer Implementierung diskutieren.
else if (!(epi->event.events & EPOLLET)) {
list_add_tail(&epi->rdllink, &ep->rdllist);
}
Es ist sehr leicht! Die Funktion
ep_send_events_proc()
fügt das Ereignis wieder zur Liste der bereitstehenden Dateideskriptoren hinzu. Infolgedessen wird beim nächsten Aufruf ep_poll()
derselbe Dateideskriptor erneut überprüft. Da ep_send_events_proc()
eine Datei immer aufgerufen wird, poll()
bevor sie an die User Space-Anwendung zurückgegeben wird, erhöht sich der Systemaufwand geringfügig (im Vergleich zu ET
), wenn der Dateideskriptor nicht mehr verfügbar ist. Bei alledem geht es jedoch darum, wie oben erwähnt, keine Ereignisse mehr zu melden, die nicht mehr verfügbar sind.
Nach
ep_send_events_proc()
Abschluss des Kopiervorgangs gibt die Funktion die Anzahl der darauf kopierten Ereignisse zurück und hält die User-Space-Anwendung auf dem neuesten Stand.
Wenn die Funktion
ep_send_events_proc()
beendet ist, werden die Funktionenep_scan_ready_list()
müssen ein wenig aufräumen. Zunächst werden die Ereignisse, die von der Funktion nicht verarbeitet wurden, in die Liste der fertigen Dateideskriptoren zurückgeführt ep_send_events_proc()
. Dies kann passieren, wenn die Anzahl der verfügbaren Ereignisse die Größe des vom Benutzerprogramm bereitgestellten Puffers überschreitet. Außerdem werden ep_send_events_proc()
alle Ereignisse ovflist
, falls vorhanden, schnell wieder an die Liste der bereitstehenden Dateideskriptoren angehängt. Weiterhin wird in ovflist
erneut aufgezeichnet EP_UNACTIVE_PTR
. Infolgedessen werden neue Ereignisse an die Hauptwarteliste angehängt ( rdllist
). Die Funktion wird beendet, indem andere "Schlaf" -Prozesse aktiviert werden, falls andere Ereignisse verfügbar sind.
Ergebnis
Damit ist der vierte und letzte Artikel der Implementierungsreihe abgeschlossen
epoll
. Während ich diese Artikel schreibe, war ich beeindruckt von der enormen mentalen Arbeit, die die Autoren des Linux-Kernel-Codes geleistet haben, um maximale Effizienz und Skalierbarkeit zu erreichen. Und ich bin allen Autoren von Linux-Code dankbar, dass sie ihr Wissen mit allen teilen, die es benötigen, indem sie die Ergebnisse ihrer Arbeit teilen.
Wie stehen Sie zu Open Source Software?