转载

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

为了暑期的腾讯实习,拿到offer之后一直在linux的道路上不断学习。

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

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

#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的缓冲区:

#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有缓冲区,那么,又如何?

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

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

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

他们在fork拷贝的时候,就将 parent的缓冲区一并拷贝过去,这时“g”还在缓冲区

那么,在输出的时候,两个子进程才一并把“g”从缓冲区输出来,那么是不是两个子进程没人多输出了一个?

1+1+1+1+1 2+1 2=8个

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

#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下这个程序,在我的电脑里输出是这样的:

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”被输出,但是现在居然诡异地多出了两个?到底是哪两个呢?我索性标注下这个输出结果吧~

(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命令试下可以得出一个树状的结构:

$ pstree -p | grep fork |-bash(3082)-+-fork(3128)-+-fork(3129)---fork(3131) |            |            `-fork(3130) 

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

#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,或是程序退出)

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

正文到此结束
Loading...