Pourquoi SIGKILL ne met-il pas fin à un programme arrêté (oui)?

8

J'utilise Ubuntu 14.04 et je rencontre ce problème que je n'arrive pas à comprendre:

  1. Exécutez la yescommande (dans le shell par défaut: Bash )
  2. Tapez CtrlZpour arrêteryes
  3. Courez jobs. Production:
    [1]+ Stopped yes
  4. Courez kill -9 %1pour vous arrêter yes. Production:
    [1]+ Stopped yes
  5. Courez jobs. Production:
    [1]+ Stopped yes

C'est sur Ubuntu 3.16.0-30-genericfonctionnant dans une machine virtuelle parallèle.

Pourquoi ma kill -9commande n'a-t-elle pas mis fin à la commande oui ? Je pensais que SIGKILL ne pouvait pas être attrapé ou ignoré? Et comment puis-je terminer la commande yes ?

s1m0n
la source
1
C'est intéressant. SIGKILL devrait fonctionner et il fonctionne sur mon Linux Mint 17. Pour tout autre signal, vous auriez normalement besoin de lui envoyer SIGCONT par la suite pour vous assurer que le signal est reçu par la cible arrêtée.
PSkocik
Bash affiche-t-il vraiment "Arrêté" pour un processus suspendu ?
edmz
Version du noyau ( uname -a) s'il vous plaît
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. J'utilise Ubuntu dans Parallels Desktop.
s1m0n
1
@black la plupart des obus disent "Arrêté". tcsh dit "Suspendu" et zsh dit "suspendu". Une différence cosmétique. Un peu plus important est le fait que bash imprime un message identique pour STOP et TSTP, où tous les autres shells marquent l'annotation du message STOP (signal)pour que vous puissiez faire la différence.

Réponses:

10

Les signaux sont bloqués pour les processus suspendus. Dans un terminal:

$ yes
...
y
y
^Zy

[1]+  Stopped                 yes

Dans un deuxième terminal:

$ killall yes

Dans le premier terminal:

$ jobs
[1]+  Stopped                 yes

$ fg
yes
Terminated

Cependant, SIGKILLne peut pas être bloqué. Faire la même chose avec killall -9 yesdepuis le deuxième terminal donne immédiatement ceci dans le yesterminal:

[1]+  Killed                  yes

Par conséquent, si kill -9 %1ne met pas fin au processus immédiatement, alors soit bashvous n'envoyez pas le signal avant fgle processus, soit vous avez découvert un bogue dans le noyau.

lcd047
la source
4
Quelques détails d'arrière-plan: Lorsque vous émettez Ctrl + Z dans votre terminal bash envoie un SIGTSTP(qui est la version bloquable de SIGSTOP) au processus actif. Cela met le processus dans un état figé où le noyau ne le planifie pas. Cela inhibe également le traitement du signal (à l'exception du SIGCONTsignal qui débloque le processus) et empêche donc le processus d'être immédiatement tué.
mreithub
1
SIGKILL, contrairement à d'autres signaux, n'est pas bloqué pour les processus suspendus. L'envoi du signal KILL à un processus suspendu le tue - de manière asynchrone, mais dans la pratique essentiellement immédiatement.
Gilles 'SO- arrête d'être méchant'
1
@Gilles C'est ce que j'essayais d'illustrer ci-dessus: SIGTERMest bloqué, mais SIGKILLne l'est pas. Quoi qu'il en soit, selon un commentaire d'OP, le problème semble être qu'il jobsne détecte pas que le processus est mort, pas le processus qui n'est pas tué par kill -9 %1.
lcd047
1
Mais je peux reproduire le comportement de s1m0n sur mon système (Debian, amd64, bash 4.3.30).
Gilles 'SO- arrête d'être méchant'
1
Bien SIGKILLqu'il ne puisse pas être bloqué, il n'y a aucune garantie qu'il sera livré dans un délai significatif. Si un processus est suspendu en attente de blocage, les E / S, par exemple, SIGKILLn'arriveront pas avant la fin du processus. Cela pourrait potentiellement ne jamais se produire si aucune E / S ne se produit.
sapi
7

Pas de panique.

Il n'y a rien de génial. Il n'y a pas de bug du noyau ici. Il s'agit d'un comportement parfaitement normal du shell Bourne Again et d'un système d'exploitation multitâche.

La chose à retenir est qu'un processus se tue , même en réponse à SIGKILL. Ce qui se passe ici, c'est que le shell Bourne Again se déplace aux choses avant que le processus qu'il vient de dire de se tuer ne se tue.

