Por que o SIGKILL não finaliza um programa parado (yes)?

8

Estou usando o Ubuntu 14.04 e estou com esse comportamento que não consigo entender:

  1. Execute o comando yes (no shell padrão: Bash )
  2. Digite Ctrl Z para parar yes
  3. Executar jobs . Saída: [1]+ Stopped yes
  4. Execute kill -9 %1 para parar yes . Saída: [1]+ Stopped yes
  5. Executar jobs . Saída: [1]+ Stopped yes

Isso está no Ubuntu 3.16.0-30-generic em execução em uma máquina virtual de paralelos.

Por que meu comando kill -9 não terminou o comando yes ? Eu pensei que SIGKILL não pode ser pego ou ignorado? E como posso terminar o comando yes ?

    
por s1m0n 12.06.2015 / 19:17

4 respostas

10

Os sinais são bloqueados para processos suspensos. Em um terminal:

$ yes
...
y
y
^Zy

[1]+  Stopped                 yes

Em um segundo terminal:

$ killall yes

No primeiro terminal:

$ jobs
[1]+  Stopped                 yes

$ fg
yes
Terminated

No entanto, SIGKILL não pode ser bloqueado. Fazer a mesma coisa com killall -9 yes do segundo terminal dá isso imediatamente no terminal yes :

[1]+  Killed                  yes

Consequentemente, se kill -9 %1 não finalizar o processo imediatamente, então bash não está realmente enviando o sinal até você fg do processo, ou você descobriu um bug no kernel.

    
por 12.06.2015 / 21:07
7

Não entre em pânico.

Não há nada de estranho acontecendo. Não há bug do kernel aqui. Esse é um comportamento perfeitamente normal do shell Bourne Again e de um sistema operacional multitarefa.

A coisa a lembrar é que um processo se mata , mesmo em resposta a SIGKILL . O que está acontecendo aqui é que o shell Bourne Again está se contorcendo para as coisas antes do processo que acabou de dizer para se matar, matando a si mesmo.

Considere o que acontece do ponto em que yes foi interrompido com SIGTSTP e você acabou de executar o comando kill com o shell Bourne Again:

  1. O shell envia SIGKILL para o processo yes .
  2. Em paralelo :
    1. O processo yes está agendado para ser executado e imediatamente se mata.
    2. O shell Bourne Again continua, emitindo outro prompt.

A razão pela qual você está vendo uma coisa e outras pessoas estão vendo outra é uma corrida simples entre dois processos prontos para serem executados, cujo vencedor é inteiramente baseado em coisas que variam de máquina para máquina e ao longo do tempo. A carga do sistema faz a diferença, assim como o fato de sua CPU ser virtual.

No caso interessante, o detalhe do passo 2 é este:

  1. O shell Bourne Again continua.
  2. Como parte dos internos do comando kill interno, ele marca a entrada em sua tabela de tarefas, pois precisa de uma mensagem de notificação impressa no próximo ponto disponível.
  3. Ele conclui o comando kill e, pouco antes de imprimir, o prompt verifica novamente se deve imprimir mensagens de notificação sobre qualquer trabalho.
  4. O processo yes ainda não teve a chance de se matar, portanto, no que diz respeito ao shell, o trabalho ainda está no estado parado. Assim, o shell imprime uma linha de status do trabalho "Parado" para esse trabalho e redefine seu sinalizador de notificação pendente.
  5. O processo yes é agendado e se auto-mata.
  6. O kernel informa ao shell, que está ocupado executando seu editor de linha de comando, que o processo se matou. O shell observa a alteração no status e sinaliza a tarefa como uma notificação pendente novamente.
  7. Basta pressionar enter para percorrer o prompt de impressão novamente, dando ao shell a chance de imprimir o novo status do trabalho.

