fork后父进程无法读取stdin的原因是什么?
问题原因与解决方法
核心原因:C标准库stdio的缓冲区机制
你遇到的问题本质是stdio用户态缓冲区和内核文件描述符偏移量的交互导致的:
- 当输入是管道(比如
echo ... | ./my_program)时,stdio默认使用全缓冲模式——会一次性从内核读取尽可能多的数据(这里就是全部9个a)到用户态缓冲区。 - fork之后,父子进程共享同一个内核文件描述符(偏移量是共享的),但各自拥有独立的stdio用户态缓冲区副本。
- 子进程调用
fgets(buf,5,stdin)时,因自身缓冲区为空,会触发系统调用把管道里的所有a读到自己的缓冲区,此时内核文件描述符的偏移量已走到EOF。 - 子进程从缓冲区取走4个
a后退出,剩下的5个a随子进程结束被销毁(用户态缓冲区属于进程私有资源)。 - 父进程调用
fgets时,自身缓冲区为空,尝试从内核读取数据,但此时内核管道已无数据,因此返回EOF。
解决方法
有两种可靠的方式解决这个问题:
方法1:禁用stdio缓冲区
在main函数开头添加一行代码,强制stdin使用无缓冲模式,让每次读取直接操作内核文件描述符:
setvbuf(stdin, NULL, _IONBF, 0);
修改后,子进程调用fgets会直接从内核读取4个字符,内核偏移量前进4位,父进程就能读取接下来的4个字符。
方法2:使用底层系统调用替代stdio函数
绕过stdio的缓冲区,直接用read系统调用读取数据:
// 子进程替换fgets的代码 read(STDIN_FILENO, buf, 4); buf[4] = '\0'; // 手动添加字符串终止符 printf("Child : %s\n",buf); // 父进程替换fgets的代码 ssize_t n = read(STDIN_FILENO, buf, 4); if (n <= 0){ printf("Parent : eof = %i\n",feof(stdin)); } else { buf[n] = '\0'; printf("Parent : %s\n",buf); }
这种方式直接操作内核文件描述符,偏移量会正确共享,父子进程能按预期分走输入数据。
验证效果
修改后执行echo 'aaaaaaaaa' | ./my_program,会得到预期输出:
Child : aaaa Parent : aaaa
内容的提问来源于stack exchange,提问作者ax_fer
相关产品推荐
相关产品推荐