Considérez ce qui se passe à partir du point où vous avez yesété arrêté SIGTSTPet vous venez d'exécuter la killcommande avec le shell Bourne Again:

  1. Le shell envoie SIGKILLau yesprocessus.
  2. En parallèle :
    1. Le yesprocessus doit s'exécuter et se tue immédiatement.
    2. Le shell Bourne Again continue, émettant une autre invite.

La raison pour laquelle vous voyez une chose et que d'autres personnes en voient une autre est une simple course entre deux processus prêts à fonctionner, dont le gagnant est entièrement dû à des choses qui varient d'une machine à l'autre et dans le temps. La charge du système fait une différence, tout comme le fait que votre CPU est virtuel.

Dans le cas intéressant, le détail de l'étape 2 est le suivant:

  1. L'obus Bourne Again continue.
  2. Dans le cadre des fonctions internes de la killcommande intégrée, il marque l'entrée dans sa table de tâches comme nécessitant un message de notification imprimé au prochain point disponible.
  3. Il termine la killcommande et, juste avant d'imprimer à nouveau l'invite, vérifie s'il doit imprimer des messages de notification concernant les travaux.
  4. Le yesprocessus n'a pas encore eu la chance de se tuer, donc en ce qui concerne le shell, le travail est toujours à l'état arrêté. Ainsi, le shell imprime une ligne d'état de travail "Arrêté" pour ce travail et réinitialise son indicateur de notification en attente.
  5. Le yesprocessus est planifié et se tue.
  6. Le noyau informe le shell, qui est occupé à exécuter son éditeur de ligne de commande, que le processus s'est tué. Le shell note le changement d'état et signale le travail comme une notification en attente.
  7. Une simple pression sur enterpour parcourir à nouveau l'impression rapide donne au shell la possibilité d'imprimer le nouveau statut de la tâche.

Les points importants sont:

  • Les processus se tuent. SIGKILLn'est pas magique. Les processus vérifient les signaux en attente lors du retour en mode application du mode noyau, ce qui se produit à la fin des erreurs de page, des interruptions (non imbriquées) et des appels système. La seule chose spéciale est que le noyau ne permet pas que l'action en réponse SIGKILLsoit autre chose qu'un suicide immédiat et inconditionnel, sans retour en mode application. Il est important de noter que les processus doivent à la fois effectuer des transitions du noyau au mode d'application et être planifiés pour s'exécuter afin de répondre aux signaux.
  • Un CPU virtuel n'est qu'un thread sur un système d'exploitation hôte. Il n'y a aucune garantie que l'hôte a planifié l'exécution du processeur virtuel. Les systèmes d'exploitation hôtes ne sont pas magiques non plus.
  • Les messages de notification ne sont pas imprimés lorsque les changements d'état du travail se produisent (sauf si vous utilisez set -o notify). Ils sont imprimés lorsque le prochain shell atteint un point de son cycle d'exécution qu'il vérifie pour voir si des notifications sont en attente.
  • L'indicateur de notification en attente est défini deux fois, une killfois par le SIGCHLDgestionnaire de signaux. Cela signifie que l'on peut voir deux messages si le shell est en cours d'exécution avant que le yesprocessus soit reprogrammé pour se tuer; un message "Arrêté" et un message "Tué".
  • De toute évidence, le /bin/killprogramme n'a aucun accès à la table des tâches internes du shell; vous ne verrez donc pas un tel comportement avec /bin/kill. L'indicateur de notification en attente n'est défini qu'une seule fois, par le SIGCHLDgestionnaire.
  • Pour la même raison, vous ne verrez pas ce comportement si vous killle yesprocessus d' une autre coquille.
