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

UNIX-Kernel:Reentrante Kernel, Synchronisierung und kritische Abschnitte

Dieser Artikel ist Teil unserer laufenden UNIX-Kernel-Übersichtsserie.

Im vorherigen Artikel dieser Serie haben wir über die UNIX-Prozessübersicht gesprochen.

Dieser Artikel erklärt auf hohem Niveau über reentrante Kernel, Synchronisation und kritische Abschnitte der UNIX-Kernelarchitektur.

Wiedereintretende Kernel

Wie der Name schon sagt, ist ein reentranter Kernel derjenige, der es mehreren Prozessen ermöglicht, zu jedem beliebigen Zeitpunkt im Kernelmodus ausgeführt zu werden, und das auch, ohne Konsistenzprobleme zwischen den Kernel-Datenstrukturen zu verursachen.

Nun, wir wissen, dass in einem Einprozessorsystem nur ein Prozess zu einem bestimmten Zeitpunkt ausgeführt werden kann, aber es könnten andere Prozesse im Kernelmodus blockiert sein, die darauf warten, ausgeführt zu werden.

In einem reentranten Kernel kann beispielsweise ein Prozess, der auf einen „read()“-Aufruf wartet, entscheiden, die CPU für einen Prozess freizugeben, der im Kernelmodus auf die Ausführung wartet.

Nun stellt sich vielleicht die Frage, warum ein Kernel reentrant gemacht wird? Nun, fangen wir mit einem Beispiel an, in dem ein Kernel nicht wiedereintrittsfähig ist, und sehen wir uns an, was passiert, wenn er mehreren Prozessen erlaubt, im Kernelmodus ausgeführt zu werden.

Nehmen wir an, dass ein Prozess im Kernel-Modus ausgeführt wird und auf eine Kernel-Datenstruktur und einige damit verbundene globale Werte zugreift.

  • Angenommen, der Prozessname ist „A“.
  • Jetzt greift 'A' auf eine globale Variable zu, um zu sehen, ob der Wert nicht Null ist (damit es einige Berechnungen usw. durchführen kann) und kurz bevor es versucht, diesen Wert in einigen seiner Logiken zu verwenden, wird ein Kontextwechsel zur Verarbeitung von ' B' passiert.
  • Jetzt versucht dieser Prozess „B“, auf den Wert derselben globalen Variablen zuzugreifen und ihn zu dekrementieren.
  • Ein weiterer Kontextwechsel findet statt und Prozess „A“ wird wieder ausgeführt.
  • Da ‚A‘ nicht weiß, dass ‚B‘ den Wert bereits dekrementiert hat, versucht es, diesen Wert erneut zu verwenden.
  • Hier ist also der Haken, Prozess „A“ sieht zwei unterschiedliche Werte der globalen Variablen, da der Wert von einem anderen Prozess „B“ geändert wurde.

Jetzt wissen wir also, warum ein Kernel reentrant sein muss. Eine weitere Frage, die aufkommen kann, ist, wie man einen Kernel reentrant macht?

Grundsätzlich könnten die folgenden Punkte in Betracht gezogen werden, um einen Kernel reentrant zu machen:

  • Schreiben Sie Kernelfunktionen, die nur die lokalen (Stapel-)Variablen ändern und nicht die globalen Variablen oder Datenstrukturen ändern. Solche Funktionen werden auch als reentrante Funktionen bezeichnet.
  • Das strikte Festhalten an der Verwendung von nur reentranten Funktionen in einem Kernel ist keine praktikable Lösung. Eine weitere verwendete Technik sind „Sperrmechanismen“, die sicherstellen, dass nur ein Prozess zu einem bestimmten Zeitpunkt eine nicht wiedereintrittsfähige Funktion verwenden kann.

