Warum beendet SIGKILL ein gestopptes Programm nicht (ja)?

8

Ich verwende Ubuntu 14.04 und habe dieses Verhalten, das ich anscheinend nicht verstehen kann:

  1. Führen Sie den yesBefehl aus (in der Standard-Shell: Bash )
  2. Geben Sie ein, CtrlZum anzuhaltenyes
  3. Ausführen jobs. Ausgabe:
    [1]+ Stopped yes
  4. Lauf kill -9 %1um anzuhalten yes. Ausgabe:
    [1]+ Stopped yes
  5. Ausführen jobs. Ausgabe:
    [1]+ Stopped yes

Dies ist unter Ubuntu, 3.16.0-30-genericdas in einer parallelen virtuellen Maschine ausgeführt wird.

Warum hat mein kill -9Befehl den Befehl yes nicht beendet ? Ich dachte, SIGKILL kann nicht gefangen oder ignoriert werden? Und wie kann ich den Befehl yes beenden ?

s1m0n
quelle
1
Das ist interessant. SIGKILL sollte funktionieren und funktioniert unter meinem Linux Mint 17. Für jedes andere Signal müssen Sie es normalerweise SIGCONT anschließend senden, um sicherzustellen, dass das Signal vom gestoppten Ziel empfangen wird.
PSkocik
Gibt bash wirklich "Gestoppt" für einen angehaltenen Prozess aus ?
Edmz
Kernel-Version ( uname -a) bitte
Roaima
Linux ubuntu 3.16.0-30-generic #40~14.04.1-Ubuntu SMP Thu Jan 15 17:43:14 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux. Ich verwende Ubuntu in Parallels Desktop.
s1m0n
1
@schwarz die meisten Muscheln sagen "Gestoppt". tcsh sagt "Suspended" und zsh sagt "suspendiert". Ein kosmetischer Unterschied. Etwas wichtiger ist die Tatsache, dass bash eine identische Nachricht für STOP und TSTP druckt, wobei alle anderen Shells die Annotation der STOP-Nachricht markieren, (signal)damit Sie den Unterschied erkennen können.

Antworten:

10

Signale werden für angehaltene Prozesse blockiert. In einem Terminal:

$ yes
...
y
y
^Zy

[1]+  Stopped                 yes

In einem zweiten Terminal:

$ killall yes

Im ersten Terminal:

$ jobs
[1]+  Stopped                 yes

$ fg
yes
Terminated

Jedoch SIGKILLkann nicht gesperrt werden. Wenn Sie killall -9 yesvom zweiten Terminal aus dasselbe tun, erhalten Sie dies sofort im yesTerminal:

[1]+  Killed                  yes

Wenn kill -9 %1der Prozess nicht sofort beendet bashwird, wird das Signal entweder erst dann gesendet, wenn Sie fgden Prozess abgeschlossen haben, oder Sie haben einen Fehler im Kernel entdeckt.

lcd047
quelle
4
Einige Hintergrunddetails: Wenn Sie Strg + Z in Ihrem Terminal-Bash ausgeben, wird eine SIGTSTP(die blockierbare Version von SIGSTOP) an den aktiven Prozess gesendet . Dies versetzt den Prozess in einen eingefrorenen Zustand, in dem der Kernel ihn nicht plant. SIGCONTDies verhindert auch die Signalverarbeitung (mit Ausnahme des Signals, das den Prozess auftaut) und verhindert daher, dass der Prozess sofort abgebrochen wird.
Mreithub
1
SIGKILL ist im Gegensatz zu anderen Signalen nicht für angehaltene Prozesse blockiert. Das Senden des KILL-Signals an einen angehaltenen Prozess beendet es - asynchron, aber in der Praxis im Grunde sofort.
Gilles 'SO - hör auf böse zu sein'
1
@ Gilles Das habe ich oben zu veranschaulichen versucht: SIGTERMist blockiert, ist es aber SIGKILLnicht. Wie auch immer, laut einem Kommentar von OP scheint das Problem darin zu bestehen, dass jobsnicht erkannt wird, dass der Prozess gestorben ist, und nicht, dass der Prozess nicht von beendet wurde kill -9 %1.
lcd047
1
Aber ich kann das Verhalten von s1m0n auf meinem System reproduzieren (Debian, amd64, bash 4.3.30).
Gilles 'SO - hör auf böse zu sein'
1
Obwohl SIGKILLes nicht blockiert werden kann, gibt es keine Garantie dafür, dass es innerhalb einer sinnvollen Zeit geliefert wird. Wenn ein Prozess bis zum Blockieren von E / A angehalten wird, kommt er beispielsweise SIGKILLerst an, wenn der Prozess aktiviert ist. Dies kann möglicherweise niemals der Fall sein, wenn keine E / A auftreten.
Sapi
7

