Por que o bash mostra 'Terminado' depois de matar um processo?

16

Aqui está o comportamento que eu quero entender:

$ ps
  PID TTY           TIME CMD
  392 ttys000    0:00.20 -bash
 4268 ttys000    0:00.00 xargs
$ kill 4268
$ ps
  PID TTY           TIME CMD
  392 ttys000    0:00.20 -bash
[1]+  Terminated: 15          xargs
$ ps
  PID TTY           TIME CMD
  392 ttys000    0:00.21 -bash

Por que mostra o [1]+ Terminated: 15 xargs depois que eu mato um processo, em vez de simplesmente não mostrar como foi morto?

Estou usando o bash no Mac OS X 10.7.5.

    
por syntagma 23.02.2013 / 23:20

2 respostas

23

Resposta curta

Em bash (e dash ) as várias mensagens de "status do trabalho" não são exibidas dos manipuladores de sinais, mas exigem uma verificação explícita. Essa verificação é executada apenas antes de um novo prompt ser fornecido, provavelmente, para não perturbar o usuário enquanto ele está digitando um novo comando.

A mensagem não é mostrada logo antes do prompt após o kill ser exibido provavelmente porque o processo ainda não está morto - essa é uma condição particularmente provável, pois kill é um comando interno do shell, portanto é muito rápido executar e não precisa de bifurcação.

Em vez disso, fazer a mesma experiência com killall geralmente produz a mensagem "kill" imediatamente, sinal de que as alterações de hora / contexto / o que for necessário para executar um comando externo causam um atraso suficiente para que o processo seja morto antes o controle retorna ao shell.

matteo@teokubuntu:~$ dash
$ sleep 60 &
$ ps
  PID TTY          TIME CMD
 4540 pts/3    00:00:00 bash
 4811 pts/3    00:00:00 sh
 4812 pts/3    00:00:00 sleep
 4813 pts/3    00:00:00 ps
$ kill -9 4812
$ 
[1] + Killed                     sleep 60
$ sleep 60 &
$ killall sleep
[1] + Terminated                 sleep 60
$ 

Resposta longa

dash

Primeiro de tudo, eu dei uma olhada nas fontes dash , já que dash exibe o mesmo comportamento e o código é certamente mais simples que bash .

Como dito acima, o ponto parece ser que as mensagens de status do trabalho não são emitidas de um manipulador de sinal (que pode interromper o fluxo de controle de shell "normal"), mas são a conseqüência de uma verificação explícita (a showjobs(out2, SHOW_CHANGED) chamada em dash ) que é executada somente antes de solicitar nova entrada do usuário, no loop REPL.

Assim, se o shell estiver bloqueado aguardando a entrada do usuário, nenhuma mensagem será emitida.

Agora, por que a checagem realizada logo após a morte mostra que o processo foi realmente encerrado? Como explicado acima, provavelmente porque é muito rápido. kill é um comando interno do shell, portanto é muito rápido de executar e não precisa de bifurcação, portanto, quando imediatamente após a execução da verificação, o processo ainda está ativo (ou, pelo menos, é ainda está sendo morto).

kill

Como esperado, bash , sendo um shell muito mais complexo, foi mais complicado e exigiu alguns bash -fu.

O backtrace para quando essa mensagem é emitida é algo como

(gdb) bt
#0  pretty_print_job (job_index=job_index@entry=0, format=format@entry=0, stream=0x7ffff7bd01a0 <_IO_2_1_stderr_>) at jobs.c:1630
#1  0x000000000044030a in notify_of_job_status () at jobs.c:3561
#2  notify_of_job_status () at jobs.c:3461
#3  0x0000000000441e97 in notify_and_cleanup () at jobs.c:2664
#4  0x00000000004205e1 in shell_getc (remove_quoted_newline=1) at /Users/chet/src/bash/src/parse.y:2213
#5  shell_getc (remove_quoted_newline=1) at /Users/chet/src/bash/src/parse.y:2159
#6  0x0000000000423316 in read_token (command=<optimized out>) at /Users/chet/src/bash/src/parse.y:2908
#7  read_token (command=0) at /Users/chet/src/bash/src/parse.y:2859
#8  0x00000000004268e4 in yylex () at /Users/chet/src/bash/src/parse.y:2517
#9  yyparse () at y.tab.c:2014
#10 0x000000000041df6a in parse_command () at eval.c:228
#11 0x000000000041e036 in read_command () at eval.c:272
#12 0x000000000041e27f in reader_loop () at eval.c:137
#13 0x000000000041c6fd in main (argc=1, argv=0x7fffffffdf48, env=0x7fffffffdf58) at shell.c:749

A chamada que verifica empregos mortos & co. é gdb (é mais ou menos o equivalente a notify_of_job_status in showjobs(..., SHOW_CHANGED) ); # 0- # 1 estão relacionados ao seu funcionamento interno; 6-8 é o código do analisador gerado pelo yacc; 10-12 é o loop REPL.

