一道经典 fork 面试题

这是一道关于 Unix fork() 系统调用的经典面试题:每一轮 fork 产生的新进程数量等于当前正在运行的进程数量。题目问——下面这个程序一共输出多少个 -

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
    int i;
    for (i = 0; i < 2; i++) {
        fork();
        printf("-");
    }
    return 0;
}

如果你对 fork() 的机制比较熟悉,会认为答案是 6 个 -,但实际上这个程序会”tricky”地输出 8 个 -

fork() 的两个关键特性

要讲清这道题,首先要知道 fork() 系统调用的两个特性:

  1. fork() 是 Unix 下以自身进程创建子进程的系统调用,一次调用、两次返回:返回值是 0 则是子进程,返回值 > 0 则是父进程(返回值是子进程的 pid)。
  2. fork() 调用处,整个父进程空间会原模原样地复制到子进程中,包括指令、变量值、调用栈、环境变量、缓冲区等等。

为什么是 8 个 -

关键在于 printf("-") 有缓冲区:printf("-")- 放到了缓存里,并没有真正输出。在 fork 的时候,缓存被复制到了子进程空间,于是多出了两个 -,结果就成了 8 个而不是 6 个。

顺带一提,Unix 下的设备有”块设备”和”字符设备”的概念:

  • 块设备:以一块一块的数据存取,如磁盘、内存,一般有缓存;
  • 字符设备:一次存取一个字符,如键盘、串口,一般没有缓存。

对于上面的问题,把 printf 那条语句改成下面任意一种,就没问题了(即输出 6 个 -):

printf("-\n");
printf("-");
fflush(stdout);

因为程序遇到 \n、EOF、缓冲区满、文件描述符关闭、主动 flush 或程序退出时,都会把数据刷出缓冲区。

需要注意:标准输出是行缓冲,所以遇到 \n 会刷出缓冲区;但磁盘这种块设备是全缓冲\n 不会引起刷出动作。你可以用 setvbuf 设置缓冲区大小,或用 fflush 刷缓存。

进一步观察进程树

如果对 fork() 还不熟,可以把程序改成下面这样,加 \n 便于观察:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
    int i;
    for (i = 0; i < 2; i++) {
        fork();
        // 注意:下面的 printf 有 "\n"
        printf("ppid=%d, pid=%d, i=%d\n", getppid(), getpid(), i);
    }
    sleep(10); // 让进程停留十秒,便于用 pstree 查看进程树
    return 0;
}

这段程序会输出(编译出的可执行文件名为 fork):

ppid=8858, pid=8518, i=0
ppid=8858, pid=8518, i=1
ppid=8518, pid=8519, i=0
ppid=8518, pid=8519, i=1
ppid=8518, pid=8520, i=1
ppid=8519, pid=8521, i=1

用 pstree 查看进程树:

$ pstree -p | grep fork
|-bash(8858)-+-fork(8518)-+-fork(8519)---fork(8521)
| | `-fork(8520)

   转载规则


《一道经典 fork 面试题》 吴杭沉 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
C++ 死锁排查:Shell + GDB 定位 C++ 死锁排查:Shell + GDB 定位
在 Linux 环境下进行 C++ 编程时,多线程能显著提升程序的并发处理能力,让程序在面对复杂任务时表现得更加高效。但多线程编程并非一帆风顺,死锁问题就像隐藏在暗处的”杀手”,随时可能让程序陷入僵局。 想象一下,你的程序原本运行得好好的,
2020-06-15
下一篇 
循环引用:用 shared_ptr 还是 weak_ptr? 循环引用:用 shared_ptr 还是 weak_ptr?
一、回顾循环引用问题当两个对象通过 shared_ptr 相互引用时,会产生循环引用问题,导致内存泄漏。因为这两个对象的引用计数永远不会变为 0,即使它们在程序的其他部分已经不被使用了。 典型循环引用: #include <memor
2020-06-03
  目录