我的问题与WCE(等待和合作退出)无关。假设我在交互式 shell (bash) 中启动了一个脚本作为前台作业:
#! /bin/bash
# script name: foreback.sh
sleep 100m & # child process in bg
sleep 200m & # ditto
sleep 300m & # ditto
sleep 7000m # child process in fg
exit 0
当此脚本运行时,按 Ctrl-C 会杀死所有前台进程(我的脚本的进程 - 父进程 - 以及预期的第四个睡眠子进程。
我的问题是:当前台进程组收到 SIGINT 时,如何识别这些后台子进程?
在发送信号之前查看以下 ps - 输出:
TT TPGID PPID PID PGID SESS STAT COMMAND
pts/0 9373 9259 9282 9282 9282 Ss | \_ /bin/bash
pts/0 9373 9282 9373 9373 9282 S+ | | \_ /bin/bash ./foreback.sh
pts/0 9373 9373 9374 9373 9282 S+ | | \_ sleep 100m
pts/0 9373 9373 9375 9373 9282 S+ | | \_ sleep 200m
pts/0 9373 9373 9376 9373 9282 S+ | | \_ sleep 300m
pts/0 9373 9373 9377 9373 9282 S+ | | \_ sleep 7000m
父进程和子进程似乎是某种“前台复合体”,因为它们都属于同一个终端进程组 (TPGID),即父前台进程的 PID,并且 STAT 列显示一个加号。当通过Ctrl-C向前台进程组发送SIGINT或通过kill -INT向进程组(PGID)发送SIGINT时 -- -PGID,shell 如何知道哪些进程要终止,哪些进程要保持活动状态?在 Ctrl-C 或上面提到的进程组终止之后,我的 ps - 输出如下所示:
TT TPGID PPID PID PGID SESS STAT COMMAND
pts/0 9282 9259 9282 9282 9282 Ss+ | \_ /bin/bash
pts/0 9282 1742 9374 9373 9282 S \_ sleep 100m
pts/0 9282 1742 9375 9373 9282 S \_ sleep 200m
pts/0 9282 1742 9376 9373 9282 S \_ sleep 300m
从父进程在后台启动的三个子进程仍然处于活动状态,STAT 列通过缺少的加号指示它们处于后台,并且终端进程组现在是交互式 shell 的组。事情应该是这样的。
但当 SIGINT 发送到前台进程组时,我当时看不到任何显示“别杀我,我是后台进程”的“标志”。
我猜正在发生以下情况:
SIGINT被发送到前台进程组
第一个接收信号并对其做出反应的进程是 PID 等于 TPGID(前台进程组组长)的进程。
当该进程终止时,shell 会“记住”哪些子进程作为后台进程启动,并将它们的 TPGID 从原始的 TPGID 更改为交互式 shell 的 TPGID,以便它们不再属于旧的前台进程组。未启动且未经历 TPGID 更改的剩余子级,接收 SIGINT 并对其做出反应(如果不进行其他处理则终止)
几周以来,我浏览了无数的网站,但找不到正确的答案。
有任何想法吗????谢谢
答案1
默认情况下,非交互式 shell 不进行作业控制,因此脚本运行的所有进程都与执行 shell 的进程位于同一进程组中。该进程组将由交互式 shell 启动脚本而成为终端的前台进程组,具体取决于脚本是否已启动/放入前台。
但是当作业控制被禁用时,POSIX shell 会这个要求:
2.11.信号和错误处理
如果当 shell 执行异步列表时禁用作业控制(请参阅 set -m 的说明),则列表中的命令将从 shell 继承 SIGINT 和 SIGQUIT 信号的忽略 (SIG_IGN) 信号操作。在所有其他情况下,shell 执行的命令应继承与 shell 从其父级继承的信号操作相同的信号操作,除非信号操作被 trap 特殊内置函数修改(请参阅 trap)
因此,实际上它是“作业”控制的一种粗略形式,其中异步列表不受Ctrl+c和Ctrl+ 的影响\。
在 Linux 上:
$ sh -c 'sleep 10 & grep SigIgn "/proc/$!/status"'
SigIgn: 0000000000000006
$ kill -l INT QUIT
2
3
(SigIgn
上面是一个位掩码,在这种情况下设置了第二个和第三个位,对应于 SIGINT 和 SIGQUIT)。
请注意,当终端在按下这些组合键时发送这些^C
或^\
字符时,shell 不参与这些信号的传递。内核(tty 驱动程序)将信号发送到终端前台进程组中的进程。
TPGID 是终端设备的属性(在您的情况下/dev/pts/0
),而不是真正的进程。ps
对于在附加到终端的会话中运行的所有进程,将始终显示相同的值。当将作业(进程组)置于前台时,交互式 shell 将执行以下操作tcsetpgrp(terminal_fd, its_pid)
来告诉终端驱动程序:这是该终端的前台进程组,其他任何内容都在后台。
事实上,幸存的sleep
进程在后台运行^C
只是因为脚本终止了,所以等待它的交互式 shell 会将终端的前台进程组改回它自己的。