Keine Panik.

Es ist nichts Ungewöhnliches los. Hier gibt es keinen Kernel-Bug. Dies ist ein völlig normales Verhalten der Bourne Again-Shell und eines Multitasking-Betriebssystems.

Die Sache, an die man sich erinnern sollte, ist, dass sich ein Prozess selbst tötet , selbst als Reaktion darauf SIGKILL. Was hier passiert ist, dass die Bourne Again-Shell sich um Dinge kümmert, bevor der Prozess, den sie gerade gesagt hat, um sich selbst zu töten, dazu führt, sich selbst zu töten.

Überlegen Sie, was ab dem Punkt passiert, an dem yesgestoppt wurde SIGTSTPund Sie den killBefehl gerade mit der Bourne Again-Shell ausgeführt haben:

  1. Die Shell sendet SIGKILLan den yesProzess.
  2. Parallel dazu :
    1. Der yesProzess soll ausgeführt werden und beendet sich sofort von selbst.
    2. Die Bourne Again-Shell wird fortgesetzt und gibt eine weitere Eingabeaufforderung aus.

Der Grund, warum Sie eine Sache sehen und andere Menschen eine andere sehen, ist ein einfaches Rennen zwischen zwei betriebsbereiten Prozessen, deren Gewinner ganz auf Dinge zurückzuführen ist, die sowohl von Maschine zu Maschine als auch im Laufe der Zeit variieren. Die Systemlast macht einen Unterschied, ebenso wie die Tatsache, dass Ihre CPU virtuell ist.

Im interessanten Fall ist das Detail von Schritt 2 das Folgende:

  1. Die Bourne Again-Shell wird fortgesetzt.
  2. Als Teil der Interna des integrierten killBefehls markiert es den Eintrag in seiner Jobtabelle als eine Benachrichtigung erforderlich, die am nächsten verfügbaren Punkt gedruckt wird.
  3. Der killBefehl wird beendet, und kurz vor dem erneuten Drucken der Eingabeaufforderung wird überprüft, ob Benachrichtigungsnachrichten zu Aufträgen gedruckt werden sollen.
  4. Der yesProzess hatte noch keine Chance, sich selbst zu töten. Was die Shell betrifft, befindet sich der Job immer noch im gestoppten Zustand. Daher druckt die Shell eine Jobstatuszeile "Gestoppt" für diesen Job und setzt das Flag "Benachrichtigung ausstehend" zurück.
  5. Der yesProzess wird geplant und tötet sich selbst.
  6. Der Kernel informiert die Shell, die gerade ihren Befehlszeileneditor ausführt, darüber, dass sich der Prozess selbst beendet hat. Die Shell notiert die Änderung des Status und kennzeichnet den Job als erneut anstehende Benachrichtigung.
  7. Durch einfaches Drücken enter, um die Eingabeaufforderung erneut zu durchlaufen, kann die Shell den neuen Auftragsstatus drucken.

