日期:2014-05-16  浏览次数:20833 次

从一道面试题分析Linux进程+IO缓冲区机制

下面的程序打印出多少个“*“    (小弟今年遇到的腾讯一面面试题,据说其他公司的面试题中也有这个题目)

#include <unistd.h>
#include <stdio.h>

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


 

答案打印出8个*,而不是6个。

 

 


分析:


如果不考虑IO机制,一般可能会想,i = 0时,fork后有两个进程,打印两个*,i=1,时有四个进程,再打印4个*。所以一共6个。其实不是这样的。

        进程机制:
         fork分叉后子进程似乎父进程的副本,子进程会复制父进程的数据空间、堆和栈。而共享代码段。

         C语言标准IO的缓冲机制:
         C语言的标准IO是带缓冲的,一般调用printf后,并不是立即把要打印的内容立即打印在控制台界面上,而是输出到一个缓冲区,对应标准输出的叫标准输出缓冲,对应标准出错的叫标准出错缓冲,当然还有标准输入缓冲。

         缓冲机制:
         缓冲机制一般分为:全缓冲、行缓冲、无缓冲。

  •          全缓冲:缓冲区满了以后,才发生真正的IO。我们通常用的磁盘文件IO就是这样的。当然你可以调用flush类函数强制刷新缓冲。
  •          行缓冲:缓冲区满了以后或者缓冲区收到一个换行符(表示已输入或输出一行),后才发生真正的IO,比如标准输出和标准输入默认的缓冲机制就是行缓冲。(行缓冲还有一些规则,参考APUE)
  •          无缓冲:立即发生IO,通常标准出错是不带缓冲的。所以建议用输出信息来调试程序时,最后用标准出错IO,以免调试信息延迟输出。

        上面三种我们都能用flush和关闭文件之类的函数强制刷新缓冲。C语言还提供了接口让我们改变默认的缓冲机制,以及缓冲区的大小。

       回到我们的问题,根据linux进程机制,题目中的标准输出文件描叙符也是会被子进程复制的。所以fork后所有的进程共享一个控制台窗口(这个解释好像有点多余,看不懂跳过)。IO的缓冲区是用malloc申请的,是属于堆区,所以也是子进程要从父进程复制的。那么i= 0时,有两个进程,他们各自输出了一个*,而且由于标准输出默认采用的是行缓冲机制,所以此时,它们只是各自把一个*复制到了标准输出的缓冲区,没有打印到c控制台窗口上。 接下来是关键时刻,接下来 i = 1,再一个运行到fork()后,子进程复制父进程的堆区,所以后面出生的子进程也和父进程有着一样的标准输出缓冲区,而且标准输出缓冲区中同样也有着一个*。然后运行大printf语句,所有的进程都又各自向自己的标准输出缓冲区输送了一个*。  然后 i=2,程序结束了,标准输出缓冲区的内容打印到控制台窗口上,你就看到几个*了。
        好的,我们现在统计一下。每个进程都一个输出了几个*。
        我们把进程按照出生的先后分为两拨,第一波是i = 0时就出现的,包括主线程和第一个字进程,在整个循环中他们向自己的标准输出缓冲区都输出了两个*。
        第二波是在i = 1时产生的两个子进程,它们各自像自己的标准输出缓冲区输出了一个*,但是由于它们从父进程的缓冲区里复制了一个*,所以它们的标准输出缓冲区中都有两个*。
        最后程序结束,关闭标准输出文件时,IO发生,一共向控制台窗口打印了:两个x两个 + 两个x两个 = 8个 *。

运行结果截图
185719r7g3jpvpvgba05ja.png 

如果把程序中的printf语句中加上一个\n符号,改为printf("*\n"),就是下面的结果,打印出了6个*。