Aus den obigen Punkten ist klar, dass die Verwendung von reentranten Funktionen und Sperrmechanismen für nicht-reentrante Funktionen der Kern ist, um einen Kernel reentrant zu machen. Da das Implementieren von reentranten Funktionen mehr mit guter Programmierung zu tun hat, hängen die Sperrmechanismen mit dem Konzept der Synchronisation zusammen.

Synchronisation und kritische Abschnitte

Wie oben in einem Beispiel besprochen, benötigt ein reentranter Kernel synchronisierten Zugriff auf globale Kernel-Variablen und Datenstrukturen.

Der Codeabschnitt, der mit diesen globalen Variablen und Datenstrukturen arbeitet, wird als kritischer Abschnitt bezeichnet.

Wenn ein Kernel-Steuerpfad aufgrund eines Kontextwechsels ausgesetzt wird (während er einen globalen Wert oder eine globale Datenstruktur verwendet), sollte kein anderer Steuerpfad in der Lage sein, auf denselben globalen Wert oder dieselbe Datenstruktur zuzugreifen. Sonst könnte es verheerende Auswirkungen haben.

Wenn wir zurückblicken und sehen, warum wir Synchronisation brauchen? Die Antwort ist die sichere Verwendung globaler Kernelvariablen und Datenstrukturen. Nun, dies kann auch durch atomare Operationen erreicht werden. Eine atomare Operation ist eine Operation, die immer ausgeführt wird, ohne dass ein anderer Prozess in der Lage ist, den während der Operation gelesenen oder geänderten Zustand zu lesen oder zu ändern. Leider können atomare Operationen nicht überall angewendet werden. Beispielsweise kann das Entfernen eines Elements aus einer verketteten Liste innerhalb des Kernels nicht zu einer atomaren Operation gemacht werden.

Konzentrieren wir uns nun wieder darauf, wie Kernel-Steuerpfade synchronisiert werden.

Kernel-Preemption-Deaktivierung

Kernel-Preemption ist ein Konzept, bei dem der Kernel die zwangsweise Aussetzung/Unterbrechung einer Aufgabe zulässt und eine andere Aufgabe mit hoher Priorität, die auf Kernel-Ressourcen gewartet hat, zur Ausführung bringt.

Einfacher ausgedrückt ist es ein Kontextwechsel von Prozessen im Kernelmodus, bei dem der laufende Prozess vom Kernel zwangsweise angehalten und der andere Prozess zur Ausführung gebracht wird.

Wenn wir uns an die Definition halten, stellen wir fest, dass es genau diese Fähigkeit des Kernels ist (vorzubeugen, wenn sich Prozesse im Kernelmodus befinden), die Synchronisierungsprobleme verursacht. Eine Lösung für das Problem besteht darin, die Kernel-Preemption zu deaktivieren. Dadurch wird sichergestellt, dass der Kontextwechsel im Kernelmodus nur dann erfolgt, wenn ein Prozess, der gerade im Kernelmodus ausgeführt wird, freiwillig die CPU freigibt und sicherstellt, dass alle Kerneldatenstrukturen und globalen Variablen in einem konsistenten Zustand sind.

Das eindeutige Deaktivieren der Kernel-Preemption ist keine sehr elegante Lösung, und diese Lösung versagt, wenn wir Multiprozessorsysteme verwenden, da zwei CPUs gleichzeitig auf denselben kritischen Abschnitt zugreifen können.

Unterbrechungsdeaktivierung

Ein weiterer Mechanismus, der angewendet werden kann, um eine Synchronisation innerhalb des Kernels zu erreichen, besteht darin, dass ein Prozess alle Hardware-Interrupts deaktiviert, bevor er in eine kritische Region eintritt, und sie aktiviert, nachdem er diese sehr kritische Region verlassen hat. Auch diese Lösung ist keine elegante Lösung, da in Fällen, in denen der kritische Bereich groß ist, Interrupts für sehr lange Zeit deaktiviert werden können, was ihren eigentlichen Zweck, ein Interrupt zu sein, zunichte macht und dazu führen kann, dass Hardwareaktivitäten einfrieren.