Die wichtigen Punkte sind:

  • Prozesse töten sich selbst. SIGKILList nicht magisch. Prozesse suchen nach ausstehenden Signalen, wenn sie vom Kernelmodus in den Anwendungsmodus zurückkehren. Dies geschieht am Ende von Seitenfehlern, (nicht verschachtelten) Interrupts und Systemaufrufen. Das einzig Besondere ist, dass der Kernel nicht zulässt, dass die Aktion als Reaktion SIGKILLetwas anderes als sofortiger und bedingungsloser Selbstmord ist, ohne dass der Anwendungsmodus wiederhergestellt wird. Wichtig ist , müssen Prozesse sowohl Kernel-to-Application-Modus Übergänge werden zu machen und geplant werden , um zu reagieren auf Signale laufen.
  • Eine virtuelle CPU ist nur ein Thread auf einem Host-Betriebssystem. Es gibt keine Garantie dafür, dass der Host die Ausführung der virtuellen CPU geplant hat. Host-Betriebssysteme sind auch nicht magisch.
  • Benachrichtigungsnachrichten werden nicht gedruckt, wenn sich der Auftragsstatus ändert (es sei denn, Sie verwenden set -o notify). Sie werden gedruckt, wenn die Shell als nächstes einen Punkt in ihrem Ausführungszyklus erreicht, an dem überprüft wird, ob Benachrichtigungen ausstehen.
  • Das Benachrichtigungs-anstehende Flag wird zweimal gesetzt, einmal von killund einmal vom SIGCHLDSignalhandler. Dies bedeutet, dass zwei Nachrichten angezeigt werden können, wenn die Shell ausgeführt wird, bevor der yesProzess neu geplant wird, um sich selbst zu töten. eine Nachricht "Gestoppt" und eine Nachricht "Getötet".
  • Offensichtlich hat das /bin/killProgramm keinen Zugriff auf die interne Jobtabelle der Shell. Sie werden ein solches Verhalten also nicht sehen /bin/kill. Das Flag für ausstehende Benachrichtigungen wird vom SIGCHLDHandler nur einmal gesetzt .
  • Aus dem gleichen Grund wird dieses Verhalten nicht angezeigt, wenn Sie killden yesProzess von einer anderen Shell ausführen.
