[英]Child process not reading from pipe, unless parent calls printf before dup2
下面的代码派生了一个子进程,并将标准输出重定向到管道。 孩子应该从管道中读取,但它没有发生。 奇怪的是,如果让父级在调用 dup2 之前至少调用一次 printf,那么一切似乎都有效。 我想这是一种不被依赖的运气......但解释仍然很好。 更重要的是,为什么孩子不识字?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
int fd[2];
pid_t p;
if(pipe(fd) == -1)
return -1;
if((p=fork()) == -1)
return -1;
if(p==0){
close(fd[1]);
dup2(fd[0],0);
fprintf(stderr,"Child starts\n");
int x;
scanf("%d",&x); // GETS STUCK HERE
fprintf(stderr,"Child ends\n");
exit(0);
}
// printf(" "); // THIS PRINTF SEEMS TO RESOLVE THE ISSUE?!
close(fd[0]);
dup2(fd[1],1);
printf("3\n");
fprintf(stderr, "Parent waiting\n");
wait(0);
fprintf(stderr, "Parent ends\n");
}
很多关于fork
和pipe
的问题都被问到了,但我找不到我的问题的答案。 抱歉,如果这是一个重复的问题。
父stdout
不是行缓冲的(因为它变成了管道)。 所以, printf("3\n");
的数据输出保留在流的缓冲区中,不会刷新到管道中。
有两种方法可以解决这个问题(在父级中):
setlinebuf(stdout);
就在那个printf
之前fflush(stdout);
紧接着printf
更新:
并添加额外的 printf() 修复它为什么? 我想那时,标准输出的底层文件描述符仍然是一个终端,因此它将流置于行缓冲模式,并且当底层文件描述符更改为管道时,这不会改变。 ——乔纳森·莱弗勒
对,那是正确的。 如果我们改变:
printf(" ");
进入:
setlinebuf(stdout);
然后,这也有效。
(即) printf
到一个真正的 tty [隐式] 设置行缓冲模式。
输出的探测/测试是一个 tty 设备 [显然] 推迟到设备完成某些活动(例如printf
)
dup2
对流是透明的,因此流的标志保持不变。
更新:
是否可以从孩子方面处理它?
不,孩子的记忆空间与父母不同。 它一开始是父母记忆的副本。 但是,它是独立的。 对其进行的更改不会影响父母的记忆(反之亦然)。
更多关于这下面。 但是,请记住,孩子正在等待一个数字,该数字是一个数字字符串,后跟空格。 这里的空格是换行符。
例如,如果父 execv 是一个不同的程序,其输出将由子程序读取,该怎么办? – 眼镜蛇
我们必须小心术语。
“父/子”是fork
但不是execv
的概念。 因此,当在父母中执行execv
时,我们不是在谈论孩子(或任何孩子)。 我们正在谈论 exec 的“目标程序”。
目标程序与以前有相同的问题。
流是用户空间/应用程序的概念。 操作系统内核不知道它们。 内核只知道“文件描述符”。 来自open
、 pipe
、 socket
等的东西。
stdio
流只是 I/O 的一种缓冲机制。 它们只存在于给定进程的内存中。 它们是一种“效率”机制,可防止对少量数据进行过多/重复/浪费的系统调用(即write
调用)。
“行缓冲”的概念只是流结构中的一个标志(以及它采取的行动)。 TTY 输出设备默认为行缓冲——当看到换行时,缓冲区被刷新。
文件(或管道;-)等其他内容默认为“标准”缓冲(例如 4096 字节)。 当超过 4096 时,它们会被刷新。 “刷新”意味着stdio
层写入底层文件描述符(通过write
系统调用)。
当一个execv
完成时,内存被完全替换并从目标程序的可执行文件内容中加载。
目标程序获得控制权并运行其crt0.o
初始化代码。 它必须从头开始创建stdin/stdout/stderr
,而不考虑这些在execv
之前的内存空间中可能存在的内容(不再存在)
因此,当目标程序的main
函数获得控制时, stdout
已附加到管道上。
它有同样的缓冲问题。 事实上,之前完成的printf(" ")
对目标的stdout
流的状态没有影响。
目标永远不会将stdout
(FD 1) 视为 TTY。 它已经被设置为管道(因此它得到标准缓冲)。 唯一的补救措施是使用setlinebuf
或定期进行fflush
调用。
如果该程序只是将数据输出到stdout
然后终止,则stdout
流(如果目标甚至使用标准输入输出)在退出时被刷新。
否则,目标程序与原始程序具有相同的问题。
程序员应该知道他们正在处理一个管道(因为他们设置了它)并相应地处理缓冲。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.