Tại sao bash hiển thị 'Chấm dứt' sau khi giết một quá trình?


17

Đây là hành vi tôi muốn hiểu:

$ 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

Tại sao nó hiển thị [1]+ Terminated: 15 xargssau khi tôi giết một quá trình, thay vì chỉ hiển thị nó như nó vừa bị giết?

Tôi đang sử dụng bash trên Mac OS X 10.7.5.

Câu trả lời:


24

Câu trả lời ngắn

Trong bash(và dash) các thông báo "trạng thái công việc" khác nhau không được hiển thị từ các trình xử lý tín hiệu, nhưng yêu cầu kiểm tra rõ ràng. Kiểm tra này chỉ được thực hiện trước khi một lời nhắc mới được cung cấp, có thể không làm phiền người dùng trong khi anh ta / cô ta đang gõ một lệnh mới.

Thông báo không được hiển thị ngay trước lời nhắc sau khi killhiển thị có thể do quá trình chưa chết - đây là điều kiện đặc biệt có thể xảy ra do killlà một lệnh nội bộ của shell, vì vậy nó rất nhanh để thực thi và không cần phải giả mạo.

killallThay vào đó, thực hiện cùng một thử nghiệm với , thường mang lại thông báo "bị giết" ngay lập tức, ký hiệu rằng công tắc thời gian / bối cảnh / bất cứ điều gì cần thiết để thực hiện lệnh bên ngoài gây ra độ trễ đủ lâu để quá trình bị hủy trước khi điều khiển quay trở lại trình bao .

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
$ 

Câu trả lời dài

dash

Trước hết, tôi đã xem xét các dashnguồn, vì dashthể hiện hành vi tương tự và mã chắc chắn đơn giản hơn bash.

Như đã nói ở trên, điểm có vẻ là các thông báo trạng thái công việc không được phát ra từ bộ xử lý tín hiệu (có thể làm gián đoạn luồng điều khiển vỏ "bình thường"), nhưng chúng là hậu quả của việc kiểm tra rõ ràng (một showjobs(out2, SHOW_CHANGED)cuộc gọi trong dash) được thực hiện chỉ trước khi yêu cầu đầu vào mới từ người dùng, trong vòng REPL.

Do đó, nếu shell bị chặn chờ người dùng nhập vào thì không có thông báo nào được phát ra.

Bây giờ, tại sao kiểm tra không được thực hiện ngay sau khi kill cho thấy quá trình thực sự bị chấm dứt? Như đã giải thích ở trên, có lẽ vì nó quá nhanh. killlà một lệnh nội bộ của shell, vì vậy nó rất nhanh để thực thi và không cần phải sử dụng, do đó, ngay sau khi killkiểm tra được thực hiện, quy trình vẫn còn sống (hoặc, ít nhất, vẫn đang bị giết).


bash

Như mong đợi, bashlà một lớp vỏ phức tạp hơn nhiều, khó hơn và cần một số gdb-fu.

Backtrace cho khi thông điệp đó được phát ra là một cái gì đó như

(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

Cuộc gọi kiểm tra các công việc đã chết & co. là notify_of_job_status(nó ít nhiều tương đương với showjobs(..., SHOW_CHANGED)in dash); # 0- # 1 liên quan đến hoạt động bên trong của nó; 6-8 là mã trình phân tích cú pháp do yacc tạo; 10-12 là vòng lặp REPL.

Địa điểm thú vị ở đây là # 4, tức là từ nơi notify_and_cleanupcuộc gọi đến. Dường như bash, không giống như dash, có thể kiểm tra các công việc bị chấm dứt ở mỗi ký tự được đọc từ dòng lệnh, nhưng đây là những gì tôi tìm thấy:

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

Vì vậy, trong chế độ tương tác, cố tình trì hoãn kiểm tra cho đến khi được cung cấp lời nhắc mới, có thể không làm phiền người dùng nhập lệnh. Về lý do tại sao kiểm tra không phát hiện ra quy trình chết khi hiển thị lời nhắc mới ngay sau đó kill, phần giải thích trước đó được giữ lại (quy trình chưa chết).


5

Để tránh bất kỳ thông báo chấm dứt công việc (trên dòng lệnh cũng như trong psđầu ra), bạn có thể đặt lệnh được làm nền trong một sh -c 'cmd &'cấu trúc.

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

Nhân tiện, có thể nhận được thông báo chấm dứt công việc ngay lập tức bashbằng cách sử dụng các tùy chọn shell set -bhoặc set -o notifytương ứng.

Trong trường hợp này " bashnhận được SIGCHLDtín hiệu và trình xử lý tín hiệu của nó sẽ hiển thị thông báo thông báo ngay lập tức - ngay cả khi bashhiện đang trong quá trình chờ quá trình tiền cảnh hoàn tất" (xem tài liệu tham khảo tiếp theo bên dưới).

Để có được chế độ thông báo kiểm soát công việc thứ ba ở giữa set +b(chế độ mặc định) và set -b(để bạn nhận được thông báo chấm dứt công việc ngay lập tức mà không làm hỏng những gì bạn đã nhập trên dòng lệnh hiện tại - tương tự ctrl-x ctrl-v), cần phải có bản vá bashcủa Simon Tatham (cho Bản vá và thông tin thêm xin vui lòng xem: Thông báo công việc không đồng bộ rõ ràng trong bash (1) ).

Vì vậy chỉ cần chúng ta hãy lặp lại Matteo Italia's gdb-fu cho một bashvỏ đã được thiết lập để thông báo về việc chấm dứt công việc ngay lập tức với 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

mát mẻ! Nhưng bạn có tin rằng có thể có một số cách khác? Tôi đang thử điều này: pid="$(sh -c 'cat "$fileName" |less & echo ${!}')"nhưng sẽ không xuất hiện
Sức mạnh Bảo Bình
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.