关闭管道未向对端发送EOF?C程序管道调用cat时进程卡死
问题根源
核心原因是管道的EOF(输入结束标识)触发机制没有被正确满足,底层逻辑为:
管道只有当所有指向该管道写端的文件描述符都被完全关闭时,读端才能通过read()调用收到0返回值(即感知到输入结束,和终端按下CTRL-D的效果完全等价)。
逐行梳理你代码里的文件描述符引用情况:
- 你创建的
outpipe是用来给cat提供输入的管道:outpipefd[1]是写端,outpipefd[0]是读端(被cat作为STDIN使用) - 你在父进程里确实关闭了自己持有的
outpipefd[1],但fork之后子进程会自动继承父进程所有打开的文件描述符,也就是子进程的地址空间里依然持有outpipefd[1]这个写端的引用,从未被关闭。 - 此时内核检测到
outpipe还有活跃的写端(子进程持有的那个),永远不会给cat的STDIN发送EOF信号,cat就会一直阻塞在输入等待上,不会退出,也不会回写之前收到的内容,最终导致整个程序死锁阻塞。
修正方案
需要在子进程完成所有dup2重定向操作后,手动关闭所有不需要的、继承来的管道文件描述符,彻底消除多余的写端引用:
- 关闭
outpipefd[1]:子进程不需要往输入管道写内容 - 关闭
inpipefd[0]:子进程不需要从输出管道读内容 - 关闭原来的
outpipefd[0]、inpipefd[1]:因为已经通过dup2把它们的读写属性绑定到了STDIN/STDOUT/STDERR,原来的文件句柄没有保留必要,关闭后也能避免文件描述符泄漏
修正后的子进程代码段如下:
if((pid=fork())==0) { /* 子进程逻辑 */ // 重定向标准输入、输出、错误流到对应管道 dup2(outpipefd[0], STDIN_FILENO); dup2(inpipefd[1], STDOUT_FILENO); dup2(inpipefd[1], STDERR_FILENO); // 关闭所有继承来的冗余管道fd close(outpipefd[0]); close(outpipefd[1]); close(inpipefd[0]); close(inpipefd[1]); // 执行cat命令 execl("/bin/cat", "cat", (char*)NULL); // execl执行失败则退出 exit(1); }
修正后的运行逻辑
- 父进程写完测试内容后,关闭自己持有的
outpipefd[1] - 此时
outpipe管道的所有写端引用(父进程的、子进程的)都已完全关闭,cat的STDIN会立刻收到EOF,和终端按CTRL-D的效果完全一致 cat收到EOF后会正常把之前收到的"Hello world!\n"回写到输出管道,随后退出- 父进程从输出管道读到
cat的回写内容,等cat退出后,输出管道的所有写端引用也被关闭,read()返回0,跳出读循环,正常回收子进程即可,不需要额外发送SIGKILL信号。
补充说明
- 原来代码里的
kill(pid, SIGKILL)逻辑其实永远执行不到:因为cat一直阻塞在输入等待上,根本没有机会回写内容,父进程的read(inpipefd[0])调用会一直阻塞在等待输出上,程序根本走不到kill的代码段 - 循环里每次手动清空
buf的操作可以去掉:根据read()的实际返回值截断字符串即可,减少不必要的开销
内容的提问来源于stack exchange,提问作者zakk
相关产品推荐
相关产品推荐

