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



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

使用此 popen2 函数时,即使在 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;
}

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

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()返回文件准备好读取仅表示有一些数据可供读取,而不是在 EOF 之前没有read()会阻止。

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

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


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

最新更新