Perché bash mostra 'Terminato' dopo aver ucciso un processo?


17

Ecco il comportamento che voglio capire:

$ 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

Perché mostra il [1]+ Terminated: 15 xargsdopo che ho ucciso un processo, invece di non mostrarlo perché è stato appena ucciso?

Sto usando bash su Mac OS X 10.7.5.

Risposte:


24

Risposta breve

Nei bash(e dash) i vari messaggi di "stato del lavoro" non vengono visualizzati dai gestori dei segnali, ma richiedono un controllo esplicito. Questo controllo viene eseguito solo prima che venga fornito un nuovo prompt, probabilmente per non disturbare l'utente mentre sta digitando un nuovo comando.

Il messaggio non viene mostrato poco prima del prompt dopo che killviene visualizzato probabilmente perché il processo non è ancora morto - questa è una condizione particolarmente probabile poiché killè un comando interno della shell, quindi è molto veloce da eseguire e non necessita di biforcazione.

Fare lo stesso esperimento con killall, invece, di solito produce immediatamente il messaggio "ucciso", segno che i cambi di tempo / contesto / qualunque cosa sia necessaria per eseguire un comando esterno causano un ritardo abbastanza lungo da far terminare il processo prima che il controllo ritorni alla 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
$ 

Risposta lunga

dash

Prima di tutto, ho dato un'occhiata alle dashfonti, poiché dashmostra lo stesso comportamento e il codice è sicuramente più semplice di bash.

Come detto sopra, il punto sembra essere che i messaggi sullo stato del lavoro non vengono emessi da un gestore di segnale (che può interrompere il flusso di controllo della shell "normale"), ma sono la conseguenza di un controllo esplicito (una showjobs(out2, SHOW_CHANGED)chiamata in dash) che viene eseguito solo prima di richiedere un nuovo input dall'utente, nel ciclo REPL.

Pertanto, se la shell viene bloccata in attesa dell'input dell'utente, tale messaggio non viene emesso.

Ora, perché il controllo non viene eseguito subito dopo l'uccisione mostra che il processo è stato effettivamente terminato? Come spiegato sopra, probabilmente perché è troppo veloce. killè un comando interno della shell, quindi è molto veloce da eseguire e non necessita di biforcazione, quindi, quando immediatamente dopo killil controllo viene eseguito, il processo è ancora vivo (o, almeno, viene ancora ucciso).


bash

Come previsto, bashessendo un guscio molto più complesso, era più complicato e richiedeva un po 'di gdbfortuna.

Il backtrace per quando viene emesso quel messaggio è qualcosa di simile

(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

La chiamata che controlla i posti di lavoro morti e altri. è notify_of_job_status(è più o meno l'equivalente di showjobs(..., SHOW_CHANGED)in dash); # 0- # 1 sono collegati al suo funzionamento interiore; 6-8 è il codice parser generato da yacc; 10-12 è il ciclo REPL.

Il posto interessante qui è il n. 4, ovvero da dove notify_and_cleanupproviene la chiamata. Sembra che bash, a differenza dash, possa verificare la presenza di lavori terminati su ciascun personaggio letto dalla riga di comando, ma ecco cosa ho trovato:

      /* 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 */
    }

Pertanto, in modalità interattiva è intenzionale ritardare il controllo fino a quando non viene fornito un nuovo prompt, probabilmente per non disturbare l'utente a immettere i comandi. Per quanto riguarda il motivo per cui il controllo non individua il processo morto quando si visualizza il nuovo prompt immediatamente dopo il kill, la spiegazione precedente vale (il processo non è ancora morto).


5

Per evitare qualsiasi messaggio di interruzione del lavoro (sulla riga comandi e psnell'output) è possibile inserire il comando in background in un sh -c 'cmd &'costrutto.

{
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 proposito, è possibile ricevere notifiche immediate di conclusione del lavoro bashutilizzando le opzioni della shell set -bo set -o notifyrispettivamente.

In questo caso " bashriceve un SIGCHLDsegnale e il suo gestore di segnale visualizza immediatamente il messaggio di notifica, anche se bashè attualmente nel mezzo dell'attesa del completamento di un processo in primo piano" (vedere il riferimento successivo di seguito).

Per ottenere una terza modalità di notifica del controllo del lavoro tra set +b(la modalità predefinita) e set -b(in modo da ricevere notifiche immediate di interruzione del lavoro senza corrompere ciò che hai già digitato sulla tua riga di comando corrente - simile a ctrl-x ctrl-v) richiede una patch a cui bashSimon Tatham (per la patch stessa e ulteriori informazioni, vedere: Notifica sensibile del lavoro asincrono in bash (1) ).

Quindi ripetiamo solo Matteo Italia' gdb-fu per una bashshell che è stata impostata per notificare immediatamente la fine del lavoro 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

freddo! ma credi che potrebbe esserci un altro modo? Ci sto provando: pid="$(sh -c 'cat "$fileName" |less & echo ${!}')"ma meno non si presenta
Aquarius Power
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.