Was ist ein Spinlock unter Linux?


32

Ich würde gerne mehr über Linux Spinlocks erfahren. könnte mir jemand erklären

Antworten:


34

Eine Drehsperre schützt eine gemeinsam genutzte Ressource davor, von zwei oder mehr Prozessen gleichzeitig geändert zu werden. Der erste Prozess, der versucht, die Ressource zu ändern, "erwirbt" die Sperre und setzt ihren Weg fort, indem er mit der Ressource das tut, was erforderlich ist. Alle anderen Prozesse, die anschließend versuchen, die Sperre abzurufen, werden angehalten. Man sagt, sie "drehen sich an Ort und Stelle" und warten darauf, dass die Sperre durch den ersten Prozess freigegeben wird, daher der Name Spin Lock.

Der Linux-Kernel verwendet Spin-Locks für viele Dinge, beispielsweise beim Senden von Daten an ein bestimmtes Peripheriegerät. Die meisten Hardware-Peripheriegeräte sind nicht für die gleichzeitige Aktualisierung mehrerer Status ausgelegt. Wenn zwei verschiedene Modifikationen vorgenommen werden müssen, muss eine genau der anderen folgen, sie dürfen sich nicht überschneiden. Eine Drehsperre bietet den erforderlichen Schutz und stellt sicher, dass die Änderungen nacheinander durchgeführt werden.

Spin-Locks sind ein Problem, da das Drehen den CPU-Kern des Threads daran hindert, andere Arbeiten auszuführen. Während der Linux-Kernel Multitasking-Dienste für User-Space-Programme bereitstellt, die unter ihm ausgeführt werden, erstreckt sich diese universelle Multitasking-Funktion nicht auf Kernel-Code.

Diese Situation ändert sich und war für den größten Teil der Existenz von Linux von Bedeutung. Bis Linux 2.0 war der Kernel fast ein reines Single-Tasking-Programm: Wenn auf der CPU Kernel-Code ausgeführt wurde, wurde nur ein CPU-Kern verwendet, da alle gemeinsam genutzten Ressourcen durch eine einzige Drehsperre geschützt wurden, die als Big Kernel Lock (BKL) bezeichnet wurde ). Ab Linux 2.2 wird die BKL langsam in viele unabhängige Sperren aufgeteilt, die jeweils eine stärker fokussierte Ressourcenklasse schützen. Heute, mit Kernel 2.6, existiert die BKL immer noch, aber sie wird nur von wirklich altem Code verwendet, der nicht ohne Weiteres in eine granularere Sperre verschoben werden kann. Es ist jetzt durchaus möglich, dass auf einer Multicore-Box auf jeder CPU nützlicher Kernel-Code ausgeführt wird.

Es gibt eine Grenze für den Nutzen der Aufteilung der BKL, da dem Linux-Kernel das allgemeine Multitasking fehlt. Wenn ein CPU-Kern blockiert wird, während er sich auf einem Kernel-Spin-Lock dreht, kann er nicht wiederholt werden, um etwas anderes zu tun, bis die Sperre aufgehoben wird. Es sitzt und dreht sich nur, bis die Sperre freigegeben wird.

Spin-Locks können eine Monster-16-Core-Box effektiv in eine Single-Core-Box verwandeln, wenn die Arbeitslast so ist, dass jeder Core immer auf eine einzelne Spin-Lock wartet. Dies ist die Hauptbeschränkung für die Skalierbarkeit des Linux-Kernels: Die Verdoppelung der CPU-Kerne von 2 auf 4 wird wahrscheinlich die Geschwindigkeit einer Linux-Box fast verdoppeln, aber die Verdoppelung von 16 auf 32 wird es bei den meisten Workloads wahrscheinlich nicht tun.


