我粗略地创建了以下代码来调用子进程:
// pipe meanings
const int READ = 0;
const int WRITE = 1;
int fd[2];
// Create pipes
if (pipe(fd))
{
throw ...
}
p_pid = fork();
if (p_pid == 0) // in the child
{
close(fd[READ]);
if (dup2(fd[WRITE], fileno(stdout)) == -1)
{
throw ...
}
close(fd[WRITE]);
// Call exec
execv(argv[0], const_cast<char*const*>(&argv[0]));
_exit(-1);
}
else if (p_pid < 0) // fork has failed
{
throw
}
else // in th parent
{
close(fd[WRITE]);
p_stdout = new std::ifstream(fd[READ]));
}
现在,如果子进程没有对stdout写太多,我可以等待它完成,然后从p_stdout
读取stdout。如果写得太多,写块和父块将永远等待它。
为了解决这个问题,我尝试在父级中等待WNOHANG
,如果没有完成,请使用readsome
读取p_stdout的所有可用输出,稍微睡一会然后重试。不幸的是,readsome
从未读过任何内容:
while (true)
{
if (waitid(P_PID, p_pid, &info, WEXITED | WNOHANG) != 0)
throw ...;
else if (info.si_pid != 0) // waiting has succeeded
break;
char tmp[1024];
size_t sizeRead;
sizeRead = p_stdout->readsome(tmp, 1024);
if (sizeRead > 0)
s_stdout.write(tmp, sizeRead);
sleep(1);
}
问题是:为什么这不起作用,我该如何解决?
编辑:如果只有孩子,只使用read
代替readsome
可能会有效,但该过程有多个孩子,需要尽快做出反应他们终止了。
答案 0 :(得分:3)
正如sarnold建议的那样,您需要更改通话顺序。先读,等等。即使您的方法有效,您也可能会错过最后一次阅读。即,在读取写入的最后一组字节之前退出循环。
问题可能是ifstream是非阻塞的。我从来都不喜欢iostream,即使在我的C ++项目中,我也总是喜欢C的stdio函数的简单性(即FILE *,fprintf等)。解决这个问题的一种方法是读取描述符是否可读。您可以使用select来确定是否有数据在该管道上等待。如果你打算从多个孩子那里读书,你将需要选择,所以不妨现在就去学习。
至于一个快速可读的功能,尝试这样的事情(请注意我没有尝试过编译):
bool isreadable(int fd, int timeoutSecs)
{
struct timeval tv = { timeoutSecs, 0 };
fd_set readSet;
FD_ZERO(&readSet);
return select(fds, &readSet, NULL, NULL, &tv) == 1;
}
然后在您的父代码中执行以下操作:
while (true) {
if (isreadable(fd[READ], 1)) {
// read fd[READ];
if (bytes <= 0)
break;
}
}
wait(pid);
答案 1 :(得分:1)
我建议重新编写代码,以便在管道返回waitpid(2)
上 read(2)
之后的之前不会调用0
来表示档案结尾。一旦你从你的阅读电话中获得文件结束返回,你就知道孩子已经死了,你终于可以waitpid(2)
了。
另一个选择是将读数与收割进一步解耦,并在SIGCHLD
信号处理程序中与读取操作异步执行等待调用。