O local interessante aqui é # 4, ou seja, de onde vem a chamada dash . Parece que notify_and_cleanup , ao contrário de bash , pode verificar se há trabalhos terminados em cada caractere lidos na linha de comando, mas aqui está o que eu encontrei:

      /* If the shell is interatctive, but not currently printing a prompt
         (interactive_shell && interactive == 0), we don't want to print
         notifies or cleanup the jobs -- we want to defer it until we do
         print the next prompt. */
      if (interactive_shell == 0 || SHOULD_PROMPT())
    {
#if defined (JOB_CONTROL)
      /* This can cause a problem when reading a command as the result
     of a trap, when the trap is called from flush_child.  This call
     had better not cause jobs to disappear from the job table in
     that case, or we will have big trouble. */
      notify_and_cleanup ();
#else /* !JOB_CONTROL */
      cleanup_dead_jobs ();
#endif /* !JOB_CONTROL */
    }

Portanto, no modo interativo, é intencional atrasar a verificação até que um novo prompt seja fornecido, provavelmente para não atrapalhar o usuário a inserir comandos. Quanto ao motivo pelo qual a verificação não identifica o processo morto ao exibir o novo prompt imediatamente após o dash , a explicação anterior é válida (o processo ainda não está morto).

    
por 24.02.2013 / 01:23
5

Para evitar mensagens de finalização de trabalho (na linha de comando, assim como na saída ps ), você pode colocar o comando em segundo plano em uma construção sh -c 'cmd &' .

{
ps
echo
pid="$(sh -c 'sleep 60 1>&-  & echo ${!}')"
#pid="$(sh -c 'sleep 60 1>/dev/null  & echo ${!}')"
#pid="$(sh -c 'sleep 60 & echo ${!}' | head -1)"
ps
kill $pid
echo
ps
}

A propósito, é possível obter notificações de término de trabalho imediatas em bash usando as opções de shell set -b ou set -o notify respectivamente.

Nesse caso, " bash recebe um sinal SIGCHLD e seu manipulador de sinal exibe a mensagem de notificação imediatamente - mesmo se bash estiver no momento aguardando a conclusão de um processo de primeiro plano" (consulte a próxima referência abaixo).

Para obter um terceiro modo de notificação de controle de job entre set +b (o modo padrão) e set -b (para obter notificações de finalização imediata de tarefas sem corromper o que você já digitou na linha de comando atual)ctrl-x ctrl-v) requer um patch para bash de Simon Tatham (para o patch em si e mais informações, consulte: Notificação de trabalho assíncrona sensível no bash (1) ).

Então, vamos repetir o Matteo Italia -fu de gdb para um shell bash que foi configurado para notificar imediatamente o término do job com set -b .

# 2 Terminal.app windows

# terminal window 1
# start Bash compiled with -g flag
~/Downloads/bash-4.2/bash -il
set -bm
echo $$ > bash.pid

# terminal window 2
gdb -n -q
(gdb) set print pretty on
(gdb) set history save on
(gdb) set history filename ~/.gdb_history
(gdb) set step-mode off
(gdb) set verbose on
(gdb) set height 0
(gdb) set width 0
(gdb) set pagination off
(gdb) set follow-fork-mode child
(gdb) thread apply all bt full
(gdb) shell cat bash.pid
(gdb) attach <bash.pid>
(gdb) break pretty_print_job

# terminal window 1
# cut & paste
# (input will be invisible on the command line)
sleep 600 &   

# terminal window 2
(gdb) continue
(gdb) ctrl-c

# terminal window 1
# cut & paste
kill $!

# terminal window 2
(gdb) continue
(gdb) bt

Reading in symbols for input.c...done.
Reading in symbols for readline.c...done.
Reading in symbols for y.tab.c...done.
Reading in symbols for eval.c...done.
Reading in symbols for shell.c...done.
#0  pretty_print_job (job_index=0, format=0, stream=0x7fff70bb9250) at jobs.c:1630
#1  0x0000000100032ae3 in notify_of_job_status () at jobs.c:3561
#2  0x0000000100031e21 in waitchld (wpid=-1, block=0) at jobs.c:3202
#3  0x0000000100031a1a in sigchld_handler (sig=20) at jobs.c:3049
#4  <signal handler called>
#5  0x00007fff85a9f464 in read ()
#6  0x00000001000b39a9 in rl_getc (stream=0x7fff70bb9120) at input.c:471
#7  0x00000001000b3940 in rl_read_key () at input.c:448
#8  0x0000000100097c88 in readline_internal_char () at readline.c:517
#9  0x0000000100097dba in readline_internal_charloop () at readline.c:579
#10 0x0000000100097de6 in readline_internal () at readline.c:593
#11 0x0000000100097842 in readline (prompt=0x100205f80 "noname:~ <yourname>$ ") at readline.c:342
#12 0x0000000100007ab7 in yy_readline_get () at parse.y:1443
#13 0x0000000100007bbe in yy_readline_get () at parse.y:1474
#14 0x00000001000079d1 in yy_getc () at parse.y:1376
#15 0x000000010000888d in shell_getc (remove_quoted_newline=1) at parse.y:2231
#16 0x0000000100009a22 in read_token (command=0) at parse.y:2908
#17 0x00000001000090c1 in yylex () at parse.y:2517
#18 0x000000010000466a in yyparse () at y.tab.c:2014
#19 0x00000001000042fb in parse_command () at eval.c:228
#20 0x00000001000043ef in read_command () at eval.c:272
#21 0x0000000100004088 in reader_loop () at eval.c:137
#22 0x0000000100001e4d in main (argc=2, argv=0x7fff5fbff528, env=0x7fff5fbff540) at shell.c:749

(gdb) detach
(gdb) quit
    
por 05.04.2013 / 11:48