@Warren: Ein paar Zweifel - ich würde gerne mehr über dieses Big Kernel Lock und seine Auswirkungen erfahren. Ich verstehe auch nicht den letzten Absatz "Das Verdoppeln von CPU-Kernen von 2 auf 4 wird wahrscheinlich die Geschwindigkeit einer Linux-Box fast verdoppeln, aber das Verdoppeln von 16 auf 32 wird es wahrscheinlich nicht"
Sen

2
Betreff: Implikationen von BKL: Ich dachte, ich hätte das oben klargestellt. Mit nur einer Sperre im Kernel wird jedes Mal, wenn zwei Kerne versuchen, etwas zu tun, das durch die BKL geschützt ist, ein Kern blockiert, während der erste mit seiner geschützten Ressource fertig ist. Je feiner die Sperrung ist, desto geringer ist die Wahrscheinlichkeit, dass dies geschieht, und desto größer ist die Prozessorauslastung.
Warren Young

2
Betreff: Verdoppeln: Ich meine, dass es ein Gesetz gibt, das die Renditen verringert, wenn einem Computer Prozessorkerne hinzugefügt werden. Mit der Anzahl der Kerne steigt auch die Wahrscheinlichkeit, dass zwei oder mehr Kerne auf eine durch eine bestimmte Sperre geschützte Ressource zugreifen müssen. Wenn Sie die Granularität der Sperren erhöhen, wird die Wahrscheinlichkeit solcher Kollisionen verringert, aber das Hinzufügen von zu vielen ist ebenfalls mit einem Mehraufwand verbunden. Sie können dies leicht an Supercomputern erkennen, die heutzutage oft Tausende von Prozessoren haben: Die meisten Workloads sind ineffizient, da sie nicht vermeiden können, dass viele Prozessoren aufgrund von Konflikten mit gemeinsam genutzten Ressourcen inaktiv sind.
Warren Young

1
Dies ist zwar eine interessante Erklärung (+1 für das), aber ich denke nicht, dass sie den Unterschied zwischen Spinlocks und anderen Arten von Sperren verdeutlicht.
Gilles 'SO- hör auf böse zu sein'

2
Wenn man den Unterschied zwischen einem Spinlock und beispielsweise einem Semaphor kennen will, ist das eine andere Frage. Eine weitere gute, aber tangentiale Frage ist, was es mit dem Linux-Kernel-Design auf sich hat, das Spin-Locks zu einer guten Wahl macht, anstatt etwas, das im Userland-Code üblicher ist, wie ein Mutex. Diese Antwort teilt viel wie es ist.
Warren Young

11

Eine Drehsperre ist, wenn ein Prozess kontinuierlich nach einer Sperre fragt, die entfernt werden soll. Es wird als schlecht angesehen, weil der Prozess (normalerweise) unnötig Zyklen verbraucht. Es ist nicht Linux-spezifisch, sondern ein allgemeines Programmiermuster. Und obwohl dies im Allgemeinen als schlechte Praxis angesehen wird, ist es in der Tat die richtige Lösung. Es gibt Fälle, in denen die Kosten für die Verwendung des Schedulers (in Bezug auf die CPU-Zyklen) höher sind als die Kosten für die wenigen Zyklen, von denen erwartet wird, dass der Spinlock von Dauer ist.

Beispiel eines Spinlocks:

#!/bin/sh
#wait for some program to clear a lock before doing stuff
while [ -f /var/run/example.lock ]; do
  sleep 1
done
#do stuff

Es gibt häufig eine Möglichkeit, eine Drehsperre zu vermeiden. Für dieses Beispiel gibt es ein Linux-Tool namens inotifywait (normalerweise nicht standardmäßig installiert). Wenn es in C geschrieben wäre, würden Sie einfach die inotify- API verwenden, die Linux bereitstellt.

Das gleiche Beispiel mit inotifywait zeigt, wie dasselbe ohne Spinlock erreicht werden kann:

#/bin/sh
inotifywait -e delete_self /var/run/example.lock
#do stuff