JdeBP
la source
3
C'est une théorie intéressante, mais l'OP arrive à taper jobset le shell voit toujours le processus comme vivant. Ce serait une condition de concurrence de programmation inhabituellement longue. :)
lcd047
3
Tout d'abord, merci pour votre réponse élaborée! J'ai certainement du sens et clarifie pas mal de choses .. Mais comme indiqué ci-dessus, je peux exécuter des jobscommandes de multiplication après killlesquelles toutes indiquent toujours que le processus est juste arrêté. Vous m'avez cependant inspiré à continuer d'expérimenter et je l'ai découvert: le message [1]+ Terminated yesest imprimé dès que j'exécute une autre commande externe (pas un shell intégré comme echoou jobs). Je peux donc courir jobsautant que je veux et ça continue d'imprimer [1]+ Stopped yes. Mais dès que je cours lspar exemple, Bash imprime[1]+ Terminated yes
s1m0n
lcd047 n'a pas lu votre commentaire sur la question; ce qui était important et aurait dû être correctement édité au début de la question. Il est facile de surcharger un système d'exploitation hôte de telle sorte que les invités semblent planifier très étrangement, de l'intérieur. Juste comme ça, et plus encore. (Une fois, j'ai réussi à provoquer une planification assez étrange avec un Bing Desktop
incontrôlable
1
@Gilles Le problème semble être qu'il jobsne remarque pas que le processus est réellement mort ... Je ne sais pas quoi faire au sujet du statut mis à jour en exécutant une autre commande.
lcd047
1
Même Gilles n'a pas vu le commentaire. C'est pourquoi vous devriez mettre ce genre de choses importantes dans la question , pas l'enterrer dans un commentaire. Gilles, la réponse parle clairement de retards dans la livraison d' un signal, pas de retards dans l' envoi . Vous les avez mélangés. Lisez également le commentaire de l'interrogateur (et en fait la puce qu'il contient ici) et voyez l'hypothèse fondamentale erronée très importante que vous faites. Les processeurs virtuels ne fonctionnent pas nécessairement au pas de vis et ne sont pas magiquement capables de toujours fonctionner à pleine vitesse.
JdeBP
2

Quelque chose de funky peut se produire sur votre système, sur le mien, votre recette fonctionne bien avec et sans le -9:

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

Obtenez le pid avec jobs -pet essayez de le tuer comme root.

Dan Cornilescu
la source
Puis-je demander quelle version de distribution / noyau / bash vous utilisez? Peut-être que la killcommande interne de votre bash va plus loin et vérifie si le travail est gelé (vous voudrez peut-être essayer de trouver le PID du travail et de le tuer en utilisant env kill <pid>. De cette façon, vous utiliserez la killcommande réelle et non la commande bash intégrée).
mreithub
bash-4.2-75.3.1.x86_64 sur opensuse 13.2. Le kill cmd n'est pas interne:which kill /usr/bin/kill
Dan Cornilescu
1
whichn'est pas un bash-builtin, donc which <anything>vous donnera toujours le chemin vers la commande réelle. Mais essayez de comparer par kill --helprapport /usr/bin/kill --help.
mreithub
Ah, c'est vrai. En effet, c'est le intégré kill.
Dan Cornilescu
2

Ce que vous observez est un bug dans cette version de bash.

kill -9 %1ne tue pas le travail immédiatement. Vous pouvez observer cela avec ps. Vous pouvez tracer le processus bash pour voir quand l' killappel système est appelé et tracer le sous-processus pour voir quand il reçoit et traite les signaux. Plus intéressant, vous pouvez aller voir ce qui se passe dans le processus.

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$ 

Dans un autre terminal:

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

Le sous-processus est un zombie . Il est mort: il ne lui reste qu'une entrée dans la table des processus (mais pas de mémoire, de code, de fichiers ouverts, etc.). L'entrée est conservée jusqu'à ce que son parent en prenne note et récupère son état de sortie en appelant l' waitappel système ou l'un de ses frères et sœurs .

Un shell interactif est censé rechercher les enfants morts et les récolter avant d'imprimer une invite (sauf configuration contraire). Cette version de bash ne parvient pas à le faire dans certaines circonstances:

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

Vous pouvez vous attendre à ce que bash signale «Tué» dès qu'il imprime l'invite après la killcommande, mais ce n'est pas garanti, car il y a une condition de concurrence. Les signaux sont délivrés de manière asynchrone: l' killappel système revient dès que le noyau a déterminé le ou les processus auxquels délivrer le signal, sans attendre qu'il soit réellement délivré. Il est possible, et cela arrive dans la pratique, que bash ait le temps de vérifier l'état de son sous-processus, de constater qu'il n'est toujours pas mort ( wait4ne signale aucun décès d'enfant) et d'imprimer que le processus est toujours arrêté. Ce qui ne va pas, c'est qu'avant l'invite suivante, le signal a été délivré ( pssignale que le processus est mort), mais bash n'a toujours pas appeléwait4(nous pouvons voir cela non seulement parce qu'il signale toujours le travail comme «Arrêté», mais parce que le zombie est toujours présent dans la table de processus). En fait, bash ne récolte le zombie que la prochaine fois qu'il doit appeler wait4, lorsqu'il exécute une autre commande externe.

Le bug est intermittent et je n'ai pas pu le reproduire pendant que bash est tracé (probablement parce que c'est une condition de concurrence où bash doit réagir rapidement). Si le signal est délivré avant les vérifications bash, tout se passe comme prévu.

Gilles 'SO- arrête d'être méchant'
la source