为了暑期的腾讯实习,拿到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有缓冲区,那么,又如何?
容我引用下酷壳大神美丽的图解:
你可以清楚的看到,用阴影框标注出来的两个“最终版子进程(i=2)”
他们在fork拷贝的时候,就将 parent的缓冲区一并拷贝过去,这时“g”还在缓冲区
那么,在输出的时候,两个子进程才一并把“g”从缓冲区输出来,那么是不是两个子进程没人多输出了一个?
1+1+1+1+12+12=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,或是程序退出)
下次大家在面试的时候如果遇到这个问题,相信都可以迎刃而解了吧 ~~~
(再次感谢酷壳大神,大家对我的看法如果有问题欢迎交流!)