JdeBP
quelle
3
Das ist eine interessante Theorie, aber das OP kann tippen jobsund die Shell sieht den Prozess immer noch als lebendig an. Das wäre eine ungewöhnlich lange geplante Rennbedingung. :)
lcd047
3
Zunächst einmal vielen Dank für Ihre ausführliche Antwort! Ich mache auf jeden Fall Sinn und kläre einige Dinge auf. Aber wie oben erwähnt, kann ich Multiplikationsbefehle jobsausführen, nach killdenen alle immer noch anzeigen, dass der Prozess gerade gestoppt wurde. Sie haben mich jedoch dazu inspiriert, weiter zu experimentieren, und ich habe [1]+ Terminated yesFolgendes entdeckt: Die Nachricht wird gedruckt, sobald ich einen anderen externen Befehl ausführe (keine eingebaute Shell wie echooder jobs). So kann ich jobsso viel laufen , wie ich möchte, und es wird weiter gedruckt [1]+ Stopped yes. Aber sobald ich lszum Beispiel [1]+ Terminated yes
renne
lcd047 hat Ihren Kommentar zu der Frage nicht gelesen. Das war wichtig und hätte zu Beginn der Frage richtig bearbeitet werden müssen. Es ist leicht, ein Host-Betriebssystem so zu überlasten, dass Gäste von innen heraus sehr seltsam zu planen scheinen. Einfach so und noch viel mehr. (Ich habe es einmal geschafft, eine ziemlich seltsame Planung mit einem
außer Kontrolle geratenen
1
@ Gilles Das Problem scheint zu sein, dass jobsnicht bemerkt wird, dass der Prozess tatsächlich gestorben ist ... Ich bin mir nicht sicher, was ich über die Aktualisierung des Status durch Ausführen eines anderen Befehls tun soll.
lcd047
1
Sogar Gilles hat den Kommentar nicht gesehen. Aus diesem Grund sollten Sie diese Art von wichtigen Dingen in die Frage aufnehmen und nicht in einem Kommentar begraben. Gilles, in der Antwort geht es eindeutig um Verzögerungen bei der Übermittlung eines Signals, nicht um Verzögerungen bei der Übertragung . Du hast sie verwechselt. Lesen Sie auch den Kommentar des Fragestellers (und in der Tat den Aufzählungspunkt, den er hier gibt) und sehen Sie die sehr wichtige falsche Grundannahme, die Sie machen. Virtuelle Prozessoren laufen nicht unbedingt im Gleichschritt und sind nicht auf magische Weise in der Lage, immer mit voller Geschwindigkeit zu laufen.
JdeBP
2

Auf Ihrem System kann etwas Ungewöhnliches passieren. Bei mir funktioniert Ihr Rezept sowohl mit als auch ohne -9:

> yes
...
^Z
[1]+  Stopped                 yes
> jobs
[1]+  Stopped                 yes
> kill %1
[1]+  Killed                  yes
> jobs
> 

Holen Sie sich die PID mit jobs -pund versuchen Sie es als zu töten root.

Dan Cornilescu
quelle
Darf ich fragen, welche Distribution / Kernel / Bash-Version Sie verwenden? Möglicherweise killgeht der interne Befehl Ihrer Bash die Extrameile und prüft, ob der Job eingefroren ist (möglicherweise möchten Sie versuchen, die PID des Jobs herauszufinden und ihn mit zu beenden. Auf env kill <pid>diese Weise verwenden Sie den eigentlichen killBefehl und nicht die integrierte Bash.
Mreithub
bash-4.2-75.3.1.x86_64 bei opensuse 13.2. Der Kill Cmd ist nicht der interne:which kill /usr/bin/kill
Dan Cornilescu
1
whichist kein Bash-Builtin, which <anything>gibt Ihnen also immer den Pfad zum eigentlichen Befehl. Aber versuchen zu vergleichen kill --helpvs. /usr/bin/kill --help.
Mreithub
Ah richtig. In der Tat ist es das Eingebaute kill.
Dan Cornilescu
2

Was Sie beobachten, ist ein Fehler in dieser Version von Bash.

kill -9 %1tötet den Job sofort. Sie können das mit beobachten ps. Sie können den Bash-Prozess verfolgen, um zu sehen, wann der killSystemaufruf aufgerufen wird, und den Unterprozess verfolgen, um zu sehen, wann er die Signale empfängt und verarbeitet. Interessanter ist, dass Sie sehen können, was mit dem Prozess passiert.

bash-4.3$ sleep 9999
^Z
[1]+  Stopped                 sleep 9999
bash-4.3$ kill -9 %1

[1]+  Stopped                 sleep 9999
bash-4.3$ jobs
[1]+  Stopped                 sleep 9999
bash-4.3$ jobs -l
[1]+  3083 Stopped                 sleep 9999
bash-4.3$ 

In einem anderen Terminal:

% ps 3083
  PID TTY      STAT   TIME COMMAND
 3083 pts/4    Z      0:00 [sleep] <defunct>

Der Unterprozess ist ein Zombie . Es ist tot: Alles, was davon übrig bleibt, ist ein Eintrag in der Prozesstabelle (aber kein Speicher, Code, geöffnete Dateien usw.). Der Eintrag bleibt so lange bestehen, bis sein Elternteil es bemerkt und seinen Exit-Status durch Aufrufen des waitSystemaufrufs oder eines seiner Geschwister abruft .

Eine interaktive Shell soll nach toten Kindern suchen und sie ernten, bevor eine Eingabeaufforderung gedruckt wird (sofern nicht anders konfiguriert). Diese Version von Bash funktioniert unter bestimmten Umständen nicht:

bash-4.3$ jobs -l
[1]+  3083 Stopped                 sleep 9999
bash-4.3$ true
bash-4.3$ /bin/true
[1]+  Killed                  sleep 9999

Es ist zu erwarten, dass bash "Killed" meldet, sobald die Eingabeaufforderung nach dem killBefehl gedruckt wird. Dies ist jedoch nicht garantiert, da eine Racebedingung vorliegt. Signale werden asynchron killübermittelt : Der Systemaufruf kehrt zurück, sobald der Kernel herausgefunden hat, an welche Prozesse das Signal übermittelt werden soll, ohne darauf zu warten, dass es tatsächlich übermittelt wird. Es ist möglich, und es kommt in der Praxis vor, dass bash Zeit hat, den Status seines Unterprozesses zu überprüfen, festzustellen, dass er noch nicht tot ist ( wait4keinen Kindstod meldet), und zu drucken, dass der Prozess immer noch gestoppt ist. Was falsch ist, ist, dass vor der nächsten Eingabeaufforderung das Signal übermittelt wurde ( psmeldet, dass der Prozess beendet ist), Bash jedoch noch nicht aufgerufen wurdewait4(Wir können das nicht nur sehen, weil der Job immer noch als "Gestoppt" gemeldet wird, sondern weil der Zombie immer noch in der Prozesstabelle vorhanden ist.) Tatsächlich erntet Bash den Zombie nur beim nächsten Aufruf wait4, wenn er einen anderen externen Befehl ausführt.

Der Fehler tritt nur sporadisch auf und ich konnte ihn nicht reproduzieren, während Bash verfolgt wird (vermutlich, weil es sich um eine Race-Bedingung handelt, bei der Bash schnell reagieren muss). Wenn das Signal vor Bash-Checks geliefert wird, geschieht alles wie erwartet.

Gilles 'SO - hör auf böse zu sein'
quelle