前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一个很有趣的fork面试程序,和大家分享下经验

一个很有趣的fork面试程序,和大家分享下经验

作者头像
程序员小王
发布2021-08-13 10:35:04
2960
发布2021-08-13 10:35:04
举报
文章被收录于专栏:架构说架构说

这两天逛了下酷壳大神的blog(http://coolshell.cn/articles/7965.html),偶然看到一个关于fork小问题,虽然之前想通了,不过还是值得回味并且和大家分享下的。

大家有兴趣可以想想,下面输出了多少个“g”?

代码语言:javascript
复制
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

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

   wait(NULL);
   wait(NULL);

   return 0;
}

也许你很快就说,那么简单,第一次循环,fork后2个进程,第二次再fork出4个,一共6个进程,肯定是6个“g”

恭喜你,如果我是面试官,那么你已经跪了 = =

答案:会输出8个“g”

有一个很重要的东西是,在fork()的调用处,整个父进程空间会原模原样地复制到子进程中,包括指令,变量值,程序调用栈,环境变量,缓冲区,等等。

等等!!什么,你说全部复制过去?那么我可以解释为: 上面的那个程序为什么会输入8个“-”,这是因为printf(“-“);语句有buffer

我们来试试printf的缓冲区:

代码语言:javascript
复制
#include <stdio.h>
#include <unistd.h>    //for sleep()

int main(void)
{
    printf("/nstart the dead loop/n");
    while(1)
    {    
        printf("/b->");
        fflush(stdout);//刷新输出缓冲区
        usleep(100000);
    }
    return 1;
}

哦哦!!如果不做fflush这个动作,上边的输出便不会显示到屏幕上咯? 除非其中有换行操作或者缓冲区,这也许就是所谓的“到终端行规程”!

呃呃,不要脱离了重点,我们还是回到刚才的fork问题上~

既然我们知道了printf有缓冲区,那么,又如何?

容我引用下酷壳大神美丽的图解:

你可以清楚的看到,用阴影框标注出来的两个“最终版子进程(i=2)”

他们在fork拷贝的时候,就将 parent的缓冲区一并拷贝过去,这时“g”还在缓冲区 那么,在输出的时候,两个子进程才一并把“g”从缓冲区输出来,那么是不是两个子进程没人多输出了一个? 1+1+1+1+12+12=8个

OK,我知道你和我一样可能有点懵懵的,我改下程序咯~

代码语言:javascript
复制
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
   int i;
   for(i=0; i<2; i++){
      fork();
      printf("g  ppid=%d, pid=%d, i=%d ", getppid(), getpid(), i);
   }

   wait(NULL);
   wait(NULL);

   return 0;

你可以gcc下这个程序,在我的电脑里输出是这样的:

代码语言:javascript
复制
g  ppid=3082, pid=3128, i=0 g  ppid=3128, pid=3130, i=1 
g  ppid=3128, pid=3129, i=0 g  ppid=3129, pid=3131, i=1 
g  ppid=3128, pid=3129, i=0 g  ppid=3128, pid=3129, i=1 
g  ppid=3082, pid=3128, i=0 g  ppid=3082, pid=3128, i=1

可以看到ppid为3082 pid为3128的输出有3次 同样的,ppid为3128 pid为3129的输出有3次

你将显然发现,原本i=0时应该只会有两个“g”被输出,但是现在居然诡异地多出了两个?到底是哪两个呢? 我索性标注下这个输出结果吧~

代码语言:javascript
复制
(1)g  ppid=3082, pid=3128, i=0 (5)g  ppid=3128, pid=3130, i=1 
(2)g  ppid=3128, pid=3129, i=0 (6)g  ppid=3129, pid=3131, i=1 
(3)g  ppid=3128, pid=3129, i=0 (7)g  ppid=3128, pid=3129, i=1 
(4)g  ppid=3082, pid=3128, i=0 (8)g  ppid=3082, pid=3128, i=1

明显的,(1)和(4)是“不可能”有两个的,(2)和(3)也是,现在你知道了吧,i=0时的两个进程,在下一次fork的时候各自成为了下一个父进程,在这一次fork时,他们的信息被完全复制到了子进程,那么最后一步每一个子进程缓冲区里还存有多余的“g”(见上图)

你还可以用pstree -p | grep fork命令试下 可以得出一个树状的结构:

代码语言:javascript
复制
$ pstree -p | grep fork
|-bash(3082)-+-fork(3128)-+-fork(3129)---fork(3131)
|            |            `-fork(3130)

那么,最后我们再试试,在printf一个g的后面加上一个换行符(或者fflush)

代码语言:javascript
复制
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
   int i;
   for(i=0; i<2; i++){
      fork();
      printf("g\n");
   }

   wait(NULL);
   wait(NULL);

   return 0;
}

输出的结果将是换行的6个“g 原因就在于,每一次换行,缓冲区的“g”,就会把数据“刷”出缓冲区

(其实或是EOF,或是缓中区满,或是文件描述符关闭,或是主动flush,或是程序退出)

下次大家在面试的时候如果遇到这个问题,相信都可以迎刃而解了吧 ~~~ (再次感谢酷壳大神,大家对我的看法如果有问题欢迎交流!)

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-07-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Offer多多 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档