我遇到了一个问题,shell 需要等待前台作业(job)
结束,这可能由多个进程组成.看起来我需要
使用waitid‘等待’‘进程组中的
工作啊.
仅依靠POSIX功能是不可行的,而且POSIX没有指定它.
try 编译此程序并通过Bash在前台运行它(例如):
#include <unistd.h>
#include <stdlib.h>
int main(void) {
pid_t child_pid = fork();
if (child_pid == 0) {
sleep(10);
}
}
注意到,当子进程(其孙子进程)仍在运行时,shell 不会等待子进程完成,而是返回到前台并立即显示一个新的命令提示符.一些系统,包括Linux,确实提供了允许shell 等待进程组中的所有进程完成的特性,但这并不是shell 实际所做的.
以下大部分内容都集中在POSIX的世界观上.
我首先编写了以下代码:
pid_t pid;
while ((pid = waitpid(-pgid, NULL, 0)) > 0) {
// Do some work
}
if (errno == ECHILD) {
// Now we know all processes in the group have finished
}
如果没有帮助,等待一个进程组的所有进程都终止是不可靠的,因为pid 1以外的进程只能等待它自己的子进程,而不能等待它的孙进程或更远的后代进程. 如果一个进程的生命周期 超过了它的父进程,那么收集它的能力和责任就落到pid 1.
然而,这是典型的空壳行为.它将收集作为指定进程组成员的所有shell 的子进程.每个子元素都负责收集自己的子元素,如果有子元素是孤儿,那么他们就会越过shell 的作业(job)控制范围.
但后来我开始怀疑:因为进程ID可以在
进程终止,我认为进程组ID也会终止,
这是真的,但没有实际意义.新进程组的进程组ID被分配为组中第一个进程的ID(就POSIX而言).PGID确实可以重复使用,因为PID可以重复使用.但是,对于waitpid()
来说,无论是pid重用还是pgid重用都不是问题,因为系统不依赖它们来确定进程的父/子关系,并且waitpid()
只收集调用进程的子进程.
此外,PID严格增加,直到它们被缠绕,因此在重复使用之前通常会有相当长的延迟.
很有可能
在组中的最后一个进程被While循环取值wait
之后
在下一个循环开始之前,将承载一个新进程,其进程
组ID也是pgid
.
如果你所说的"机会"指的是"有机会",那么是的.但如果你的意思是"很有可能",那就不,一点也不.即使自创建原始过程组以来,ID号have绕回也不会.即使发生这种情况,对于您的特定代码来说也不是问题,因为新进程组中的进程将不会受到您的waitpid()
调用的影响,尽管pgid.
在这种情况下,循环将继续等待
对于新的流程,尽管它实际上不属于前者
一群人.
不是的.一个进程只能等待它自己的子进程.您可以将它们中的哪一个等待给属于特定进程组的进程,但除非您是PID1,否则您不能等待不是您的子进程的进程.
事实上,我认为使用服务员并不能解决问题.
waitpid()
没有你认为的那样的问题.
后来,注意到child subreaper是用来处理子女和孙辈的,[...]
Subbreaper是一个Linux特有的特性,通常不用于shell作业(job)控制(即使在Linux上). 但是如果你真的使用了它,那么最自然的方式就是让你的shell把自己设置成一个subreaper,在这种情况下,它的孤立后代将落在它身上收集.这将允许你原来的waitpid()
收集所有的后代,就像你认为的那样,但是不会允许它收集任何不是它的后代的进程,不管它们的pgid是什么.
我的问题是,1)有什么简单的方法可以解决这个问题吗?
您已经在做的事情已经达到了收集由shell 启动的作业(job)的进程组的所有成员的目的,就像shell 通常所做的那样.
还是什么复杂的方法?
在Linux上,如果您还想收集孤立的子代,那么您可以使用prctl()
和PR_SET_CHILD_SUBREAPER
来使您的shell 的一个实例成为子收割器,然后继续您已经在做的事情.实际上,这并不比这复杂多少.但你不应该这样,因为这不是贝壳通常的行为方式.
- 还是我对这个问题的关注
不必要的?4)或者,问题实际上不会发生?
您特别担心您的shell 可能会try 等待它不应该等待的进程,这是没有根据的.重新使用PID和/或PGID将不会产生这样的效果.
Glibc手册中有a detailed discussion about implementing a job-control shell个.你可能会发现它很有帮助.