Wenn sich ein Prozess im Benutzermodus befindet, kann er jederzeit unterbrochen werden (Wechsel in den Kernelmodus). Wenn der Kernel in den Benutzermodus zurückkehrt, prüft er, ob irgendwelche Signale anstehen (einschließlich derjenigen, die zum Beenden des Prozesses verwendet werden, wie z. B. SIGTERM
und SIGKILL
). Das bedeutet, dass ein Prozess nur beendet werden kann, wenn er in den Benutzermodus zurückkehrt.
Der Grund, warum ein Prozess im Kernelmodus nicht beendet werden kann, ist, dass er möglicherweise die Kernelstrukturen beschädigen könnte, die von allen anderen Prozessen auf derselben Maschine verwendet werden (auf die gleiche Weise kann das Beenden eines Threads möglicherweise Datenstrukturen beschädigen, die von anderen Threads im selben Prozess verwendet werden). .
Wenn der Kernel etwas tun muss, was lange dauern könnte (z. B. auf eine von einem anderen Prozess geschriebene Pipe warten oder darauf warten, dass die Hardware etwas tut), schläft er, indem er sich selbst als schlafend markiert und den Scheduler aufruft, zu einem anderen zu wechseln Prozess (wenn es keinen nicht schlafenden Prozess gibt, wechselt er zu einem "Dummy"-Prozess, der der CPU mitteilt, etwas langsamer zu werden, und in einer Schleife sitzt - der Leerlaufschleife).
Wenn ein Signal an einen schlafenden Prozess gesendet wird, muss er aufgeweckt werden, bevor er in den Benutzerbereich zurückkehrt und somit das anstehende Signal verarbeitet. Hier haben wir den Unterschied zwischen den beiden Hauptschlafarten:
TASK_INTERRUPTIBLE
, der unterbrechbare Schlaf. Wenn eine Task mit diesem Flag gekennzeichnet ist, schläft sie, kann aber durch Signale geweckt werden. Dies bedeutet, dass der Code, der die Aufgabe als schlafend markiert hat, ein mögliches Signal erwartet und nach dem Aufwachen danach sucht und vom Systemaufruf zurückkehrt. Nachdem das Signal verarbeitet wurde, kann der Systemaufruf möglicherweise automatisch neu gestartet werden (und ich werde nicht näher darauf eingehen, wie das funktioniert).TASK_UNINTERRUPTIBLE
, der ununterbrochene Schlaf. Wenn eine Task mit diesem Flag markiert ist, erwartet sie nicht, von irgendetwas anderem als dem, worauf sie wartet, aufgeweckt zu werden, entweder weil sie nicht einfach neu gestartet werden kann oder weil Programme erwarten, dass der Systemaufruf atomar ist. Dies kann auch für bekanntermaßen sehr kurze Schlafzeiten verwendet werden.
TASK_KILLABLE
(erwähnt in dem LWN-Artikel, auf den die Antwort von ddaa verweist) ist eine neue Variante.
Damit ist deine erste Frage beantwortet. Zu Ihrer zweiten Frage:Sie können unterbrechungsfreie Ruhezeiten nicht vermeiden, sie sind eine normale Sache (es passiert zum Beispiel jedes Mal, wenn ein Prozess von/auf die Festplatte liest/schreibt); sie sollten jedoch nur einen Bruchteil einer Sekunde dauern. Wenn sie viel länger dauern, bedeutet dies normalerweise ein Hardwareproblem (oder ein Gerätetreiberproblem, das für den Kernel gleich aussieht), bei dem der Gerätetreiber darauf wartet, dass die Hardware etwas tut, was niemals passieren wird. Es kann auch bedeuten, dass Sie NFS verwenden und der NFS-Server heruntergefahren ist (er wartet auf die Wiederherstellung des Servers; Sie können auch die Option "intr" verwenden, um das Problem zu vermeiden).
Schließlich ist der Grund, warum Sie nicht wiederherstellen können, derselbe Grund, warum der Kernel wartet, bis er in den Benutzermodus zurückkehrt, um ein Signal zu liefern oder den Prozess zu beenden:Es würde möglicherweise die Datenstrukturen des Kernels beschädigen (Code, der auf einen unterbrechbaren Ruhezustand wartet, kann einen Fehler erhalten, der ihm dies mitteilt um zum Benutzerbereich zurückzukehren, wo der Prozess beendet werden kann; Code, der auf einen unterbrechungsfreien Ruhezustand wartet, erwartet keinen Fehler).
Nicht unterbrechbare Prozesse warten normalerweise auf E/A nach einem Seitenfehler.
Bedenken Sie Folgendes:
- Der Thread versucht, auf eine Seite zuzugreifen, die sich nicht im Kern befindet (entweder eine ausführbare Datei, die bei Bedarf geladen wird, eine Seite mit anonymem Speicher, die ausgelagert wurde, oder eine mmap()-Datei, die bei Bedarf geladen wird, was sind ziemlich dasselbe)
- Der Kernel (versucht) ihn jetzt einzuladen
- Der Vorgang kann erst fortgesetzt werden, wenn die Seite verfügbar ist.
Der Prozess/die Aufgabe kann in diesem Zustand nicht unterbrochen werden, da er keine Signale verarbeiten kann; Wenn dies der Fall wäre, würde ein weiterer Seitenfehler auftreten und es wäre wieder dort, wo es war.
Wenn ich „Prozess“ sage, meine ich wirklich „Task“, was unter Linux (2.6) grob übersetzt „Thread“ bedeutet, der einen individuellen „Thread-Gruppen“-Eintrag in /proc
haben kann oder auch nichtIn manchen Fällen kann es zu langen Wartezeiten kommen. Ein typisches Beispiel hierfür wäre, wenn sich die ausführbare oder mmap-Datei auf einem Netzwerkdateisystem befindet, auf dem der Server ausgefallen ist. Wenn die E/A schließlich erfolgreich ist, wird die Aufgabe fortgesetzt. Wenn es schließlich fehlschlägt, erhält die Aufgabe im Allgemeinen einen SIGBUS oder so etwas.
Ein unterbrechungsfreier Prozess ist ein Prozess, der sich zufällig in einem Systemaufruf (Kernel-Funktion) befindet, der nicht durch ein Signal unterbrochen werden kann.
Um zu verstehen, was das bedeutet, müssen Sie das Konzept eines unterbrechbaren Systemaufrufs verstehen. Das klassische Beispiel ist read()
. Dies ist ein Systemaufruf, der lange (Sekunden) dauern kann, da er möglicherweise das Hochfahren einer Festplatte oder das Bewegen von Köpfen beinhalten kann. Während des größten Teils dieser Zeit schläft der Prozess und blockiert die Hardware.
Während der Prozess im Systemaufruf schläft, kann er ein asynchrones Unix-Signal (z. B. SIGTERM) empfangen, dann passiert Folgendes:
- Der Systemaufruf wird vorzeitig beendet und ist so eingerichtet, dass er -EINTR an den Benutzerbereich zurückgibt.
- Der Signalhandler wird ausgeführt.
- Wenn der Prozess noch läuft, erhält er den Rückgabewert vom Systemaufruf und kann denselben Aufruf erneut durchführen.
Das vorzeitige Zurückkehren vom Systemaufruf ermöglicht es dem Benutzerraumcode, sein Verhalten als Reaktion auf das Signal sofort zu ändern. Zum Beispiel sauberes Beenden als Reaktion auf SIGINT oder SIGTERM.
Andererseits dürfen einige Systemaufrufe nicht auf diese Weise unterbrochen werden. Wenn das System aus irgendeinem Grund Stalls aufruft, kann der Prozess auf unbestimmte Zeit in diesem nicht zu beendenden Zustand verbleiben.
LWN veröffentlichte einen netten Artikel, der dieses Thema im Juli berührte.
Um die ursprüngliche Frage zu beantworten:
-
Wie Sie dies verhindern können:Finden Sie heraus, welcher Treiber Ihnen Probleme bereitet, und hören Sie entweder auf, ihn zu verwenden, oder werden Sie ein Kernel-Hacker und beheben Sie ihn.
-
So beenden Sie einen unterbrechungsfreien Prozess ohne Neustart:Bringen Sie den Systemaufruf irgendwie zum Beenden. Häufig ist die effektivste Methode, dies zu tun, ohne den Netzschalter zu betätigen, das Netzkabel zu ziehen. Sie können auch ein Kernel-Hacker werden und den Treiber dazu bringen, TASK_KILLABLE zu verwenden, wie im LWN-Artikel erklärt.
Zu Ihrer 3. Frage:Ich denke, Sie können die unterbrechungsfreien Prozesse beenden, indem Sie sudo kill -HUP 1
ausführen .Init wird neu gestartet, ohne die laufenden Prozesse zu beenden, und nach dem Ausführen waren meine unterbrechungsfreien Prozesse weg.