如何防止父进程在孩子死后丢失控制台输入?

How to prevent parent process from losing console input after the child dies?

本文关键字:失控 控制台 输入 何防止 进程 孩子      更新时间:2023-10-16

我正在尝试围绕交互式程序制作包装器。为此,我使用pipedup2poll的组合。一切似乎都很顺利,直到孩子终止。在此步骤中,父进程似乎失去了stdin,这就是我似乎无法理解的原因。

代码如下:

#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <poll.h>
#include <fcntl.h>
#include <signal.h>
#include <vector>
#include <string>
#include <iostream>
struct SystemFunctionFailure
{
    std::string what;
    SystemFunctionFailure(std::string const& what) : what(what) {}
};
template<typename T,size_t N> constexpr size_t countof(const T(&)[N]) { return N; }
void readAndPrint(std::string const& what, int fd)
{
    std::cerr << "Reading "+what+"n";
    std::vector<char> buffer(1024);
    const auto bytesRead=read(fd,buffer.data(),buffer.size());
    if(bytesRead==-1)
    {
        if(errno!=EAGAIN)
            throw SystemFunctionFailure("read "+what);
    }
    else if(bytesRead==0)
    {
        std::cerr << "EOF reached on "+what+"n";
        exit(0);
    }
    else
        std::cerr << "CONTENTS OF "+what+": "+std::string(buffer.data(),buffer.size())+"n";
}
int main()
{
    try
    {
        int pipeChildOut[2];
        if(pipe(pipeChildOut)==-1) throw SystemFunctionFailure("pipe for child stdout");
        int pipeChildErr[2];
        if(pipe(pipeChildErr)==-1) throw SystemFunctionFailure("pipe for child stderr");
        int pipeChildIn[2];
        if(pipe(pipeChildIn)==-1) throw SystemFunctionFailure("pipe for child stdin");
        const auto child=fork();
        if(child==-1) throw SystemFunctionFailure("fork");
        if(child)
        {
            dup2(pipeChildOut[1],STDOUT_FILENO);
            close(pipeChildOut[0]);
            dup2(pipeChildErr[1],STDERR_FILENO);
            close(pipeChildErr[0]);
            dup2(pipeChildIn[0],STDIN_FILENO);
            close(pipeChildIn[1]);
            execlp("sh","sh","-c","sleep 1; echo Test ; sleep 1; echo Child is exiting... >&2",nullptr);
            throw SystemFunctionFailure("execlp returned");
        }
        else
        {
            const int childStdErr=pipeChildErr[0];
            const int childStdOut=pipeChildOut[0];
            dup2(pipeChildIn[1],STDOUT_FILENO);
            fcntl(childStdErr,F_SETFL,O_NONBLOCK);
            fcntl(childStdOut,F_SETFL,O_NONBLOCK);
            fcntl(STDIN_FILENO,F_SETFL,O_NONBLOCK);
            while(true)
            {
                std::cerr << "New iteration of IO loopn";
                pollfd pollfds[]={ // making the indices coincide with .._FILENO
                                  {STDIN_FILENO,POLLIN},
                                  {childStdOut,POLLIN},
                                  {childStdErr,POLLIN},
                                 };
                if(poll(pollfds,countof(pollfds),{-1})==-1)
                    throw SystemFunctionFailure("poll");
                std::cerr << "poll returnedn";
                for(unsigned i=0;i<countof(pollfds);++i)
                    std::cerr <<" pollfds["<<i<<"].revents: " << pollfds[i].revents << "n";
                if(pollfds[ STDIN_FILENO].revents&POLLIN) readAndPrint("stdin" ,pollfds[ STDIN_FILENO].fd);
                if(pollfds[STDOUT_FILENO].revents&POLLIN) readAndPrint("stdout",pollfds[STDOUT_FILENO].fd);
                if(pollfds[STDERR_FILENO].revents&POLLIN) readAndPrint("stderr",pollfds[STDERR_FILENO].fd);
            }
        }
    }
    catch(SystemFunctionFailure& ex)
    {
        perror(ex.what.c_str());
        exit(EXIT_FAILURE);
    }
}

在这里,子项通过 dup2 隐式关闭其原始stdin,因此似乎不会影响父级对控制台输入的任何访问。但出于某种原因,这是我得到的输出:

$ g++ test.cpp -o test -std=c++14 && ./test
New iteration of IO loop
poll returned
 pollfds[0].revents: 0
 pollfds[1].revents: 1
 pollfds[2].revents: 0
Reading stdout
CONTENTS OF stdout: Test
New iteration of IO loop
poll returned
 pollfds[0].revents: 0
 pollfds[1].revents: 0
 pollfds[2].revents: 1
Reading stderr
CONTENTS OF stderr: Child is exiting...
New iteration of IO loop
$

即我得到 shell 提示,所以父级不再在前台。在此之后,如果我等待几秒钟并键入一个字母,我会得到以下输出:

poll returned
 pollfds[0].revents: 1
 pollfds[1].revents: 0
 pollfds[2].revents: 0
Reading stdin
read stdin: Input/output error

我想至少让父进程在孩子死后保留对其控制台输入的访问权限。在阅读了另一个问题的答案后,我认为我的问题与我的问题有关,但该答案并没有回答我的问题:"如何正确做?

我认为if(child)应该if(child == 0).

从 https://linux.die.net/man/2/fork

成功后,子进程的 PID 将在父进程中返回, 在子项中返回 0。失败时,在 父进程,则不创建子进程,并正确设置 errno。