Welche Rolle spielt der Scheduler dort? Oder hat es irgendeine Rolle?
Sen

1
Bei der Spin-Lock-Methode setzt der Scheduler den Prozess alle ~ 1 Sekunde fort, um seine Aufgabe zu erledigen (dh einfach das Vorhandensein einer Datei zu überprüfen). Im Beispiel inotifywait setzt der Scheduler den Prozess nur dann fort, wenn der untergeordnete Prozess (inotifywait) beendet wird. Inotifywait schläft auch; Der Scheduler nimmt es erst wieder auf, wenn das Ereignis inotify eintritt.
Shawn J. Goff

Wie wird dieses Szenario in einem Single-Core-Prozessorsystem gehandhabt?
Sen

@Sen: Dies wird in Linux-Gerätetreibern recht gut erklärt .
Gilles 'SO- hör auf böse zu sein'

1
Dieses Bash-Skript ist ein schlechtes Beispiel für einen Spinlock. Sie unterbrechen den Vorgang und lassen ihn einschlafen. Ein Spinlock schläft nie. Auf einem Single-Core-Rechner wird der Scheduler nur angehalten und fortgesetzt (ohne Wartezeit)
Martin

7

Wenn ein Thread versucht, eine Sperre zu erlangen, können drei Dinge passieren, wenn es fehlschlägt, es kann versuchen und blockieren, es kann versuchen, fortzufahren, es kann dann schlafen gehen und dem Betriebssystem sagen, es solle aufgeweckt werden, wenn ein Ereignis eintritt.

Jetzt kostet ein Versuch, fortzufahren, viel weniger Zeit als ein Versuch, zu blockieren. Nehmen wir für den Moment an, dass ein "try and continue" eine Zeiteinheit und ein "try and block" eine Hundert benötigt.

Nehmen wir nun für den Moment an, dass ein Thread im Durchschnitt 4 Einheiten Zeit benötigt, um die Sperre zu halten. Es ist verschwenderisch, 100 Einheiten zu warten. Also schreibst du stattdessen eine Schleife von "try and continue". Beim vierten Versuch erwerben Sie normalerweise das Schloss. Dies ist eine Drehsperre. Es heißt so, weil sich der Faden weiter dreht, bis er die Sperre erreicht.

Eine zusätzliche Sicherheitsmaßnahme besteht darin, die Anzahl der Schleifendurchläufe zu begrenzen. Im Beispiel führen Sie also eine for-Schleife aus, z. B. sechsmal. Wenn dies fehlschlägt, "versuchen und blockieren" Sie.

Wenn Sie wissen, dass ein Thread immer die Sperre für beispielsweise 200 Einheiten hält, verschwenden Sie die Computerzeit für jeden Versuch und fahren fort.

Letztendlich kann ein Spin-Lock also sehr effizient oder verschwenderisch sein. Es ist verschwenderisch, wenn die "typische" Zeit zum Halten eines Schlosses länger ist als die Zeit, die zum "Versuchen und Blockieren" benötigt wird. Es ist effizient, wenn die typische Zeit zum Halten eines Schlosses viel kürzer ist als die Zeit zum "Versuchen und Blockieren".

Ps: Das Buch zum Lesen auf Threads ist "A Thread Primer", wenn Sie es noch finden können.


Der Faden dreht sich weiter, bis die Sperre erreicht ist . Könnten Sie mir bitte sagen, was das für eine Dreherei ist? Ist es so, als ob es in die wait_queue kommt und vom Scheduler ausgeführt wird? Vielleicht versuche ich, auf das niedrige Niveau zu kommen, aber ich kann meinen Zweifel immer noch nicht halten.
Sen

Im Grunde genommen zwischen den 3-4 Befehlen in der Schleife drehen.
Paul Stelian

5

