Heim  >  Artikel  >  System-Tutorial  >  Detaillierte Erläuterung des RCU-Mechanismus im Linux-Kernel

Detaillierte Erläuterung des RCU-Mechanismus im Linux-Kernel

WBOY
WBOYnach vorne
2024-02-10 21:09:111350Durchsuche

Der Linux-Kernel ist ein komplexes System, das sich mit einer Vielzahl von Parallelitätsproblemen befassen muss, wie z. B. Prozessplanung, Speicherverwaltung, Gerätetreiber, Netzwerkprotokolle usw. Um die Konsistenz und Korrektheit der Daten sicherzustellen, bietet der Linux-Kernel verschiedene Synchronisationsmechanismen wie Spin-Locks, Semaphore, Lese-/Schreibsperren usw. Diese Synchronisationsmechanismen weisen jedoch einige Mängel auf, wie zum Beispiel:

Detaillierte Erläuterung des RCU-Mechanismus im Linux-Kernel

  • Spin-Locks führen dazu, dass die CPU viel Zeit mit Warten verschwendet und können nicht in präventiven Kerneln verwendet werden
  • Semaphor führt dazu, dass der Prozess in den Ruhezustand versetzt und wieder aktiviert wird, was den Aufwand für den Kontextwechsel erhöht
  • Lese-/Schreibsperren führen zu einer Schreib- oder Lesesperre, und wenn es mehr Leser als Schreiber gibt, muss der Schreiber auch die Gemeinkosten für den Erwerb der Sperre tragen.

Gibt es also einen besseren Synchronisierungsmechanismus? Die Antwort lautet: Ja, das ist RCU (Read Copy Update). RCU ist ein Synchronisationsmechanismus, der auf dem Publish-Subscribe-Modell basiert und effiziente Lesevorgänge und Aktualisierungsvorgänge mit geringer Latenz erreichen kann. Die Grundidee von RCU ist folgende:

  • Für den Lesevorgang müssen keine Sperren erworben werden. Verwenden Sie einfach rcu_read_lock() und rcu_read_unlock(), um den kritischen Bereich zu markieren Der Aktualisierungsvorgang erfordert zunächst das Erstellen einer Kopie der Daten, das Vornehmen von Änderungen an der Kopie, dann die Verwendung von rcu_assign_pointer() zum Veröffentlichen der neuen Daten und das Verwenden von call_rcu() oder synchronisiert_rcu(), um auf den Abschluss aller Lesevorgänge zu warten, bevor die Daten recycelt werden alte Daten.
  • Was sind die Vorteile von RCU? Es verfügt über folgende Funktionen:

RCU kann die Sperrkonkurrenz und den Kontextwechsel reduzieren und die Parallelitätsleistung und Reaktionsfähigkeit des Systems verbessern
    RCU kann Probleme wie Deadlock und Prioritätsumkehr vermeiden und das Programmiermodell vereinfachen
  • RCU kann sich an verschiedene Szenarien anpassen, z. B. Echtzeitsysteme, NUMA-Systeme, SMP-Systeme usw.;
  • RCU kann in Verbindung mit anderen Synchronisationsmechanismen wie Spin-Locks, atomaren Operationen usw. verwendet werden.
  • Also, wie funktioniert RCU? Welche Anwendungsszenarien gibt es? In diesem Artikel wird der effiziente Synchronisationsmechanismus von RCU unter zwei Aspekten vorgestellt: Prinzip und Anwendung. Die Designidee von RCU ist relativ klar und der sperrenfreie Freigabeschutz wird durch Ersetzen alter und neuer Zeiger erreicht. Aber wenn es um die Codeebene geht, ist es immer noch etwas schwer zu verstehen. In Kapitel 4 von „Ausführlicher Linux-Gerätetreiber-Kernelmechanismus“ wurden die hinter RCU befolgten Regeln sehr klar beschrieben. Diese Regeln werden aus einer relativ hohen Perspektive betrachtet, da ich denke, dass es für den Leser zu einfach ist, sie zu analysieren in den Details verloren. Nachdem ich das Buch vor kurzem erhalten habe, habe ich den Text im RCU-Teil noch einmal sorgfältig gelesen und war der Meinung, dass ich etwas mehr Inhalt hinzufügen sollte, da einige Dinge möglicherweise nicht für die Beschreibung im Buch geeignet sind.
  • Das Zeichen dafür, dass die RCU-Leseseite den kritischen Abschnitt betritt, ist der Aufruf von rcu_read_lock. Der Code dieser Funktion lautet:
    1. 
    2. static inline void rcu_read_lock(void)
    3. {
    4. ​    __rcu_read_lock();
    5. ​    __acquire(RCU);
    6. ​    rcu_read_acquire();
    7. }
    
  • In dieser Implementierung scheint es drei Funktionsaufrufe zu geben, aber die eigentliche Arbeit wird von der ersten Funktion __rcu_read_lock() erledigt, die die Kernel-Präemptivität durch den Aufruf von preempt_disable() deaktiviert. Es wird jedoch davon ausgegangen, dass sich der Leser im kritischen RCU-Bereich befindet und gerade einen Zeiger p im gemeinsam genutzten Datenbereich gelesen hat (aber noch nicht auf die Datenelemente in p zugegriffen hat). ISR muss zufällig den Datenbereich ändern, auf den p zeigt. Gemäß den Entwurfsprinzipien von RCU weist ISR einen neuen Datenbereich new_p derselben Größe zu und kopiert dann die Daten im alten Datenbereich p in den neuen Datenbereich und dann in new_p Führen Sie im Wesentlichen die Datenänderungsarbeit nach dem ISR durch (da sie im new_p-Bereich geändert wird, gibt es keinen gleichzeitigen Zugriff auf p, sodass RCU ein sperrfreier Mechanismus ist, und das ist der Grund). Schließt die Datenaktualisierungsarbeit ab, weist p new_p zu (p = new_p) und registriert schließlich eine Rückruffunktion, um den alten Zeiger p zum richtigen Zeitpunkt freizugeben. Daher gibt es kein Problem, p freizugeben, solange alle Verweise auf den alten Zeiger p beendet sind. Wenn die Interrupt-Verarbeitungsroutine von der Ausführung dieser Aufgaben zurückkehrt, greift der unterbrochene Prozess weiterhin auf die Daten im p-Raum zu, dh auf die alten Daten. Dieses Ergebnis wird vom RCU-Mechanismus zugelassen.
