bash 如何在接收到 Ctrl-C 时识别由前台进程(脚本)启动的后台子进程

bash 如何在接收到 Ctrl-C 时识别由前台进程(脚本)启动的后台子进程

我的问题与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 发送到前台进程组时,我当时看不到任何显示“别杀我,我是后台进程”的“标志”。

我猜正在发生以下情况:

  1. SIGINT被发送到前台进程组

  2. 第一个接收信号并对其做出反应的进程是 PID 等于 TPGID(前台进程组组长)的进程。

  3. 当该进程终止时,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+cCtrl+ 的影响\

在 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 会将终端的前台进程组改回它自己的。

相关内容