Por que o SIGKILL não encerra um programa parado (sim)?

8

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

  1. Execute o yescomando (no shell padrão: Bash )
  2. Digite CtrlZpara pararyes
  3. Corra jobs. Resultado:
    [1]+ Stopped yes
  4. Corra kill -9 %1para parar yes. Resultado:
    [1]+ Stopped yes
  5. Corra jobs. Resultado:
    [1]+ Stopped yes

Isso ocorre no Ubuntu 3.16.0-30-genericrodando em uma máquina virtual paralela.

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

s1m0n
fonte
1
Isso é interessante. O SIGKILL deve funcionar e funciona no meu Linux Mint 17. Para qualquer outro sinal, você normalmente precisará enviá-lo SIGCONT posteriormente para garantir que o sinal seja recebido pelo alvo parado.
PSKocik
O bash realmente imprime "Interrompido" para um processo suspenso ?
Edmz 12/06/2015
Versão do kernel ( uname -a) please
roaima 12/06
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. Estou executando o Ubuntu no Parallels Desktop.
S1m0n
1
@ preto a maioria dos reservatórios diz "Parado". O tcsh diz "Suspenso" e o zsh diz "suspenso". Uma diferença cosmética. De alguma importância é o fato de o bash imprimir uma mensagem idêntica para STOP e TSTP, onde todos os outros shells marcam a anotação da mensagem STOP (signal)para que você possa ver a diferença.

Respostas:

10

Os sinais estã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, SIGKILLnão pode ser bloqueado. Fazer a mesma coisa com killall -9 yeso segundo terminal fornece isso imediatamente no yesterminal:

[1]+  Killed                  yes

Consequentemente, se kill -9 %1não encerrar o processo imediatamente, você bashnão estará enviando o sinal até fgo processo ou você descobriu um bug no kernel.

lcd047
fonte
4
Alguns detalhes de plano de fundo: Ao emitir Ctrl + Z no bash do terminal, envia um SIGTSTP(que é a versão bloqueável SIGSTOP) para o processo ativo. Isso coloca o processo em um estado congelado, onde o kernel não o agenda. Isso também inibe o processamento do sinal (exceto o SIGCONTsinal que descongela o processo) e, portanto, impede que o processo seja eliminado imediatamente.
Mreithub
1
SIGKILL, diferentemente de outros sinais, não é bloqueado para processos suspensos. O envio do sinal KILL para um processo suspenso o mata - de forma assíncrona, mas na prática basicamente imediatamente.
Gilles 'SO- stop be evil'
1
@ Gilles Isso é o que eu estava tentando ilustrar acima: SIGTERMestá bloqueado, mas SIGKILLnão está. De qualquer forma, de acordo com um comentário do OP, o problema parece ser que jobsnão detecta que o processo morreu, não o processo que não está sendo morto kill -9 %1.
Lcd047
1
Mas posso reproduzir o comportamento do s1m0n no meu sistema (Debian, amd64, bash 4.3.30).
Gilles 'SO- stop be evil'
1
Embora SIGKILLnão possa ser bloqueado, não há garantia de que será entregue dentro de um período significativo. Se um processo for suspenso, a E / S de bloqueio pendente, por exemplo, SIGKILLnão chegará até que o processo seja ativado. Isso pode ser potencialmente nunca, se nenhuma E / S ocorrer.
sapi 13/06/2015
7

Não entre em pânico.

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

É importante lembrar que um processo se mata , mesmo em resposta a SIGKILL. O que está acontecendo aqui é que o shell Bourne Again está lidando com as coisas antes do processo que ele acabou de dizer para se matar.

Considere o que acontece a partir do ponto em yesque foi interrompido SIGTSTPe você acabou de executar o killcomando com o shell Bourne Again:

  1. O shell envia SIGKILLpara o yesprocesso.
  2. Em paralelo :
    1. O yesprocesso está programado para ser executado e se mata imediatamente.
    2. O shell Bourne Again continua, emitindo outro prompt.

O motivo pelo qual você está vendo uma coisa e outras pessoas estão vendo outra é uma corrida simples entre dois processos prontos para execução, cujo vencedor é inteiramente o que varia 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, os detalhes da etapa 2 são os seguintes:

  1. O shell Bourne Again continua.
  2. Como parte dos elementos internos do killcomando interno, ele marca a entrada em sua tabela de tarefas como necessitando de uma mensagem de notificação impressa no próximo ponto disponível.
  3. Ele termina o killcomando e, pouco antes de imprimir, o prompt verifica novamente se deve imprimir mensagens de notificação sobre algum trabalho.
  4. O yesprocesso ainda não teve a chance de se matar, portanto, no que diz respeito ao shell, o trabalho ainda está no estado parado. Portanto, o shell imprime uma linha de status de trabalho "Interrompido" para esse trabalho e redefine seu sinalizador de notificação pendente.
  5. O yesprocesso é agendado e se 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 o trabalho como notificação pendente novamente.
  7. Basta pressionar enterpara percorrer novamente a impressão rápida, dando ao shell a chance de imprimir o novo status do trabalho.

