web-dev-qa-db-ja.com

プロセスを強制終了した後、bashに「Terminated」と表示されるのはなぜですか?

これが私が理解したい行動です:

$ 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を使用しています。

17
syntagma

短い答え

bash(およびdash)では、さまざまな「ジョブステータス」メッセージがシグナルハンドラーから表示されませんが、明示的なチェックが必要です。このチェックは、新しいプロンプトが提供される前にのみ実行されます。おそらく、ユーザーが新しいコマンドを入力しているときにユーザーの邪魔にならないようにします。

killが表示された後のプロンプトの直前にメッセージが表示されないのは、おそらくプロセスがまだ終了していないためと考えられます。killはシェルの内部コマンドであるため、これは特に可能性の高い状態です。実行が非常に速く、フォークする必要はありません。

代わりにkillallを使用して同じ実験を行うと、通常はすぐに「killed」メッセージが表示され、時間/コンテキストの切り替え/外部コマンドを実行するために必要なものが、プロセスが終了するまでの十分な遅延が発生することを示します。コントロールがシェルに戻ります。

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ソースを確認しました。

上記のように、ポイントはジョブステータスメッセージがシグナルハンドラーから発行されない(「通常の」シェル制御フローを中断する可能性がある)ようですが、これらは明示的なチェックの結果です(showjobs(out2, SHOW_CHANGED) REPLループで、ユーザーからの新しい入力を要求する前にのみ実行されるdash)を呼び出します。

したがって、シェルがユーザー入力を待機してブロックされている場合、そのようなメッセージは出力されません。

さて、なぜkillの直後に実行されたチェックが、プロセスが実際に終了したことを示さないのですか?上で説明したように、おそらく速すぎるためです。 killはシェルの内部コマンドであるため、実行が非常に速く、フォークする必要はありません。したがって、killの直後にチェックが実行されても、プロセスはまだ生きています(または、少なくともまだ殺されている)。


bash

予想通り、より複雑なシェルであるbashは扱いにくく、いくつかの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です(多かれ少なかれdashshowjobs(..., SHOW_CHANGED)と同等です); #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の直後に新しいプロンプトを表示するときに、チェックが停止したプロセスを特定しない理由については、前の説明が当てはまります(プロセスはまだ停止していません)。

24
Matteo Italia

(コマンドラインと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
}

ちなみに、シェルオプションset -bまたはset -o notifyをそれぞれ使用することで、bashのジョブ終了通知をすぐに取得できます。

この場合、「bashSIGCHLDシグナルを受信し、そのシグナルハンドラーは、フォアグラウンドプロセスの待機を現在bashが待機している場合でも、すぐに通知メッセージを表示します。完了」(次のリファレンスを参照)。

set +b(デフォルトモード)とset -bの間にあるジョブ制御通知の3番目のモードを取得するには(現在のコマンドラインで入力した内容を壊すことなく、即時のジョブ終了通知を取得します。 ctrl-x ctrl-v)には、Simon Tathamによるbashへのパッチが必要です(パッチ自体と詳細については、次を参照してください: bash(1) の適切な非同期ジョブ通知)。

そのため、Matteo Italiaですぐにジョブの終了を通知するように設定されているgdbシェルに対して、set -bbash- fuを繰り返してみましょう。

# 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
5
phron