一个fork问题的思考

来源:互联网 发布:mac地址绑定错误 编辑:程序博客网 时间:2024/06/10 15:16

问题一: 下面程序一共输出多少个"-"

#include <stdio.h>#include <sys/types.h>#include <unistd.h>int main(void){   int i;   for(i=0; i<2; i++){      fork();      printf("-");   }   return 0;}

按照fork()机制的运行逻辑,其输出应该是6个"-",但是这段程序的输出结果是8个"-"

在弄清楚这个问题之前,需要了解fork()系统调用的几个特性:

  • fork()系统调用是用于创建子进程的系统调用,一次调用,两次返回(大家可以分析一下fork系统调用的汇编代码,看它是如何实现两次返回的),如果返回0,则是子进程;如果返回值大于0,则是父进程(返回值是子进程的pid)
  • 还需要注意的一个细节就是:在fork()系统调用处,真个父进程的执行环境会全部复制给子进程,其中包括指令、变量值、程序调用栈、环境变量、缓冲区等。

上面的执行结果之所以会输出8个"-",就是因为printf("-")语句是带有buffer的,printf("-")会将"-"放到该进程的buffer中,当进行fork()系统调用时,自己程会复制父进程buffer中的内容,从而多了两个"-"

另外,linux中的设备分为块设备字符设备,一般情况下块设备是有缓冲的,而字符设备是没有缓冲的。

如果将上述代码修改为:

printf("-\n") ;

或是

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

最终的输出结果就是6个,因为程序遇到"\n",或是EOF,或是缓冲区满,或是文件描述符关闭,或是主动flush,或是程序退出,就会把数据刷出缓冲区。由于标准输出是行缓冲,所以遇到"\n"时会刷出缓冲,而对于磁盘这个块设备来说,"\n"并不会引起缓冲区刷出操作, 因为磁盘是全缓冲,可以使用setvbuf来设置缓冲区大小,或是用fflush刷新缓存。因此如果将程序的输出重定向到文件的话,第一种代码修改方案依旧会输出8个"-",而第二种方案会正常输出6个

下面以图的形式展现整个fork的过程:


上图中相同颜色的表示同一个进程,使用pstree -p | grep fork命令可以打印出进程之间的关系,如下图所示:


这样,对于printf("-");这样的语句,我们就可以清楚的知道,那个子进程复制到父进程缓冲区的内容而导致多次输出了。如下图所示,加了阴影和双边框的两个子进程导致了多次输出


问题二:下面代码的字符串是一起输出还是一部分一部分地输出?

printf("----");sleep(5);printf("++++");sleep(5);printf("\n");

代码执行结果是:所有字符串等到执行printf("\n")时才一起输出,这也就是说printf("----")只是将字符串放到缓冲区中,在执行printf("\n")或是程序结束前并没有输出出来。有了这个概念之后,下面的问题三就比较好理解了

问题三:下面代码的输出是什么?

#include <stdio.h>#include <sys/types.h>#include <unistd.h>int main(void){   int i;   for(i=0; i<2; i++){      printf("-");      printf("ppid=%d, pid=%d, i=%d \n", getppid(), getpid(), i);      fork();   }   return 0;}

这段代码的输出结果依旧是8个"-",解析过程如下图所示:


执行到程序结束时,系统中有图示的最下面的4个进程,图中相同进程我已使用相同颜色标识,虽然这4个进程在i=2时均没有执行printf("-"),但是其缓冲区内均由字符串"--",因此在进程结束的时候,系统会强制将缓冲区的内容刷新到输出设备上,所以会产生8个"-".

如果将上述代码修改为:

printf("-\n") ;

或是

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

程序的输出是3个"-",也就是图中进程树的第一层和第二层执行的printf("-")的结果。

Reference: http://coolshell.cn/articles/7965.html

0 0
原创粉丝点击