有如下shell 腳本 pingoo.sh
#!/bin/bash
while true
do
ping 127.0.0.1
done;
$> pingoo.sh
鍵盤鍵入 ctrl +c(^c)
ooooops!
你會(huì)發(fā)現(xiàn)無論你按多少次都無法終止該腳本的運(yùn)行.
使用ps 命令找到進(jìn)程樹
$> ps -j f
PID PGID SID TTY STAT TIME COMMAND
21994 21994 21994 pts/29 Ss 0:00 /bin/bash
28833 28833 21994 pts/29 S+ 0:00 \_ /bin/bash ./pingoo.sh
28834 28833 21994 pts/29 S+ 0:00 \_ ping 127.0.0.1
STAT 進(jìn)程狀態(tài)標(biāo)志:
S: 可中斷的sleep,
s: session leader;
+: 前臺(tái)進(jìn)程;
首先要明確一個(gè)重要假設(shè):當(dāng)用戶按下Ctrl c 時(shí), 他是要終止前臺(tái)進(jìn)城組的運(yùn)行, 出發(fā)進(jìn)程注冊(cè)了自定義的信號(hào)處理函數(shù);
但是如果子進(jìn)程capture sigint, 則 shell 會(huì)假設(shè)用戶通過發(fā)送sigint 使子進(jìn)程做特定工作(比如ping的統(tǒng)計(jì)信息), 也就說用戶發(fā)送sigint的目的并不是結(jié)束前臺(tái)進(jìn)程組, 而是觸發(fā)特定動(dòng)作;ping 就是這類安裝了自定義的信號(hào)處理函數(shù)的進(jìn)程;
當(dāng)你按下ctrl c后發(fā)生了什么?
ctrl +c 被終端驅(qū)動(dòng)程序解釋為 sigint 信號(hào), 由kernel 發(fā)送給 前臺(tái)進(jìn)程組 . ping 進(jìn)程 capture 該信號(hào), 調(diào)用信號(hào)處理函數(shù)。 同時(shí), ping的父進(jìn)程也收到sigint信號(hào)(父進(jìn)程處理interruptible sleep 狀態(tài), 也就出出于wait系統(tǒng)調(diào)用中), 父進(jìn)程被迫退出wait系統(tǒng)調(diào)用,檢查退出原因(是否是EINTR,也就是中斷的系統(tǒng)調(diào)用),然后通過WIFSIGNALED宏可以判斷子進(jìn)程是否注冊(cè)了信號(hào)處理函數(shù) 。如果 注冊(cè)了信號(hào)處理函數(shù),capture sigint, 父進(jìn)程繼續(xù)運(yùn)行(當(dāng)前例子是開始下一次循環(huán)); 如果是子進(jìn)程是因收到sigint信號(hào)終止的(按照default 方式處理sigint), 父進(jìn)程會(huì)終止運(yùn)行.
當(dāng)用戶鍵入ctrl+c后,tty driver 產(chǎn)生SIGINT信號(hào)給前臺(tái)進(jìn)程, pingoo.sh和其子進(jìn)程 都看見sigint信號(hào),(他們是前臺(tái)進(jìn)程組成員)。 ping.sh作為父進(jìn)程處于waitpid blocking狀態(tài), 收到sigint 后waitpid 立即返回 -1, 設(shè)置errorno為 EINTR。同時(shí) bash要求child 立即死亡(也就是sigint的默認(rèn)動(dòng)作 terminate)。最后設(shè)置 child_caught_sigint = 0, bash 隨后會(huì)根據(jù)該flag 退出;
如果child capture signal,設(shè)置 child_caught_sigint = 1; bash 根據(jù)該flag 不退出, 因?yàn)樽舆M(jìn)程capture sigint后, 觸發(fā)信號(hào)處理函數(shù)。 其信號(hào)處理函數(shù)需要做特定處理,如果子進(jìn)程退出,則 父進(jìn)程會(huì)受到 sigchld 信號(hào),父進(jìn)程會(huì)認(rèn)為子進(jìn)程正常退出, 循環(huán)繼續(xù)。
源代碼解析:
bash 4.3.3: jobs.c
- waitpid
pid<-1 等待進(jìn)程組識(shí)別碼為 pid 絕對(duì)值的任何子進(jìn)程。
pid=-1 等待任何子進(jìn)程,相當(dāng)于 wait()。
pid=0 等待進(jìn)程組識(shí)別碼與目前進(jìn)程相同的任何子進(jìn)程。
pid>0 等待任何子進(jìn)程識(shí)別碼為 pid 的子進(jìn)程。
- WIFSIGNALED:
This macro returns a nonzero value if the child process terminated because it received a signal that was not handled.- WTERMSIG:
IfWIFSIGNALEDis true of <var style="background-color: inherit;">status</var>, this macro returns the signal number of the signal that terminated the child process
/* If waitpid returns -1/EINTR and the shell saw a SIGINT, then we
assume the child has blocked or handled SIGINT. In that case, we
require the child to actually die due to SIGINT to act on the
SIGINT we received; otherwise we assume the child handled it and
let it go. */
// status = waitpid(pid), pid <0
if (pid < 0 && errno == EINTR && wait_sigint_received)
child_caught_sigint = 1; //waitpid被 sigint中斷,立即 假設(shè)此時(shí) 子進(jìn)程也 capture signit
if (pid <= 0)
continue; /* jumps right to the test */
/* If the child process did die due to SIGINT, forget our assumption
that it caught or otherwise handled it. */
if (WIFSIGNALED (status) && WTERMSIG (status) == SIGINT)
child_caught_sigint = 0;
if (JOBSTATE (job) == JDEAD)
{
/* If we're running a shell script and we get a SIGINT with a
SIGINT trap handler, but the foreground job handles it and
does not exit due to SIGINT, run the trap handler but do not
otherwise act as if we got the interrupt. */
// wait_sigint_received : wait 阻塞中收到sigint,
// child_caught_sigint : 子進(jìn)程 capture sigint
// IS_FOREGROUND : 前臺(tái)進(jìn)程組
// interactive_shell : 交互shell?
// signal_is_trapped :
if (wait_sigint_received && interactive_shell == 0 &&
child_caught_sigint && IS_FOREGROUND (job) &&
signal_is_trapped (SIGINT))
{
int old_frozen;
wait_sigint_received = 0;
last_command_exit_value = process_exit_status (child->status);
old_frozen = jobs_list_frozen;
jobs_list_frozen = 1;
tstatus = maybe_call_trap_handler (SIGINT); //
jobs_list_frozen = old_frozen;
}
如何終止該腳本的運(yùn)行呢:
$> kill -9 28833 28834
一定要先殺死父進(jìn)程