我正在学习信号并编写一个简单的shell 程序.我遇到了一个问题,即shell 程序需要等待前台作业(job)结束,这可能由多个进程组成.似乎我需要使用waitpid来‘等待’该作业(job)的进程组中的所有进程.我首先编写了以下代码:

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
}

但随后我开始怀疑:由于进程id可以在进程死后重新使用,我认为进程组id也可以,所以很可能在组中的最后一个进程被While循环取值wait之后,在下一个循环开始之前,会产生一个新的进程,其进程组id也是pgid.在本例中,新流程的循环将继续到wait,尽管它实际上不属于前一组.事实上,我认为使用waitpid并不能解决问题.在最后的waitpid之后,我需要再次使用它来判断是否有更多相同pgid的进程.在这两个对waitpid的调用之间,可以承载具有相同pgid的新进程.

后来,注意到child subreaper用于处理子进程和孙子进程,我想到了一个 idea ,即可以使用进程A专门处理前台作业(job),而使用进程B处理所有其他进程,这样任何新进程(属于进程B)都不会干扰进程A的waitpid.我仍然不知道这个方法是否可行,是否会有进一步的问题.

我的问题是,1)有什么简单的方法来解决这个问题吗?2)或者一些复杂的方法?3)或者,我对这个问题的担忧是不必要的?4)或者,这个问题实际上不会发生?

推荐答案

我遇到了一个问题,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 的一个实例成为子收割器,然后继续您已经在做的事情.实际上,这并不比这复杂多少.但你不应该这样,因为这不是贝壳通常的行为方式.

  1. 还是我对这个问题的关注 不必要的?4)或者,问题实际上不会发生?

您特别担心您的shell 可能会try 等待它不应该等待的进程,这是没有根据的.重新使用PID和/或PGID将不会产生这样的效果.

Glibc手册中有a detailed discussion about implementing a job-control shell个.你可能会发现它很有帮助.

Linux相关问答推荐

为什么我们不能使用${$#}来获取传递给shell 脚本的最后一个参数?

jinja2.exceptions.TemplateSyntaxError:预期标记,,得到整数(支持十六进制,八进制和二进制整数文字)

如何在REPL控制台中使用PowerShell将特定的CSV列转换为TitleCase?

使用awk命令将以:分隔的两个文件合并的方法

进程Forking 后 pthread_key_create() 生成的密钥会发生什么?

为什么在 find 命令中使用 dirname 会 for each 匹配项提供点?

如何判断 Linux 机器是否支持 AVX/AVX2 指令?

如何在makefile中包含静态库

tmux:挂起不加载,不响应任何选项命令

Java 8 上的 SQL Server JDBC 错误:驱动程序无法使用安全套接字层 (SSL) 加密建立与 SQL Server 的安全连接

更改核心转储的位置

bash 中的线程?

如何搜索文件并将它们压缩到一个 zip 文件中

用于数据库备份的 Linux shell 脚本

CLOCK_MONOTONIC 和 CLOCK_MONOTONIC_RAW 有什么区别?

如何在 linux 中使用 CMake 和 Kdevelop 编译 GLUT + OpenGL 项目?

使用 SED 将单词的首字母大写

网络共享文件夹上的 GIT 存储库中的并发性

带有日期和时间的 Linux 命令历史记录

readelf vs. objdump:为什么都需要