为何Bash会将通过&启动的进程短暂置于前台进程组?
咱们先把你遇到的核心现象和测试数据理清楚:
带PID的测试输出:
tcgetpgrp=29148 getpgrp=29148 getpid=29149
休眠后再次输出:
tcgetpgrp=28566 getpgrp=29148 getpid=29149
(其中28566是Bash的PID,29149是你的测试程序PID,29148是测试程序的进程组ID)
这个现象只在**(./test&)(子Shell中启动后台进程)且系统高负载**时出现,直接用./test&启动从未触发,你想知道Bash 4.4.12的这种行为是否合规。
先明确Bash处理后台进程的常规逻辑
正常情况下,Bash的行为完全符合POSIX标准:
- 不带
&运行外部命令:Bash会为命令创建新进程组,通知终端将该进程组设为前台进程组,直到命令执行完毕,才恢复自身为前台进程组。 - 带
&运行外部命令:Bash同样为命令创建新进程组,但不会将其设为前台进程组,自身始终保持终端前台进程组的身份。
子Shell场景下的特殊逻辑(关键原因)
你用的(./test&)是在子Shell中启动后台进程,这里的子Shell本身是一个前台执行的命令块,POSIX标准要求Shell为这类前台命令块创建新的进程组,并将其设为终端的前台进程组。具体流程是:
- 父Bash创建子Shell进程(假设PID为29148),并将这个子Shell的进程组设为终端前台进程组。
- 子Shell内部执行
./test&:创建你的测试程序进程(PID29149),并将它加入子Shell的进程组(所以测试程序的进程组ID是29148)。 - 子Shell完成后台进程启动后会退出,此时父Bash会将自身的进程组(28566)恢复为终端前台进程组。
高负载下的时序问题(为什么会看到中间状态)
在系统负载不高时,子Shell的退出速度很快,测试程序还没来得及执行tcgetpgrp,父Bash就已经恢复了前台进程组,所以你看不到这个中间状态。但高负载下,进程调度延迟会让测试程序在子Shell退出前就执行了第一次tcgetpgrp,此时终端的前台进程组还是子Shell的进程组(29148),所以输出tcgetpgrp=29148;等到休眠结束后,子Shell已经退出,父Bash恢复前台,第二次tcgetpgrp就读到了Bash的PID(28566)。
合规性判断
Bash 4.4.12的这个行为完全符合POSIX标准:
- 子Shell作为前台命令块被设为前台进程组,是POSIX明确要求的行为。
- 后台进程(你的测试程序)最终并没有被留在前台进程组:子Shell退出后,父Bash立即恢复自身为前台,测试程序的进程组会成为后台进程组,不会接收终端的键盘信号(比如Ctrl+C),这符合
&运算符的预期。 - 你捕捉到的只是高负载下的临时中间状态,并非Bash的最终行为违反标准。
另外,直接用./test&时,父Bash不会创建新的前台进程组,所以测试程序启动时前台进程组就是Bash的PID,自然不会出现状态变化,这也和常规逻辑一致。
内容的提问来源于stack exchange,提问作者Igor Liferenko