Semaphoren

Dies ist eine sehr beliebte Methode, um eine Synchronisation innerhalb des Kernels bereitzustellen.

Es ist sowohl auf Einprozessor- als auch auf Mehrprozessorsystemen wirksam. Gemäß diesem Konzept kann ein Semaphor als Zähler betrachtet werden, der jeder Datenstruktur zugeordnet ist und von allen Kernel-Threads überprüft wird, wenn sie versuchen, auf diese bestimmte Datenstruktur zuzugreifen.

Ein Semaphor enthält Informationen über den Zählerwert, eine Liste von Prozessen, die darauf warten, das Semaphor zu erwerben (um auf die Datenstruktur zuzugreifen) und zwei Methoden, um den Wert des mit diesem Semaphor verbundenen Zählers zu erhöhen oder zu verringern.

Die Arbeitslogik ist wie folgt:

  • Angenommen, ein Prozess möchte auf eine bestimmte Datenstruktur zugreifen, überprüft er zuerst den Zähler, der dem Semaphor der Datenstruktur zugeordnet ist.
  • Wenn der Zähler positiv ist, wird der Prozess Semaphore erwerben, den Wert des Zählers verringern, den kritischen Bereich ausführen und den Semaphore-Zähler erhöhen.
  • Aber wenn ein Prozess den Wert des Zählers als Null findet, dann wird der Prozess zu der Liste (der Semaphore zugeordnet) von Prozessen hinzugefügt, die darauf warten, die Semaphore zu erwerben.
  • Wenn nun der Zähler positiv wird, versuchen alle Prozesse, die auf die Semaphore warten, diese zu erwerben.
  • Derjenige, der erneut erfasst, verringert den Zähler, führt den kritischen Bereich aus und erhöht dann den Zähler wieder, während die anderen Prozesse in den Wartemodus zurückkehren.

Deadlocks vermeiden

Die Arbeit mit einem Synchronisationsschema wie Semaphores hat einen Nebeneffekt von „Deadlocks“.

Nehmen wir ein Beispiel:

  • Angenommen, ein Prozess A erwirbt ein Semaphor für eine bestimmte Datenstruktur, während Prozess B ein Semaphor für eine andere Datenstruktur erwirbt.
  • Im nächsten Schritt wollen nun beide Prozesse Semaphore für die Datenstrukturen erwerben, die voneinander erworben werden, dh Prozess A möchte Semaphore erwerben, die bereits von Prozess B erworben wurde, und umgekehrt.
  • Diese Art von Situation, in der ein Prozess darauf wartet, dass ein anderer Prozess eine Ressource freigibt, während der andere darauf wartet, dass der erste eine Ressource freigibt, wird als Deadlock bezeichnet.
  • Deadlocks können ein vollständiges Einfrieren der Kernel-Kontrollpfade verursachen.

Diese Art von Deadlocks tritt häufiger in Designs auf, in denen eine große Anzahl von Kernel-Locks verwendet werden. Bei diesen Entwürfen wird es äußerst schwierig festzustellen, dass niemals ein Deadlock-Zustand auftreten würde. In Betriebssystemen wie Linux werden Deadlocks vermieden, indem sie der Reihe nach erworben werden.


Linux
  1. Benutzerdefinierte Kernel in Ubuntu/Debian – wie, wann und warum

  2. Benutzer- und Kernelzeit eines laufenden Prozesses abrufen?

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

  4. Ukuu Kernel Manager – Installieren und aktualisieren Sie Linux-Kernel in Ubuntu

  5. Kernel-Stack und User-Space-Stack

So installieren und verwalten Sie mehrere Kernel unter Arch Linux

Was ist der Unterschied zwischen Linux und Unix?

Linux-Kernel und seine Funktionen

So installieren Sie Rclone unter Linux und Unix

So konfigurieren Sie eine statische IP-Adresse in Linux und Unix

Unix- und Linux-Geschichte