进程间通信,从多个 children stdout 读取

Interprocess communication, reading from multiple children stdout

我正在尝试编写自定义 shell-like 程序,其中可以同时执行多个命令。对于单个命令,这并不复杂。但是,当我尝试同时执行多个命令(每个命令在一个单独的 child 中)并捕获它们的标准输出时,我遇到了问题。

到目前为止我尝试的是在我的 shell 应用程序下我有两个函数可以同时 运行 命令,execute() 接受多个命令并且对于每个命令它 fork()一个child进程来执行命令,subprocess()需要1个cmd并执行它。

void execute(std::vector<std::string> cmds) {
    int fds[2];
    pipe(fds);
    std::pair<pid_t, int> sp;

    for (int i = 0; i < cmds.size(); i++) {
        std::pair<pid_t, int> sp = this->subprocess(cmds[i], fds);
    }

    
    // wait for all children
    while (wait(NULL) > 0);
    close(sp.second);
}

std::pair<pid_t, int> subprocess(std::string &cmd, int *fds) {
    std::pair<pid_t, int> process = std::make_pair(fork(), fds[0]);
    if (process.first == 0) {
        close(fds[0]); // no reading
        dup2(fds[1], STDIN_FILENO);
        close(fds[1]);
        char *argv[] = {"/bin/sh", "-c", cmd.data(), NULL};
        execvp(argv[0], argv);
        exit(0);
    }
    close(fds[1]); // only reading
    return process;
} 

这里的问题是,当我在我的自定义 shell 上执行多个命令时(这里不深入研究细节,但它会在某些时候调用 execute()。)如果我使用 STDIN_FILENO 如上捕获 child 进程 stdout,它会永远写入 shell 的 stdin 捕获的输出是什么,例如

如果输入命令是

echo im done, yet?
echo nope
echo maybe

然后,在写入 STDIN_FILENO 的情况下,输出就像(其中 >>> )是我的用户输入标记。

im done, yet?
nope
maybe
>>> nope
maybe
im done, yet?
>>> im done, yet?
nope
maybe

在写给STDOUT_FILENO的情况下,似乎忽略了其中一个命令(可能是第一个child),我不知道为什么?

maybe
nope
>>> maybe
nope
>>> nope
maybe
>>> maybe
nope
>>> nope

所以,我认为潜在的东西在我的 shell 中,我在 while 循环中使用 std::cin >> ... 进行用户输入,这可能与标准输入案例有某种冲突。另一方面,在主进程中(parent)我正在等待所有 children 退出,所以 children 不知何故没有退出,但 child 应该在 execvp 之后死掉,对吗?而且,我在主进程close(sp.second)关闭了读取端。在这一点上,我不确定为什么会发生这种情况?

我不应该对这样的过程使用 pipe() 吗?如果我使用临时文件重定向 child 进程的标准输出,一切都会好起来吗?如果是这样,你能解释一下为什么吗?

显示的代码中存在多个基本的概念性问题。

std::pair<pid_t, int> sp;

这声明了一个新的 std::pair object。到目前为止一切顺利。

std::pair<pid_t, int> sp = this->subprocess(cmds[i], fds);

这会在 for 循环中声明一个新的 std::pair object。它恰好与函数范围内的 sp object 同名。但它是一个不同的 object,与它毫无关系。这就是 C++ 的工作方式:当您在内部作用域、if 语句、for 循环或另一对 { 中的任何内容中声明 object 时。 .. } 你最终声明了一个新的 object。它的名称是否恰好与在更大范围内声明的另一个名称相同,无关紧要。这是一个新的 object.

    // wait for all children
    while (wait(NULL) > 0);
    close(sp.second);

这里有两个不同的问题。

  1. 对于初学者来说,如果我们一直注意的话:这个 sp object 还没有被初始化为任何东西。

  2. 如果这里的目标是从children读取,那部分就完全没有了,应该在之前 等待 child 进程退出。如果,正如此处描述的目标,child 进程将写入此管道,则应从中读取管道。否则,如果没有从管道中读取任何内容:管道的内部缓冲区是有限的,如果 child 进程填满了管道,它们将被阻塞,等待管道被读取。但是 parent 进程正在等待 child 进程存在,所以一切都会挂起。

最后,还不清楚为什么管道的文件描述符被传递给同一个函数,只传递给 return 具有相同文件描述符的 std::pairstd::pair 在显示的代码中没有任何用处,因此很可能还有更多代码未在此处显示,而在此处使用它。

至少必须解决上述所有问题,才能使显示的代码正常工作。如果还有其他代码未显示,它也可能有也可能没有其他问题。