Os pontos importantes são:

  • Processos se matam. SIGKILL não é mágico. Os processos verificam sinais pendentes ao retornar ao modo de aplicativo do modo kernel, o que acontece nas extremidades de falhas de página, interrupções (não aninhadas) e chamadas do sistema. A única coisa especial é que o kernel não permite que a ação em resposta a SIGKILL seja outra coisa senão suicídio imediato e incondicional, sem retorno ao modo de aplicação. É importante ressaltar que os processos precisam tornar as transições do modo kernel-para-aplicativo e agendadas para serem executadas para responder aos sinais.
  • Uma CPU virtual é apenas um encadeamento em um sistema operacional host. Não há garantia de que o host programou a CPU virtual para ser executada. Os sistemas operacionais host também não são mágicos.
  • As mensagens de notificação não são impressas quando as alterações do estado do trabalho ocorrem (a menos que você use set -o notify ). Eles são impressos quando, em seguida, o shell atinge um ponto em seu ciclo de execução que verifica se alguma notificação está pendente.
  • O sinalizador de notificação pendente está sendo definido duas vezes, uma vez por kill e uma vez pelo manipulador de sinal SIGCHLD . Isso significa que é possível ver duas mensagens se o shell estiver sendo executado antes do processo yes que está sendo reprogramado para se matar; uma mensagem "Stopped" e uma mensagem "Killed".
  • Obviamente, o programa /bin/kill não tem acesso à tabela de jobs internos do shell; assim você não verá esse comportamento com /bin/kill . O sinalizador de notificação pendente é definido apenas uma vez, pelo manipulador SIGCHLD .
  • Pelo mesmo motivo, você não verá esse comportamento se você kill the yes processar de outro shell.
por 12.06.2015 / 22:57
2

Algo interessante pode estar acontecendo no seu sistema, no meu, sua receita funciona bem com e sem o -9 :

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

Pegue o pid com jobs -p e tente matá-lo como root .

    
por 12.06.2015 / 19:33
2

O que você está observando é um bug nesta versão do bash.

kill -9 %1 mata o trabalho imediatamente. Você pode observar isso com ps . Você pode rastrear o processo bash para ver quando a chamada do sistema kill é chamada e rastrear o subprocesso para ver quando ele recebe e processa os sinais. Mais interstingly, você pode ir e ver o que está acontecendo com o processo.

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$ 

Em outro terminal:

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

O subprocesso é um zumbi . Está morto: tudo o que resta dele é uma entrada na tabela de processos (mas sem memória, código, arquivos abertos, etc.). A entrada é deixada até que seus pais tomem conhecimento e recuperem seu status de saída chamando a wait chamada de sistema ou um dos seus irmãos .

Um shell interativo deve procurar crianças mortas e colhê-las antes de imprimir um prompt (a menos que seja configurado de outra forma). Esta versão do bash falha em fazer isso em algumas circunstâncias:

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

Você pode esperar que o bash relate "Killed" assim que estiver imprimindo o prompt após o comando kill , mas isso não é garantido, porque há uma condição de corrida. Os sinais são entregues de forma assíncrona: a chamada do sistema kill retorna assim que o kernel tenha descoberto para qual (is) processo (s) entregar o sinal, sem esperar que ele seja realmente entregue. É possível, e isso acontece na prática, o bash tem tempo para verificar o status de seu subprocesso, descobrir que ele ainda não está morto ( wait4 não relata qualquer morte de criança) e imprimir que o processo ainda está parado . O que está errado é que, antes do próximo prompt, o sinal foi entregue ( ps informa que o processo está morto), mas o bash ainda não chamou wait4 (podemos ver isso não apenas porque ele ainda relata o trabalho como "Parado", mas porque o zumbi ainda está presente na tabela de processos). Na verdade, o bash só colhe o zumbi na próxima vez que precisar chamar wait4 , quando ele executar outro comando externo.

O bug é intermitente e não consegui reproduzi-lo enquanto o bash é rastreado (presumivelmente porque é uma condição de corrida em que o bash precisa reagir rapidamente). Se o sinal for entregue antes das verificações bash, tudo acontece conforme o esperado.

    
por 13.06.2015 / 02:45