RCU-Regeln ermöglichen vorübergehende Inkonsistenzen in der Ressourcenansicht, die durch Zeigerwechsel zwischen Lesern und Autoren verursacht werden

.

Die nächste interessante Frage zu RCU ist: Wann kann der alte Zeiger freigegeben werden? Die Antwort darauf, die ich in vielen Büchern gesehen habe, lautet: Wenn auf allen Prozessoren im System ein Prozesswechsel stattfindet. Diese stilisierte Antwort verwirrt oft Leser, die mit dem RCU-Mechanismus noch nicht vertraut sind. Warum müssen wir warten, bis auf allen Prozessoren ein Prozesswechsel erfolgt, bevor wir die Rückruffunktion aufrufen, um den alten Zeiger freizugeben? Dies wird tatsächlich durch die Entwurfsregeln von RCU bestimmt: Alle Verweise auf alte Zeiger können nur im kritischen Abschnitt auftreten, der in rcu_read_lock und rcu_read_unlock enthalten ist, und in diesem kritischen Abschnitt ist kein Prozesswechsel möglich. , Sobald der kritische Abschnitt vorhanden ist, sollte dies nicht der Fall sein haben keinen Bezug mehr zum alten Zeiger p. Offensichtlich erfordert diese Regel, dass der Leser den Prozess im kritischen Abschnitt nicht wechseln kann, da bei einem Prozesswechsel die Rückruffunktion aufgerufen werden kann, die den alten Zeiger freigibt, wodurch der alte Zeiger freigegeben wird, wenn der Prozess umgeschaltet wird neu geplant wird, verweist es möglicherweise auf einen freigegebenen Speicherplatz.

Jetzt sehen wir, warum rcu_read_lock nur die Kernel-Präemptivität ausschalten muss, weil es es unmöglich macht, den aktuellen Prozess zu wechseln und zu entfernen, selbst wenn im kritischen Abschnitt ein Interrupt auftritt. Die Kernel-Entwickler, oder besser gesagt die Designer von RCU, können nur eine begrenzte Menge tun. Der nächste Schritt liegt in der Verantwortung des Benutzers. Wenn eine Funktion im kritischen Bereich der RCU aufgerufen wird, schläft die Funktion möglicherweise ein, wodurch die Entwurfsregeln der RCU verletzt werden und das System in einen instabilen Zustand gerät.

Dies zeigt einmal mehr, dass man, wenn man etwas nutzen möchte, seinen inneren Mechanismus verstehen muss. Auch wenn es jetzt keine Probleme mit dem Programm gibt, sind die im System verbliebenen Gefahren wie eine Zeitbombe , was jederzeit passieren kann, insbesondere wenn es lange dauert, bis das Problem plötzlich auftritt. In den meisten Fällen dauert es möglicherweise viel länger, das Problem zu finden, als sich zu beruhigen und die Prinzipien von RCU sorgfältig zu verstehen.

