Dlaczego bash pokazuje „Zakończony” po zabiciu procesu?

17

Oto zachowanie, które chcę zrozumieć:

$ 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

Dlaczego pokazuje [1]+ Terminated: 15 xargspo tym, jak zabiłem proces, zamiast po prostu nie pokazywać tego, jak właśnie został zabity?

Używam bash na Mac OS X 10.7.5.

syntagma
źródło

Odpowiedzi:

24

Krótka odpowiedź

W bash(idash ) różne komunikaty „status zadania” nie są wyświetlane przez programy obsługi sygnałów, ale wymagają wyraźnego sprawdzenia. To sprawdzenie jest wykonywane tylko przed wyświetleniem nowego monitu, prawdopodobnie nie przeszkadzając użytkownikowi podczas pisania nowej komendy.

Komunikat nie jest wyświetlany tuż przed wyświetleniem monitu po, killprawdopodobnie dlatego, że proces nie jest jeszcze martwy - jest to szczególnie prawdopodobny warunek, ponieważ killjest to wewnętrzne polecenie powłoki, więc jest bardzo szybkie do wykonania i nie wymaga rozwidlenia.

Wykonanie tego samego eksperymentu z killall, zamiast tego, zwykle powoduje natychmiastowy komunikat „zabity”, znak, że przełączniki czasu / kontekstu / cokolwiek wymaganego do wykonania polecenia zewnętrznego powodują opóźnienie wystarczająco długie, aby proces mógł zostać zabity, zanim formant powróci do powłoki .

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
$ 

Długa odpowiedź

dash

Przede wszystkim rzuciłem okiem na dashźródła, ponieważ dashwykazuje to samo zachowanie, a kod jest z pewnością prostszy niż bash.

Jak wspomniano powyżej, wydaje się, że chodzi o to, że komunikaty o statusie zadania nie są emitowane z procedury obsługi sygnału (która może zakłócać „normalny” przepływ kontroli powłoki), ale są konsekwencją jawnego sprawdzenia ( showjobs(out2, SHOW_CHANGED)wywołania dash), które jest wykonywane tylko przed zażądaniem nowego wejścia od użytkownika, w pętli REPL.

Zatem jeśli powłoka jest zablokowana w oczekiwaniu na dane wejściowe użytkownika, taki komunikat nie jest emitowany.

Dlaczego więc kontrola przeprowadzona tuż po zabiciu nie pokazuje, że proces został faktycznie zakończony? Jak wyjaśniono powyżej, prawdopodobnie dlatego, że jest za szybki. killjest wewnętrzną komendą powłoki, więc jest bardzo szybka do wykonania i nie wymaga rozwidlenia, dlatego gdy natychmiast po killprzeprowadzeniu kontroli proces jest nadal aktywny (lub przynajmniej wciąż jest zabijany).


bash

Zgodnie z oczekiwaniami bashbycie znacznie bardziej złożoną powłoką było trudniejsze i wymagało trochę gdb-fu.

Ślad wsteczny po wysłaniu tej wiadomości jest podobny

(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

Połączenie sprawdzające martwe zlecenia i spółkę. jest notify_of_job_status(to mniej więcej odpowiednik showjobs(..., SHOW_CHANGED)in dash); # 0- # 1 związane są z jego wewnętrznym działaniem; 6-8 to wygenerowany przez yacc kod parsera; 10-12 to pętla REPL.

Interesującym miejscem jest tutaj # 4, tj. Skąd notify_and_cleanuppochodzi połączenie. Wygląda na to bash, że w przeciwieństwie do tego dashmoże sprawdzać zakończone zadania przy każdym znaku odczytanym z wiersza poleceń, ale oto, co znalazłem:

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

Tak więc w trybie interaktywnym celowe jest opóźnianie sprawdzania do momentu pojawienia się nowego monitu, prawdopodobnie nie zakłócając wprowadzania poleceń przez użytkownika. Jeśli chodzi o to, dlaczego sprawdzanie nie wykrywa martwego procesu podczas wyświetlania nowego monitu bezpośrednio po kill, poprzednie wytłumaczenie pozostaje w mocy (proces nie jest jeszcze martwy).

Matteo Italia
źródło
5

Aby uniknąć komunikatów o zakończeniu zadania (zarówno w wierszu poleceń, jak i na pswyjściu), możesz umieścić polecenie w tle w sh -c 'cmd &'konstrukcji.

{
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
}

Nawiasem mówiąc, możliwe jest otrzymywanie natychmiastowych powiadomień o zakończeniu pracy bashza pomocą opcji powłoki set -blubset -o notify odpowiednio.

W tym przypadku „ bashodbiera SIGCHLDsygnał, a jego moduł obsługi sygnału wyświetla komunikat powiadomienia natychmiast - nawet jeśli bashjest w trakcie oczekiwania na zakończenie procesu pierwszoplanowego” (patrz następne odniesienie poniżej).

Aby uzyskać trzeci tryb powiadamiania o kontroli zadań między nimi set +b(tryb domyślny) i set -b(aby uzyskać natychmiastowe powiadomienia o zakończeniu pracy bez uszkodzenia tego, co już wpisałeś w bieżącym wierszu poleceń - podobnie jak ctrl-x ctrl-v), wymagana jest łatka bashautorstwa Simona Tathama (dla sama łatka i dalsze informacje patrz: Sensowne asynchroniczne powiadomienie o zadaniu w bash (1) ).

Więc powtórzmy Matteo Italia„s gdb-FU dla bashskorupy, która została ustawiona do natychmiastowego powiadomienia o wypowiedzeniu z pracy 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
phron
źródło
chłodny! ale czy wierzysz, że może być jakiś inny sposób? Próbuję tego: pid="$(sh -c 'cat "$fileName" |less & echo ${!}')"ale mniej się nie pojawi
Aquarius Power