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


17

这是我要了解的行为:

$ 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。

Answers:


24

简短答案

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) in调用dash)的结果仅在从用户请求新输入之前,在REPL循环中。

因此,如果外壳被阻塞以等待用户输入,则不会发出任何此类消息。

现在,为什么在终止之后不执行检查就表明进程实际上已终止?如上所述,可能是因为它太快了。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

检查是否有死活的电话&co。是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,前面的解释成立了(该进程尚未死)。


5

为了避免出现任何作业终止消息(在命令行以及在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 -bset -o notify分别。

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

要获得介于set +b(默认模式)和set -b(以便在不破坏您在当前命令行上已经键入的内容的情况下立即获得工作终止通知-类似ctrl-x ctrl-v)之间的第三种工作控制通知模式,需要bashSimon Tatham 提供的补丁修补程序本身和更多信息,请参见:bash(1)中的明智的异步作业通知)。

所以才让的重复Matteo Italiagdb一个-fu bash已设置与立即通知作业终止的壳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

凉!但是您相信还有其他方法吗?我正在尝试:pid="$(sh -c 'cat "$fileName" |less & echo ${!}')"但不会少出现
Aquarius Power
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.