Leser in RCU haben einen höheren Freiheitsgrad als Leser in rwlock. Da der RCU-Leser die Gefühle des Autors beim Zugriff auf eine gemeinsam genutzte Ressource nicht berücksichtigen muss, unterscheidet sich dies vom rwlock-Leser. Der rwlock-Leser muss sicherstellen, dass kein Autor die Ressource bedient, wenn er die gemeinsam genutzte Ressource liest. Der Unterschied zwischen den beiden liegt darin, dass RCU gemeinsame Ressourcen zwischen Lesern und Autoren trennt, während rwlock-Leser und -Autoren von Anfang bis Ende nur eine Kopie der gemeinsam genutzten Ressourcen verwenden . Dies bedeutet auch, dass Autoren in RCU mehr Verantwortung tragen müssen und eine Art gegenseitiger Ausschlussmechanismus zwischen mehreren Autoren eingeführt werden muss, die dieselbe gemeinsam genutzte Ressource aktualisieren, sodass RCU ein „sperrfreier Mechanismus“ ist. Die Aussage ist auf Leser und beschränkt Schriftsteller. Wir sehen also, dass der RCU-Mechanismus in Situationen verwendet werden sollte, in denen es viele Lesevorgänge und relativ wenige Aktualisierungsvorgänge gibt. Zu diesem Zeitpunkt kann RCU die Systemleistung erheblich verbessern, da der Lesevorgang von RCU im Vergleich zu einigen anderen Sperrmechanismen fast keinen Sperraufwand verursacht.

Bei der tatsächlichen Verwendung liegen gemeinsam genutzte Ressourcen häufig in Form verknüpfter Listen vor. Leser und Benutzer sollten diese Kernelfunktionen wie list_add_tail_rcu, list_add_rcu, hlist_replace_rcu usw. verwenden. Informationen zur spezifischen Verwendung finden Sie in einigen Informationen zur Kernel-Programmierung oder zum Gerätetreiber.

In Bezug auf die Veröffentlichung alter Zeiger bietet der Linux-Kernel Benutzern zwei Methoden zur Verwendung: Eine besteht darin, call_rcu aufzurufen, und die andere darin, synchronisiert_rcu aufzurufen. Ersteres ist eine asynchrone Methode. call_rcu fügt die Rückruffunktion, die den alten Zeiger freigibt, in einen Knoten ein und fügt den Knoten dann zur lokalen verknüpften Liste des Prozessors hinzu, der aktuell call_rcu im Softirq-Teil des Taktinterrupts ausführt. , die RCU-Soft-Interrupt-Verarbeitungsfunktion rcu_process_callbacks prüft, ob der aktuelle Prozessor eine Ruhephase durchlaufen hat (Ruhezustand, der die Kernel-Prozessplanung und andere Aspekte umfasst). Die Kernel-Code-Implementierung von RCU bestimmt, ob alle Prozessoren im System eine Ruhephase durchlaufen haben Zeitraum (was bedeutet, dass auf allen Prozessoren ein Prozesswechsel stattgefunden hat, sodass der alte Zeiger zu diesem Zeitpunkt sicher freigegeben werden kann), wird die von call_rcu bereitgestellte Rückruffunktion aufgerufen.
Die Implementierung von synchronisiert_rcu nutzt die Warteschlange. Während der Implementierung wird auch ein Knoten zur lokalen verknüpften Liste des aktuellen Prozessors hinzugefügt. Der Unterschied zu call_rcu besteht darin, dass die Rückruffunktion in diesem Knoten wakeme_after_rcu ist und dann synchronisiert_rcu bleibt in einer Warteschlange, bis ein Prozesswechsel auf allen Prozessoren im System erfolgt, daher wird wakeme_after_rcu von rcu_process_callbacks aufgerufen, um den schlafenden synchronisiert_rcu aufzuwecken. Nach dem Aufwecken weiß synchronisiert_rcu, dass es nun den alten Zeiger freigeben kann.

Wir sehen also, dass die registrierte Rückruffunktion nach der Rückkehr von call_rcu möglicherweise nicht aufgerufen wurde, was bedeutet, dass der alte Zeiger nicht freigegeben wurde und der alte Zeiger nach der Rückkehr von synchronisiert_rcu freigegeben worden sein muss. Ob call_rcu oder synchronisiert_rcu aufgerufen werden soll, hängt daher von den spezifischen Anforderungen und dem aktuellen Kontext ab. Beispielsweise kann die Funktion synchronisiert_rcu nicht im Kontext der Interrupt-Verarbeitung verwendet werden.

In diesem Artikel wird RCU vorgestellt, ein effizienter Synchronisierungsmechanismus im Linux-Kernel. Es handelt sich um einen Synchronisierungsmechanismus, der auf dem Publish-Subscribe-Modell basiert. Wir haben die Grundideen, Schlüsselschnittstellen und Implementierungsdetails von RCU unter prinzipiellen Aspekten analysiert und entsprechende Codebeispiele gegeben. Wir haben auch die Verwendung von RCU in verknüpften Listenoperationen, Timer-Management, Soft-Interrupt-Verarbeitung und anderen Szenarien aus Anwendungssicht vorgestellt und entsprechende Codebeispiele gegeben. Durch das Studium dieses Artikels können wir die grundlegende Verwendung von RCU beherrschen und RCU in der tatsächlichen Entwicklung flexibel verwenden, um effiziente Synchronisierungsanforderungen zu erreichen. Ich hoffe, dieser Artikel ist hilfreich für Sie!

Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung des RCU-Mechanismus im Linux-Kernel. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:lxlinux.net. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen