为什么 bash 在终止进程后显示“已终止”?

为什么 bash 在终止进程后显示“已终止”?

这是我想理解的行为:

$ 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

为什么它在我杀死一个进程后显示[1]+ Terminated: 15 xargs,而不是在它刚刚被杀死时不显示它?

我在 Mac OS X 10.7.5 上使用 bash。

答案1

简短回答

bash(和dash)中,各种“作业状态”消息不会从信号处理程序中显示,但需要显式检查。仅在提供新提示之前执行此检查,可能不会在用户键入新命令时打扰他/她。

该消息不会在kill显示后的提示之前显示,可能是因为该进程尚未死亡 - 这是特别可能的情况,因为它kill是 shell 的内部命令,因此执行速度非常快并且不需要分叉。

相反,使用 进行相同的实验killall通常会立即产生“killed”消息,表明时间/上下文切换/执行外部命令所需的任何内容都会导致足够长的延迟,以便在控制权返回到 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
$ 

长答案

dash

首先,我查看了dash源代码,因为dash表现出相同的行为,并且代码肯定比bash.

如上所述,重点似乎是作业状态消息不是从信号处理程序发出的(这可以中断“正常”shell 控制流),但它们是执行的显式检查(showjobs(out2, SHOW_CHANGED)调用)的结果dash仅在 REPL 循环中向用户请求新输入之前。

因此,如果 shell 被阻止等待用户输入,则不会发出此类消息。

现在,为什么在终止后执行的检查没有显示进程实际上已终止?如上所述,可能是因为它太快了。kill是 shell 的内部命令,因此执行速度非常快,并且不需要分叉,因此,在kill执行检查后立即,进程仍然处于活动状态(或者至少仍然被杀死)。


bash

正如预期的那样bash,作为一个更加复杂的 shell,更加棘手并且需要一些gdb-fu。

发出该消息时的回溯类似于

(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

检查死亡职位和公司的电话。 is notify_of_job_status(或多或少相当于showjobs(..., SHOW_CHANGED)in dash); #0-#1与其内部工作有关; 6-8是yacc生成的解析器代码; 10-12 是 REPL 循环。

这里有趣的地方是#4,即呼叫notify_and_cleanup来自的地方。bash与 不同的是,似乎dash可以在从命令行读取的每个字符处检查终止的作业,但这是我发现的:

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

所以,在交互模式下故意的延迟检查直到提供新的提示,可能不会打扰用户输入命令。至于为什么在 后立即显示新提示时检查没有发现死进程kill,前面的解释成立(进程尚未死)。

答案2

为了避免任何作业终止消息(在命令行以及ps输出中),您可以将要后台运行的命令放入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
}

顺便说一句,可以分别bash使用 shell 选项set -b或来立即获取作业终止通知set -o notify

在这种情况下,“bash收到SIGCHLD信号,其信号处理程序立即显示通知消息 - 即使bash当前正在等待前台进程完成”(请参阅​​下面的下一个参考)。

要获得介于set +b(默认模式)和set -b(以便您立即获得作业终止通知而不会破坏您已在当前命令行上键入的内容 - 类似于)之间的作业控制通知的第三种模式,需要由 Simon Tathamctrl-x ctrl-v进行修补(对于bash补丁本身和更多信息请参阅:bash 中明智的异步作业通知(1))。

因此,让我们对已设置为立即通知作业终止的 shell重复Matteo Italia-fu 。gdbbashset -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

相关内容