Mit einer Sperre können zwei oder mehr Aufgaben (Prozesse, Threads) synchronisiert werden. Insbesondere wenn beide Tasks zeitweise Zugriff auf eine Ressource benötigen, die jeweils nur von einer Task verwendet werden kann, können die Tasks so einrichten, dass die Ressource nicht gleichzeitig verwendet wird. Um auf die Ressource zuzugreifen, muss eine Aufgabe die folgenden Schritte ausführen:

take the lock
use the resource
release the lock

Das Aufnehmen einer Sperre ist nicht möglich, wenn sie bereits von einer anderen Aufgabe übernommen wurde. (Stellen Sie sich die Sperre als ein physisches Token-Objekt vor. Entweder befindet sich das Objekt in einer Schublade oder jemand hat es in der Hand. Nur die Person, die das Objekt hält, darf auf die Ressource zugreifen.) „Sperre aufheben“ bedeutet also wirklich „Warten bis niemand anderes hat das Schloss, dann nimm es “.

Auf hoher Ebene gibt es zwei Hauptmethoden zum Implementieren von Sperren: Spinlocks und Bedingungen. Bei Spinlocks bedeutet das Aufnehmen der Sperre, dass Sie sich nur drehen (dh nichts in einer Schleife tun), bis niemand mehr die Sperre hat. Unter bestimmten Bedingungen tritt der Neueinsteiger in eine Warteschlange ein, wenn eine Task versucht, die Sperre zu übernehmen, diese jedoch blockiert, weil sie von einer anderen Task gehalten wird. Der Freigabevorgang signalisiert jedem wartenden Task, dass das Schloss jetzt verfügbar ist.

(Diese Erklärungen reichen nicht aus, um eine Sperre zu implementieren, da ich nichts über Atomarität gesagt habe. Aber Atomarität ist hier nicht wichtig.)

Spinlocks sind offensichtlich verschwenderisch: Die wartende Task überprüft ständig, ob die Sperre übernommen wurde. Warum und wann wird es verwendet? Spinlocks sind oft sehr billig zu bekommen, wenn das Schloss nicht gehalten wird. Dies macht es attraktiv, wenn die Chance, dass das Schloss gehalten wird, gering ist. Darüber hinaus sind Spinlocks nur dann funktionsfähig, wenn der Erhalt der Sperre nicht lange dauern dürfte. Spinlocks werden daher in der Regel in Situationen verwendet, in denen sie nur sehr kurz gehalten werden, sodass die meisten Versuche beim ersten Versuch erfolgreich sind und diejenigen, die eine Wartezeit benötigen, nicht lange warten.

Spinlocks und andere Parallelitätsmechanismen des Linux-Kernels werden in Linux Device Drivers , Kapitel 5, ausführlich erläutert .


Was wäre ein guter Weg, um andere Synchronisationsprimitive zu implementieren? Nehmen Sie ein Spinlock, prüfen Sie, ob das eigentliche Grundelement von einer anderen Person implementiert wurde, und verwenden Sie dann entweder den Scheduler oder gewähren Sie den Zugriff. Könnten wir den synchronized () - Block in Java als eine Form von Spinlock betrachten und in richtig implementierten Primitiven einfach einen Spinlock verwenden?
Paul Stelian

@PaulStelian Es ist in der Tat üblich, auf diese Weise "langsame" Sperren zu implementieren. Ich kenne nicht genug Java, um diesen Teil zu beantworten, aber ich bezweifle, dass synchronizeddies von einem Spinlock implementiert würde: Ein synchronizedBlock könnte sehr lange laufen. synchronizedist ein Sprachkonstrukt, mit dem Sperren in bestimmten Fällen einfach verwendet werden können, kein Grundelement zum Erstellen größerer Synchronisationsgrundelemente.
Gilles 'SO- hör auf böse zu sein'

3

