奇怪的fork结果

本文最后更新于:2023年12月17日 下午

前言

最近在上操作系统的相关课程,恰好学到了fork函数。这么为了引入问题,简单测试一下,请问下面的代码会输出什么?

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
fork();
fork();
fork();
printf("Hello World\n");
exit(0);
}

奇妙的过程

相信聪明的小伙伴,马上就能回答,“会输出8行 Hello World”。原因也很简单,因为每次执行完fork函数都会生成一个跟父进程一样的子进程,所以第一次执行完fork,总共有2个进程,第二次执行完fork就会有4个,第三次执行完就会有8个,所以最后每个进程都会输出一行Hello World。

接下来,我们稍微改变代码,变成下面这样子,那么这次输出结果会怎么样呢?

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
printf("Hello World\n");
fork();
fork();
fork();
exit(0);
}

这不也还是很简单吗,结果只是输出一行“Hello World”,因为打印操作在fork操作前,所以接下来的子进程都不会再执行打印输出的操作了。一开始我也是这么想的,但是VScode上的输出结果却令人大吃一惊,这怎么还是输出8行啊?

VScode输出结果


这真让人有些摸不着头脑,然后我又试了试在命令行下直接编译执行,发现这次得到的结果符合预期,这更加令人疑惑了。所以我又去试了试在Clion下执行这段代码,发现输出的结果也是符合预期的。

命令行下编译执行

Clion执行结果

那么在多方对比之下,只能是认为是VScode出了问题。当时我还认为是VScode里Code Runner这个插件的问题,所以在Stack Overflow上提问了[1],随后得到了大牛的解答。

简单来说printf是有一个buffer的,也就是说并不是执行完printf语句,就会立即输出显示到终端屏幕(标准输出stdout)上,只有buffer满了,或者显示调用刷新,或是输入回车符 ‘\n’ ,才会立即将缓冲区的内容输出到屏幕上。

但是对于VScode来说,程序代码并不是直接运行在终端shell里的,标准输出stdout会绑定到IDE自身的一个GUI(图形用户界面)。此时,只有当缓冲区满了,或者是手动刷新缓冲区的时候,才会立刻输出到GUI界面上,也就是说缓冲区接收到了’\n’回车符也不会立即输出。下图是我的理解,可能不太准确,不过大体上这么解释能够说得通。

个人图解(可能不太准确)

因此就有了输出8行的奇怪形象,因为每个子进程都继承了缓冲区里的内容,直到最后程序退出时刷新缓冲区,因此最后每个程序都输出一行。

然后在尝试下添加fflush手动刷新缓冲区,输出果然变成了只有一行,验证了之前的想法。

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
printf("Hello World\n");
fflush(stdout);
fork();
fork();
fork();
exit(0);
}

添加fflush后输出结果

小结

至此,有关fork之后奇怪的输出结果终于水落石出,其中涉及到的一个关键点就是printf或者说stdout是有缓冲区的,并不是执行完该函数语句就会立刻输出到屏幕上,这一个知识点其实以前也了解过,但是没有重视,或者说没有进一步了解底层实现,只知道个概念,所以真正遇到问题时,还是不能想到这一点。

所以还是要看源码啊,源码之前,了无秘密。但尴尬的是,我在IDE里想看对应库函数的源代码实现时,总是跳转不到对应的实现,只有一些声明,这就很尴尬。不知道有没有人跟我有类似的苦恼,正好等下次解决了,又可以水一篇博文了 :-)。

备注/参考


奇怪的fork结果
https://2017zhangyuxuan.github.io/2022/04/02/2022-04/2022-04-02 奇怪的fork结果/
作者
Zhang Yuxuan
发布于
2022年4月2日
许可协议