先看一个fork的例子:html
int glob = 4; int main(void) { int var, pid; var = 88; if ((pid = fork()) < 0) { printf("vfork error"); exit(-1); } else if (pid == 0) { /* 子进程 */ var++; glob++; exit(0); } printf("pid=%d, glob=%d, var=%d\n", getpid(), glob, var); return 0; }
运行结果:c++
[root@localhost tmp]# ./a.out pid=15297, glob=4, var=88
可见,子进程修改的局部变量var和全局变量glob后,父进程是不可见的。shell
若是把代码中的fork替换成vfork,再次运行,获得的结果:函数
[root@localhost tmp]# ./a.out pid=15304, glob=5, var=89
可见,父进程共享了子进程的修改操做。性能
在使用vfork时,若是子进程使用return语句结束,会发生什么呢?spa
int glob = 4; int main(void) { int var, pid; var = 88; if ((pid = vfork()) < 0) { printf("vfork error"); exit(-1); } else if (pid == 0) { /* 子进程 */ var++; glob++; return 0; } printf("pid=%d, glob=%d, var=%d\n", getpid(), glob, var); return 0; }
在个人机器上,致使了无限循环(直到vfork调用出错),这是由于子进程调用return语句破坏了父进程的栈。设计
fork与vfork的区别:code
咱们知道,fork一般采用写时复制技术(copy-on-write, COW)建立子进程,以提升进程clone的性能;但在更早尚未COW的年代,fork建立子进程时时须要完整的复制父进程地址空间到子进程中,若是咱们建立子进程的目的是为了调用exec,那么这种复制就显得既低效又无必要。而vfork让子进程共享父进程的地址空间,而不做克隆操做,就是为了节省这种没必要要的复制开销。htm
回到上面return致使程序crash的例子,return会释放局部变量,并弹栈,回到上级函数执行。exit直接退掉。若是你用c++ 你就知道,return会调用局部对象的析构函数,exit不会。(注:exit不是系统调用,是glibc对系统调用 _exit()或_exitgroup()的封装)对象
可见,子进程调用exit() 没有修改函数栈,因此,父进程得以顺利执行。而子进程调用return,至关于在父进程的栈上执行了弹栈操做,父进程也就跪了。
注意:
一、vfork保证子进程先运行,在它调用exec或exit以后父进程才可能被调度运行;
二、子进程在调用exec或exit以前是在父进程的地址空间中运行的。
可见,vfork的设计初衷是为了应对那些子进程须要立刻调用exec的场景,所以不对父进程的地址空间作任何复制。
再看一个fork的有趣例子,
int main(void) { int i, pid = 0; for (i = 0; i < 2; i++) { pid = fork(); if (pid == 0) { printf("pid:%d\n", getpid()); } } return 0; }
问题是,执行这段代码,一共产生了几个进程呢?
从执行结果来看,printf函数打印了3次,fork被调用了3次,连上main进程一共有4个进程。
再看下面这个例子,一共打印了多少个 “_” 呢?
int main(void) { int i; for(i=0; i<2; i++){ fork(); printf("-"); } return 0; }
按照上面的例子,程序运行过程当中一共有4个进程,把main进程记为A,则有
i=0时,A进程 fork调用,产生子进程B1,而后A、B1各打印一个"_";
i=1时,A进程 fork调用,产生子进程B2,而后A、B2各打印一个"_";
与此同时,B1进程fork调用,产生子进程C1,而后B一、C1各打印一个"_";
看起来,好像有6个"_"被打印了,但这段代码的执行结果倒是8个,这是为啥呢?
先来看下,这4个进程间的关系以下:
A --> B1 --> C1
|--> B2
可见,B一、B2继承自A,而C1继承自B1。
一、B1是在i=0时复制A的,此时A尚未调用过printf函数;
二、B2是在i=1时复制A的,此时A已经调用过一次printf函数;
三、C1是在i=1时复制B1的,此时B1已经调用过一次printf函数;
咱们知道,fork进程会让子进程完整复制父进程的地址空间,这也就包括了I/O缓冲区,这就是为何最终打印了8个"_"的缘由。
参考文档: