¿Por qué SIGKILL no termina un programa detenido (sí)?

8

Estoy usando Ubuntu 14.04 y estoy experimentando este comportamiento que parece que no puedo entender:

  1. Ejecute el yescomando (en el shell predeterminado: Bash )
  2. Escribe CtrlZpara deteneryes
  3. Ejecutar jobs. Salida:
    [1]+ Stopped yes
  4. Corre kill -9 %1para detenerte yes. Salida:
    [1]+ Stopped yes
  5. Ejecutar jobs. Salida:
    [1]+ Stopped yes

Esto está en Ubuntu 3.16.0-30-genericejecutándose en una máquina virtual paralela.

¿Por qué mi kill -9comando no finalizó el comando ? Pensé que SIGKILL no puede ser atrapado o ignorado? ¿Y cómo puedo terminar el comando yes ?

s1m0n
fuente
1
Eso es interesante. SIGKILL debería funcionar y funciona en mi Linux Mint 17. Para cualquier otra señal, normalmente necesitaría enviarle SIGCONT luego para asegurarse de que la señal sea recibida por el objetivo detenido.
PSkocik
¿Bash realmente imprime "Detenido" para un proceso que está suspendido ?
edmz
Kernel version ( uname -a) please
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. Estoy ejecutando Ubuntu en Parallels Desktop.
s1m0n
1
@black la mayoría de las conchas dicen "Detenido". tcsh dice "Suspendido" y zsh dice "suspendido". Una diferencia cosmética. De algo más importante es el hecho de que bash imprime un mensaje idéntico para STOP y TSTP, donde todos los otros shells marcan la anotación del mensaje STOP (signal)para que pueda notar la diferencia.

Respuestas:

10

Las señales están bloqueadas para procesos suspendidos. En una terminal:

$ yes
...
y
y
^Zy

[1]+  Stopped                 yes

En una segunda terminal:

$ killall yes

En la primera terminal:

$ jobs
[1]+  Stopped                 yes

$ fg
yes
Terminated

Sin embargo SIGKILL, no se puede bloquear. Hacer lo mismo con killall -9 yesla segunda terminal inmediatamente da esto en la yesterminal:

[1]+  Killed                  yes

En consecuencia, si kill -9 %1no finaliza el proceso de inmediato, entonces bashno está enviando la señal hasta que se realiza fgel proceso, o ha descubierto un error en el núcleo.

lcd047
fuente
44
Algunos detalles de fondo: Al emitir Ctrl + Z en su terminal bash envía un SIGTSTP(que es la versión bloqueable de SIGSTOP) al proceso activo. Esto pone el proceso en un estado congelado donde el núcleo no lo programará. Eso también inhibe el procesamiento de la señal (excepto la SIGCONTseñal que descongela el proceso) y, por lo tanto, evita que el proceso se elimine de inmediato.
mreithub
1
SIGKILL, a diferencia de otras señales, no está bloqueado para procesos suspendidos. Enviar la señal KILL a un proceso suspendido la mata, de forma asincrónica, pero en la práctica básicamente de inmediato.
Gilles 'SO- deja de ser malvado'
1
@Gilles Eso es lo que estaba tratando de ilustrar arriba: SIGTERMestá bloqueado, pero SIGKILLno lo está. De todos modos, según un comentario de OP, el problema parece ser que jobsno detecta que el proceso ha muerto, no el proceso por el que no está siendo asesinado kill -9 %1.
lcd047
1
Pero puedo reproducir el comportamiento de s1m0n en mi sistema (Debian, amd64, bash 4.3.30).
Gilles 'SO- deja de ser malvado'
1
Si bien SIGKILLno se puede bloquear, no hay garantía de que se entregue en un tiempo significativo. Si un proceso se suspende pendiente de bloqueo de E / S, por ejemplo, SIGKILLno llegará hasta que el proceso se active. Potencialmente, esto podría ser nunca, si no se produce E / S.
sapi
7

No entres en pánico.

No pasa nada funky. No hay error de kernel aquí. Este es un comportamiento perfectamente normal del shell Bourne Again y un sistema operativo multitarea.

Lo que hay que recordar es que un proceso se mata a sí mismo , incluso en respuesta a SIGKILL. Lo que está sucediendo aquí es que el shell Bourne Again está dando vueltas a las cosas antes de que el proceso que acaba de decir que se mate se convierta en suicidio.

Considere lo que sucede desde el punto donde yesse detuvo SIGTSTPy acaba de ejecutar el killcomando con el shell Bourne Again:

  1. El shell envía SIGKILLal yesproceso.
  2. En paralelo :
    1. El yesproceso está programado para ejecutarse e inmediatamente se suicida.
    2. El shell Bourne Again continúa, emitiendo otro aviso.

La razón por la que está viendo una cosa y otras personas están viendo otra es una carrera simple entre dos procesos listos para ejecutarse, cuyo ganador se debe exclusivamente a cosas que varían de una máquina a otra y con el tiempo. La carga del sistema hace la diferencia, al igual que el hecho de que su CPU es virtual.

En el caso interesante, el detalle del paso 2 es el siguiente:

  1. El shell Bourne Again continúa.
  2. Como parte de la parte interna del killcomando incorporado , marca la entrada en su tabla de trabajo como que necesita un mensaje de notificación impreso en el siguiente punto disponible.
  3. Termina el killcomando y, justo antes de imprimir, la solicitud vuelve a comprobar si debe imprimir mensajes de notificación sobre cualquier trabajo.
  4. El yesproceso aún no ha tenido la oportunidad de suicidarse, por lo que, en lo que respecta al shell, el trabajo aún está en estado detenido. Por lo tanto, el shell imprime una línea de estado de trabajo "Detenido" para ese trabajo y restablece su indicador de notificación pendiente.
  5. El yesproceso se programa y se suicida.
  6. El núcleo informa al shell, que está ocupado ejecutando su editor de línea de comandos, que el proceso se ha suicidado. El shell observa el cambio de estado y marca el trabajo como notificación pendiente nuevamente.
  7. Simplemente presionando enterpara pasar nuevamente por la impresión rápida le da al shell la oportunidad de imprimir el nuevo estado del trabajo.

Los puntos importantes son:

  • Los procesos se suicidan. SIGKILLno es mágico Los procesos verifican si hay señales pendientes cuando se regresa al modo de aplicación desde el modo kernel, que ocurre al final de los fallos de página, las interrupciones (no anidadas) y las llamadas al sistema. Lo único especial es que el núcleo no permite que la acción en respuesta SIGKILLsea ​​otra cosa que un suicidio inmediato e incondicional, sin volver al modo de aplicación. Es importante destacar que los procesos deben estar haciendo transiciones de modo de núcleo a aplicación y estar programados para ejecutarse a fin de responder a las señales.
  • Una CPU virtual es solo un hilo en un sistema operativo host. No hay garantía de que el host haya programado la ejecución de la CPU virtual. Los sistemas operativos host tampoco son mágicos.
  • Los mensajes de notificación no se imprimen cuando se producen cambios en el estado del trabajo (a menos que lo use set -o notify). Se imprimen la próxima vez que el shell alcanza un punto en su ciclo de ejecución que verifica para ver si hay notificaciones pendientes.
  • El indicador de notificación pendiente se está configurando dos veces, una killvez por el SIGCHLDcontrolador de señal. Esto significa que uno puede ver dos mensajes si el shell se está ejecutando antes del yesproceso que se reprograma para suicidarse; uno un mensaje "Detenido" y otro un mensaje "Muerto".
  • Obviamente, el /bin/killprograma no tiene acceso a la tabla de trabajos internos del shell; así que no verás tal comportamiento con /bin/kill. El indicador de notificación pendiente solo se establece una vez, por el SIGCHLDcontrolador.
  • Por la misma razón, no verá este comportamiento si realiza killel yesproceso desde otro shell.
JdeBP
fuente
3
Esa es una teoría interesante, pero el OP llega a escribir jobsy el shell todavía ve el proceso como vivo. Esa sería una condición de carrera de programación inusualmente larga. :)
lcd047
3
En primer lugar, ¡gracias por su elaborada respuesta! Ciertamente tiene sentido y aclara algunas cosas ... Pero como se indicó anteriormente, puedo ejecutar jobscomandos de multiplicación después de lo killcual todos todavía indican que el proceso se ha detenido. Sin embargo, me inspiró a seguir experimentando y descubrí esto: el mensaje [1]+ Terminated yesse imprime tan pronto como ejecuto otro comando externo (no un shell integrado como echoo jobs). Entonces puedo correr jobstanto como quiera y sigue imprimiendo [1]+ Stopped yes. Pero tan pronto como corro, lspor ejemplo, Bash imprime[1]+ Terminated yes
s1m0n
lcd047 no leyó su comentario a la pregunta; lo cual era importante y debería haberse editado correctamente al comienzo de la pregunta. Es fácil sobrecargar un sistema operativo host de modo que los invitados parezcan programar de manera muy extraña, desde adentro. Solo así, y más además. (Una vez me las arreglé para hacer que la programación bastante extraño con un fugitivo Bing escritorio consume la mayor parte del tiempo de la CPU host.)
JdeBP
1
@Gilles El problema parece ser que jobsno se da cuenta de que el proceso realmente ha muerto ... Sin embargo, no estoy seguro de qué hacer con el estado que se está actualizando ejecutando otro comando.
lcd047
1
Incluso Gilles no vio el comentario. Es por eso que debe poner este tipo de cosas importantes en la pregunta , no enterrarlo en un comentario. Gilles, la respuesta claramente habla de retrasos en la entrega de una señal, no retrasos en el envío . Los has mezclado. Además, lea el comentario del interlocutor (y de hecho el punto que se da aquí) y vea la suposición fundamental errónea muy importante que está haciendo. Los procesadores virtuales no necesariamente funcionan a la perfección y no son mágicamente capaces de funcionar siempre a toda velocidad.
JdeBP
2

Algo funky puede estar sucediendo en su sistema, en la mía su receta funciona muy bien con y sin -9:

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

Obtenga el pid con jobs -pe intente matarlo como root.

Dan Cornilescu
fuente
¿Puedo preguntar qué versión de distribución / kernel / bash está utilizando? Tal vez el killcomando interno de su bash haga un esfuerzo adicional y verifique si el trabajo está congelado (es posible que desee intentar averiguar el PID del trabajo y matarlo usando env kill <pid>. De esa manera usará el killcomando real y no el bash incorporado.
mreithub
bash-4.2-75.3.1.x86_64 en opensuse 13.2. El cmd kill no es el interno:which kill /usr/bin/kill
Dan Cornilescu
1
whichno es un bash-builtin, por which <anything>lo que siempre le dará la ruta al comando real. Pero intente comparar kill --helpvs /usr/bin/kill --help.
mreithub
Ah bien. De hecho, es el incorporado kill.
Dan Cornilescu
2

Lo que estás observando es un error en esta versión de bash.

kill -9 %1mata el trabajo de inmediato. Puedes observar eso con ps. Puede rastrear el proceso bash para ver cuándo killse llama a la llamada del sistema, y ​​rastrear el subproceso para ver cuándo recibe y procesa las señales. Más interesante, puedes ir y ver qué está pasando con el proceso.

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$ 

En otra terminal:

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

El subproceso es un zombie . Está muerto: todo lo que queda es una entrada en la tabla de procesos (pero no hay memoria, código, archivos abiertos, etc.). La entrada se deja hasta que su padre tome nota y recupere su estado de salida llamando a la llamada del waitsistema o a uno de sus hermanos .

Se supone que un shell interactivo verifica si hay niños muertos y los cosecha antes de imprimir un mensaje (a menos que esté configurado de otra manera). Esta versión de bash no puede hacerlo en algunas circunstancias:

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

Puede esperar que bash informe "Muerto" tan pronto como se imprima el mensaje después del killcomando, pero eso no está garantizado, porque hay una condición de carrera. Las señales se entregan de forma asíncrona: la killllamada del sistema vuelve tan pronto como el núcleo ha descubierto a qué proceso (s) entregar la señal, sin esperar a que se entregue realmente. Es posible, y sucede en la práctica, que bash tenga tiempo para verificar el estado de su subproceso, descubrir que aún no está muerto ( wait4no informa la muerte de ningún niño) e imprimir que el proceso aún se detiene. Lo que está mal es que antes del próximo aviso, la señal se haya entregado ( psinforma que el proceso está muerto), pero bash todavía no ha llamadowait4(podemos ver eso no solo porque todavía informa que el trabajo está "Detenido", sino porque el zombi todavía está presente en la tabla de proceso). De hecho, bash solo cosecha al zombi la próxima vez que necesite llamar wait4, cuando ejecuta algún otro comando externo.

El error es intermitente y no pude reproducirlo mientras se rastrea bash (presumiblemente porque es una condición de carrera en la que bash debe reaccionar rápidamente). Si la señal se entrega antes de las comprobaciones de bash, todo sucede como se esperaba.

Gilles 'SO- deja de ser malvado'
fuente