Ein Spinlock ist eine Sperre, die durch Deaktivieren des Schedulers und möglicherweise durch Interrupts (irqsave-Variante) für den jeweiligen Kern ausgeführt wird, für den die Sperre erworben wurde. Es unterscheidet sich von einem Mutex darin, dass es die Zeitplanung deaktiviert, sodass nur Ihr Thread ausgeführt werden kann, während Spinlock gehalten wird. Mit einem Mutex können andere Threads mit höherer Priorität eingeplant werden, während sie gehalten werden, aber sie können den geschützten Abschnitt nicht gleichzeitig ausführen. Da Spinlocks Multitasking deaktivieren, können Sie keinen Spinlock nehmen und dann einen anderen Code aufrufen, der versucht, einen Mutex zu erhalten. Ihr Code im Spinlock-Bereich darf niemals in den Ruhezustand versetzt werden (der Code wird normalerweise in den Ruhezustand versetzt, wenn er auf einen gesperrten Mutex oder ein leeres Semaphor stößt).

Ein weiterer Unterschied zu einem Mutex besteht darin, dass Threads normalerweise für einen Mutex anstehen, sodass ein Mutex darunter eine Warteschlange hat. Während Spinlock nur sicherstellt, dass kein anderer Thread ausgeführt wird, auch wenn dies erforderlich ist. Daher dürfen Sie niemals einen Spinlock halten, wenn Sie Funktionen außerhalb Ihrer Datei aufrufen, von denen Sie nicht sicher sind, dass sie nicht in den Ruhezustand versetzt werden.

Wenn Sie Ihr Spinlock mit einem Interrupt teilen möchten, müssen Sie die Variante irqsave verwenden. Dies deaktiviert nicht nur den Scheduler, sondern auch die Interrupts. Es macht Sinn, oder? Spinlock funktioniert, indem sichergestellt wird, dass nichts anderes ausgeführt wird. Wenn Sie nicht möchten, dass ein Interrupt ausgeführt wird, deaktivieren Sie ihn und fahren Sie sicher mit dem kritischen Abschnitt fort.

Auf Multicore-Maschinen dreht sich ein Spinlock und wartet auf einen anderen Kern, der das Schloss hält, um es zu lösen. Dieses Drehen findet nur auf Multicore-Maschinen statt, da dies auf Single-Core-Maschinen nicht möglich ist (Sie halten entweder Spinlock und fahren fort oder Sie werden erst ausgeführt, wenn die Sperre aufgehoben wird).

Spinlock ist nicht verschwenderisch, wo es Sinn macht. Bei sehr kleinen kritischen Abschnitten wäre es verschwenderisch, eine Mutex-Task-Warteschlange zuzuweisen, anstatt den Scheduler nur für einige Mikrosekunden anzuhalten, die zum Beenden der wichtigen Arbeit erforderlich sind. Wenn Sie schlafen müssen oder die Sperre für eine Operation halten müssen (die schlafen kann), verwenden Sie einen Mutex. Sperren Sie auf keinen Fall ein Spinlock und versuchen Sie dann, es innerhalb eines Interrupts freizugeben. Während dies funktioniert, wird es wie der Arduino-Mist von while (Flagnotset) sein; Verwenden Sie in diesem Fall ein Semaphor.

Schnappen Sie sich einen Spinlock, wenn Sie einen einfachen gegenseitigen Ausschluss für Blöcke von Speichertransaktionen benötigen. Greifen Sie zu einem Mutex, wenn mehrere Threads unmittelbar vor einer Mutex-Sperre angehalten werden sollen, und wählen Sie dann den Thread mit der höchsten Priorität aus, der fortgesetzt werden soll, wenn der Mutex frei wird und wenn Sie im selben Thread sperren und freigeben. Holen Sie sich ein Semaphor, wenn Sie es in einem Thread oder einem Interrupt veröffentlichen möchten, und nehmen Sie es in einen anderen Thread. Es gibt drei leicht unterschiedliche Möglichkeiten, um gegenseitigen Ausschluss zu gewährleisten, und sie werden für leicht unterschiedliche Zwecke verwendet.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.