Mengapa bash menunjukkan 'Dihentikan' setelah membunuh suatu proses?

17

Inilah perilaku yang ingin saya pahami:

$ 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

Mengapa ini menunjukkan [1]+ Terminated: 15 xargssetelah saya membunuh suatu proses, alih-alih tidak menunjukkannya karena baru saja dibunuh?

Saya menggunakan bash di Mac OS X 10.7.5.

syntagma
sumber

Jawaban:

24

Jawaban singkat

Dalam bash(dan dash) berbagai "status pekerjaan" pesan tidak ditampilkan dari penangan sinyal, tetapi memerlukan pemeriksaan eksplisit. Pemeriksaan ini dilakukan hanya sebelum prompt baru disediakan, mungkin tidak mengganggu pengguna saat dia mengetik perintah baru.

Pesan tidak ditampilkan tepat sebelum prompt setelah killditampilkan mungkin karena prosesnya belum mati - ini adalah kondisi yang sangat mungkin karena killmerupakan perintah internal shell, jadi sangat cepat untuk mengeksekusi dan tidak perlu forking.

Melakukan percobaan yang sama dengan killall, sebagai gantinya, biasanya menghasilkan pesan "dibunuh" segera, tanda bahwa waktu / konteks beralih / apa pun yang diperlukan untuk mengeksekusi perintah eksternal menyebabkan penundaan yang cukup lama untuk proses untuk dibunuh sebelum kontrol kembali ke 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
$ 

Jawaban panjang

dash

Pertama-tama, saya melihat dashsumber - sumbernya, karena dashmenunjukkan perilaku yang sama dan kodenya jelas lebih sederhana dari itu bash.

Seperti dikatakan di atas, intinya tampaknya bahwa pesan status pekerjaan tidak dipancarkan dari pengendali sinyal (yang dapat mengganggu aliran kontrol shell "normal"), tetapi mereka adalah konsekuensi dari pemeriksaan eksplisit ( showjobs(out2, SHOW_CHANGED)panggilan masuk dash) yang dilakukan hanya sebelum meminta input baru dari pengguna, dalam loop REPL.

Jadi, jika shell diblokir menunggu input pengguna, tidak ada pesan seperti itu yang dipancarkan.

Sekarang, mengapa cek tidak dilakukan sesaat setelah pembunuhan menunjukkan bahwa proses itu benar-benar dihentikan? Seperti dijelaskan di atas, mungkin karena terlalu cepat. killadalah perintah internal shell, jadi sangat cepat untuk dieksekusi dan tidak perlu forking, sehingga, segera setelah killpemeriksaan dilakukan, prosesnya masih hidup (atau, setidaknya, masih dibunuh).


bash

Seperti yang diharapkan, bashmenjadi cangkang yang jauh lebih kompleks, lebih rumit dan membutuhkan beberapa gdbfu.

Backtrace ketika pesan itu dipancarkan adalah sesuatu seperti

(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

Panggilan yang memeriksa pekerjaan mati & rekan. is notify_of_job_status(kurang lebih sama dengan showjobs(..., SHOW_CHANGED)di dash); # 0 # 1 terkait dengan pekerjaan batinnya; 6-8 adalah kode parser yang dihasilkan yacc; 10-12 adalah loop REPL.

Tempat yang menarik di sini adalah # 4, yaitu dari mana notify_and_cleanuppanggilan itu berasal. Tampaknya bash, tidak seperti dash, dapat memeriksa pekerjaan yang dihentikan pada setiap karakter yang dibaca dari baris perintah, tapi inilah yang saya temukan:

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

Jadi, dalam mode interaktif itu sengaja untuk menunda pemeriksaan sampai prompt baru disediakan, mungkin tidak mengganggu pengguna memasukkan perintah. Adapun mengapa cek tidak melihat proses mati saat menampilkan prompt baru segera setelah kill, penjelasan sebelumnya berlaku (proses belum mati).

Matteo Italia
sumber
5

Untuk menghindari pesan pemutusan hubungan kerja (pada baris perintah dan juga dalam psoutput), Anda dapat menempatkan perintah yang akan di-background menjadi sebuah sh -c 'cmd &'konstruk.

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

Omong-omong, adalah mungkin untuk mendapatkan pemberitahuan pemutusan kerja langsung bashdengan menggunakan opsi shell set -batau set -o notifymasing - masing.

Dalam hal ini " bashmenerima SIGCHLDsinyal, dan penangan sinyalnya segera menampilkan pesan notifikasi - meskipun bashsaat ini sedang menunggu proses foreground selesai" (lihat referensi berikutnya di bawah).

Untuk mendapatkan mode ketiga pemberitahuan kontrol pekerjaan inbetween set +b(mode default) dan set -b(sehingga Anda mendapatkan pemberitahuan pemutusan kerja langsung tanpa merusak apa yang sudah Anda ketikkan pada baris perintah Anda saat ini - mirip dengan ctrl-x ctrl-v) memerlukan tambalan basholeh Simon Tatham (untuk tambalan itu sendiri dan informasi lebih lanjut silakan lihat: Pemberitahuan pekerjaan asinkron yang masuk akal di bash (1) ).

Jadi hanya mari kita ulangi Matteo Italia's gdbfu untuk bashshell yang telah ditetapkan untuk memberitahu penghentian pekerjaan segera dengan 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
sumber
keren! tetapi apakah Anda percaya ada cara lain? Saya mencoba ini: pid="$(sh -c 'cat "$fileName" |less & echo ${!}')"tetapi kurang muncul
Aquarius Power