Os pontos importantes são:

  • Processos se matam. SIGKILLnão é mágico. Os processos verificam sinais pendentes ao retornar ao modo de aplicativo a partir do modo kernel, o que ocorre no final das falhas da 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 SIGKILLseja outra coisa senão suicídio imediato e incondicional, sem retorno ao modo de aplicativo. É importante ressaltar que os processos precisam estar fazendo transições do modo kernel para aplicativo e devem ser agendados para serem executados para responder a sinais.
  • Uma CPU virtual é apenas um encadeamento em um sistema operacional host. Não há garantia de que o host tenha agendado a execução da CPU virtual. 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 no estado do trabalho acontecem (a menos que você use set -o notify). Eles são impressos quando, em seguida, o shell atingir um ponto em seu ciclo de execução que verifica se há alguma notificação pendente.
  • O sinalizador de notificação pendente está sendo definido duas vezes, uma vez kille uma vez pelo SIGCHLDmanipulador de sinal. Isso significa que é possível ver duas mensagens se o shell estiver executando antes do yesprocesso ser remarcado para se matar; uma mensagem "Parada" e uma mensagem "Morto".
  • Obviamente, o /bin/killprograma não tem acesso à tabela de tarefas internas do shell; então você não verá esse comportamento com /bin/kill. O sinalizador pendente de notificação é definido apenas uma vez, pelo SIGCHLDmanipulador.
  • Pelo mesmo motivo, você não verá esse comportamento se killo yesprocesso for de outro shell.
JdeBP
fonte
3
Essa é uma teoria interessante, mas o OP começa a digitar jobse o shell ainda vê o processo como vivo. Essa seria uma condição de corrida incomumente longa. :)
lcd047
3
Antes de tudo, obrigado pela sua resposta elaborada! Eu certamente faz sentido e esclareço algumas coisas. Mas, como mencionado acima, eu posso executar jobscomandos de multiplicação após os killquais todos ainda indicam que o processo está parado. Você, no entanto, me inspirou a continuar experimentando e eu descobri isso: a mensagem [1]+ Terminated yesé impressa assim que eu executo outro comando externo (não um shell embutido como echoou jobs). Para que eu possa executar o jobsquanto quiser e ele continua imprimindo [1]+ Stopped yes. Mas assim que eu corro lspor exemplo, impressões Bash[1]+ Terminated yes
s1m0n
lcd047 não leu seu comentário para a pergunta; o que era importante e deveria ter sido editado corretamente no início da pergunta. É fácil sobrecarregar um sistema operacional host, de modo que os convidados pareçam agendar muito estranhamente, de dentro para fora. Assim, e mais ainda. (Uma vez eu consegui fazer com programação bastante estranho com um fugitivo Bing área de trabalho consumindo a maior parte do tempo da CPU host.)
JdeBP
1
@ Gilles O problema parece ser que jobsnão percebe que o processo realmente morreu ... Não sei o que fazer com o status sendo atualizado executando outro comando.
Lcd047
1
Até Gilles não viu o comentário. É por isso que você deve colocar esse tipo de coisa importante em questão , não enterrá-lo em um comentário. Gilles, a resposta fala claramente de atrasos na entrega de um sinal, não de atrasos no envio . Você os misturou. Leia também o comentário do interlocutor (e, de fato, o ponto de referência que é dado aqui) e veja a suposição fundamental errada muito importante que você está fazendo. Os processadores virtuais não são necessariamente executados em trava de bloqueio e não são magicamente capazes de executar sempre a toda velocidade.
JdeBP
2

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

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

Receba o pid com jobs -pe tente matá-lo como root.

Dan Cornilescu
fonte
Posso perguntar qual versão de distribuição / kernel / bash você está usando? Talvez o killcomando interno do seu bash vá além e verifique se o trabalho está congelado (você pode tentar descobrir o PID do trabalho e matá-lo usando env kill <pid>. Dessa forma, você usará o killcomando real e não o bash embutido.
Mreithub
bash-4.2-75.3.1.x86_64 no opensuse 13.2. O cmd da matança não é interno:which kill /usr/bin/kill
Dan Cornilescu
1
whichnão é um bash-builtin, por which <anything>isso sempre fornecerá o caminho para o comando real. Mas tente comparar kill --helpvs. /usr/bin/kill --help.
Mreithub
Ah, certo. Na verdade, é o builtin kill.
Dan Cornilescu
2

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

kill -9 %1mata o trabalho imediatamente. Você pode observar isso com ps. Você pode rastrear o processo bash para ver quando a killchamada do sistema é chamada e rastrear o subprocesso para ver quando recebe e processa os sinais. Mais interessante, você pode 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 é uma entrada na tabela de processos (mas não há memória, código, arquivos abertos etc.). A entrada é deixada em aberto até que seu pai ou mãe notifique e recupere seu status de saída chamando a waitchamada de sistema ou um de seus irmãos .

Um shell interativo deve verificar se há filhos mortos e colhê-los antes de imprimir um prompt (a menos que configurado de outra forma). Esta versão do bash falha ao fazê-lo 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 imprimir o prompt após o killcomando, mas isso não é garantido, porque há uma condição de corrida. Os sinais são entregues de forma assíncrona: a killchamada do sistema retorna assim que o kernel descobre para quais processos entregar o sinal, sem esperar que ele seja realmente entregue. É possível, e acontece na prática, que o bash tenha tempo para verificar o status de seu subprocesso, descobrir que ele ainda não está morto ( wait4não relata nenhuma morte infantil) e imprimir que o processo ainda está parado. O que há de errado é que, antes do próximo prompt, o sinal foi entregue ( psrelata que o processo está morto), mas o bash ainda não foi chamadowait4(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). De fato, o bash só colhe o zumbi na próxima vez que ele precisar chamar wait4, quando executa algum outro comando externo.

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

Gilles 'SO- parar de ser mau'
fonte