首页 > 解决方案 > 从衍生进程中读取管道时,select() 阻塞后的 read()

问题描述

有 3 个管道调用来为新进程创建标准输入、标准输出和标准错误。调用 fork(),调用 exec()。这包含在一个可以工作的 popen2 函数中。

当使用这个 popen2 函数时,从新进程的 stdout 读取会在 read() 上阻塞,即使在 select() 返回后 read 已准​​备好读取。我希望它能够读取()。我唯一的猜测是这个 fd 的 read() 试图填充输入 buf。

一个例外:如果 stdin 关闭,stdout 被子进程关闭,即使 buf 无法填充,读取也会完成。

我需要的是 read() 返回准备好阅读的内容?默认模式是缓冲的,我不知道吗?


pid_t popen2(const char *const argv[], int *in, int *out, int *err)
    {
    int res;
    pid_t pid = 0;
    int inpipefd[2];
    int outpipefd[2];
    int errpipefd[2];
    if(0!=pipe(inpipefd)) {
        perror("allocating pipe for child stdin");
        return -1;
    }
    if(0!=pipe(outpipefd)) {
        close(inpipefd[0]);
        close(inpipefd[1]);
        perror("allocating pipe for child stdout");
        return -1;
    }
    if(0!=pipe(errpipefd)) {
        close(inpipefd[0]);
        close(inpipefd[1]);
        close(outpipefd[0]);
        close(outpipefd[1]);
        perror("allocating pipe for child stderr");
        return -1;
    }
    pid = fork();
    if (0==pid) {
        if (-1==dup2(inpipefd[0], STDIN_FILENO)) {exit(errno);}
        if (-1==dup2(outpipefd[1], STDOUT_FILENO)) {exit(errno);}
        if (-1==dup2(errpipefd[1], STDERR_FILENO)) {exit(errno);}
        close(inpipefd[0]);
        close(inpipefd[1]);
        close(outpipefd[0]);
        close(outpipefd[1]);
        close(errpipefd[0]);
        close(errpipefd[1]);
        execvp(argv[0], (char* const*)argv);
        perror("exec failed");
        exit(1);
    }
    close(inpipefd[0]);
    close(outpipefd[1]);
    close(errpipefd[1]);
    *in = inpipefd[1];
    *out = outpipefd[0];
    *err = errpipefd[0];
    return pid;
    }

...
if(0>=(pid = popen2(argv, &in, &out, &err))) {
        return make_unique<std::string>();
    }
    res = writeall(in, cont.c_str(), cont.length());
    if(res==-1) {
        goto err;
    }
    close(in);
...

unique_ptr<std::string> DecryptProcess::Read() {
    auto result = make_unique<std::string>();
    const unsigned int BUFLEN = 1024*16;
    std::vector<char> buf(BUFLEN);
    fd_set rfds;
    struct timeval tv;
    int n;
    int fcnt;
    FD_ZERO(&rfds);
    FD_SET(out_fd, &rfds);
    FD_SET(err_fd, &rfds);
    tv.tv_sec = 0;
    tv.tv_usec = 100000;
    fcnt = select(std::max(out_fd, err_fd)+1, &rfds, NULL, NULL, &tv);
    if (fcnt == -1) {
        return result;
        } else if (!fcnt) {
        return result;
        }
    if (FD_ISSET(err_fd, &rfds)) {
        n = read(err_fd, &buf[0], buf.size());
        }
    if (FD_ISSET(out_fd, &rfds)) {
        do
            {
            n = read(out_fd, &buf[0], buf.size());
            if (n == -1) {
                return result;
                }
            if (n>0)
                result->append(buf.cbegin(), buf.cbegin()+n);
            } while ( n > 0 );
        }
    return result;
    }


标签: c++unix

解决方案


[删除了只会使视图混乱的调试语句]

    if (FD_ISSET(out_fd, &rfds)) {
        do
            {
            n = read(out_fd, &buf[0], buf.size());
            if (n == -1) {
                return result;
                }
            if (n>0)
                result->append(buf.cbegin(), buf.cbegin()+n);
            } while ( n > 0 );
        }

在这里,您不是在执行单个read(),而是read()在循环中执行,直到它返回错误或遇到 EOF。所以代码会循环,直到它消耗所有已经写入管道的数据,然后阻塞,直到有更多的数据写入它。select()返回一个准备好读取的文件只告诉有一些数据可以从中读取,而不是read()直到 EOF 才会阻塞。

顺便说一句,不保证select()即使fd 上标记为准备好读取的单个文件实际上也不会阻塞。[1]确保这一点的唯一方法是将 fd 设置为非阻塞模式(例如 with )并检查错误情况。read()fcntl(O_NONBLOCK)errno == EAGAIN

您的代码还有许多其他问题(例如,两次刷新 stdio 缓冲区、无法检查EINTR等)。


[1]请参阅Linux 手册页的 BUGS 部分


推荐阅读