プロセスを強制終了した後、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を使用しています。

回答:


24

短い答え

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)呼び出しdash)の結果ですREPLループで、ユーザーに新しい入力を要求する前のみ。

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

さて、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

死んだジョブと共同をチェックする呼び出し。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、前の説明が保持されます(プロセスはまだデッドではありません)。


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シェルオプションを使用するset -bか、set -o notifyそれぞれを使用して、ジョブの終了通知をすぐに受け取ることができます。

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

中間のジョブ制御通知の3番目のモードset +b(デフォルトモード)set -bを取得するには(現在のコマンドラインで既に入力した内容を破損せずに即時ジョブ終了通知を取得するには-に似ていますctrl-x ctrl-vbashサイモンタサムによるパッチが必要です(パッチ自体および詳細については、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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.