select()之后的read()在从生成的进程中从管道读取时阻塞

时间:2019-05-09 21:16:28

标签: c++ unix

有3个管道调用来为新进程创建stdin,stdout,stderr。调用fork(),调用exec()。这包装在一个有效的popen2函数中。

使用此popen2函数时,即使在select()返回已准备好读取的读取之后,也从read()上的新处理块中从stdout读取。我希望它能够读取()。我唯一的猜测是此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;
    }


1 个答案:

答案 0 :(得分:3)

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

    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上只有一个read()也会不会实际被阻止。 [1] 唯一可以确保的方法是将fd设置为非阻塞模式(例如,使用fcntl(O_NONBLOCK)),并在错误情况下检查errno == EAGAIN

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


[1] 参见BUGS section of the Linux man page

相关问题