ทำไมทุบตีแสดง 'ยุติ' หลังจากฆ่ากระบวนการ?


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

คำตอบ:


24

คำตอบสั้น ๆ

ในbash(และdash) ข้อความ "สถานะงาน" ต่าง ๆ จะไม่แสดงจากตัวจัดการสัญญาณ แต่ต้องมีการตรวจสอบอย่างชัดเจน การตรวจสอบนี้จะดำเนินการก่อนที่จะได้รับพรอมต์ใหม่อาจไม่รบกวนผู้ใช้ในขณะที่เขา / เธอกำลังพิมพ์คำสั่งใหม่

ข้อความไม่ปรากฏขึ้นก่อนที่พรอมต์killจะปรากฏขึ้นเนื่องจากกระบวนการยังไม่ตาย - นี่เป็นเงื่อนไขที่เป็นไปได้โดยเฉพาะเนื่องจากkillเป็นคำสั่งภายในของเชลล์ดังนั้นจึงรวดเร็วในการดำเนินการและไม่ต้องการการฟอร์ก

ทำการทดลองแบบเดียวกันโดยkillallปกติจะให้ข้อความ "ถูกฆ่า" ทันทีลงชื่อว่าเวลา / บริบทสวิตช์ / สิ่งที่จำเป็นในการดำเนินการคำสั่งภายนอกทำให้เกิดความล่าช้านานพอที่กระบวนการจะถูกฆ่าก่อนที่การควบคุมจะกลับสู่เชลล์ .

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การตรวจสอบถูกดำเนินการกระบวนการยังมีชีวิตอยู่ (หรืออย่างน้อยก็ยังคงถูกฆ่า)


bash

อย่างที่คาดไว้bashการเป็นเปลือกที่ซับซ้อนมากขึ้นนั้นยากกว่าและจำเป็นต้องมีgdb-fu

backtrace สำหรับเมื่อข้อความถูกปล่อยออกมาเป็นสิ่งที่ต้องการ

(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)ใน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ขณะนี้อยู่ระหว่างการรอให้กระบวนการพื้นหน้าให้เสร็จสมบูรณ์" (ดูการอ้างอิงถัดไปด้านล่าง)

หากต้องการรับโหมดที่สามของการแจ้งเตือนการควบคุมงานในระหว่างset +b(โหมดเริ่มต้น) และset -b(เพื่อให้คุณได้รับการแจ้งเตือนการเลิกงานทันทีโดยไม่ทำลายสิ่งที่คุณได้พิมพ์ลงบนบรรทัดคำสั่งปัจจุบันของคุณ - คล้ายกับctrl-x ctrl-v ) ต้องใช้โปรแกรมแก้ไขbashโดย Simon Tatham แพตช์เองและข้อมูลเพิ่มเติมโปรดดูที่: การแจ้งเตือนงานแบบอะซิงโครนัสที่เหมาะสมใน bash (1) )

ดังนั้นเพียงแค่ให้ทำซ้ำของMatteo Italia's gdb-fu สำหรับเปลือกหอยที่ได้รับการตั้งค่าให้แจ้งการเลิกจ้างงานทันทีด้วยbashset -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.