繁体   English   中英

子进程不从管道读取,除非父进程在 dup2 之前调用 printf

[英]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");
}

很多关于forkpipe的问题都被问到了,但我找不到我的问题的答案。 抱歉,如果这是一个重复的问题。

stdout不是行缓冲的(因为它变成了管道)。 所以, printf("3\n");的数据输出保留在流的缓冲区中,不会刷新到管道中。

有两种方法可以解决这个问题(在父级中):

  1. 添加setlinebuf(stdout); 就在那个printf之前
  2. 添加fflush(stdout); 紧接着printf

更新:

并添加额外的 printf() 修复它为什么? 我想那时,标准输出的底层文件描述符仍然是一个终端,因此它将流置于行缓冲模式,并且当底层文件描述符更改为管道时,这不会改变。 ——乔纳森·莱弗勒

对,那是正确的。 如果我们改变:

printf(" ");

进入:

setlinebuf(stdout);

然后,这也有效。

(即) printf到一个真正的 tty [隐式] 设置行缓冲模式。

输出的探测/测试是一个 tty 设备 [显然] 推迟到设备完成某些活动(例如printf

dup2对流是透明的,因此流的标志保持不变。


更新:

是否可以从孩子方面处理它?

不,孩子的记忆空间与父母不同。 它一开始是父母记忆的副本。 但是,它是独立的。 对其进行的更改不会影响父母的记忆(反之亦然)。

更多关于这下面。 但是,请记住,孩子正在等待一个数字,该数字是一个数字字符串,后跟空格。 这里的空格是换行符。

例如,如果父 execv 是一个不同的程序,其输出将由子程序读取,该怎么办? – 眼镜蛇

我们必须小心术语。

“父/子”是fork不是execv的概念。 因此,当在父母中执行execv时,我们不是谈论孩子(或任何孩子)。 我们正在谈论 exec 的“目标程序”。

目标程序与以前有相同的问题。

流是用户空间/应用程序的概念。 操作系统内核知道它们。 内核只知道“文件描述符”。 来自openpipesocket等的东西。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM