介绍
vfork和fork一样都可以用来创建一个新进程,与fork相比,它有一些自己独特的用处。下面是他们的一些异同
* vfork 与fork一样都是调用一次,返回两次(一个是父进程调用vfork后的返回值,该返回值为子进程的pid。一个是子进程调用vfork的返回值,该返回值为0)
* 使用fork创建一个子进程的时候,子进程只是完整复制子进程的资源。这样得到的子进程独立于父进程,具有良好的并发性。而使用vfork创建子进程时,操作系统并不将父进程的地址空间完全复制到子进程,用vfork创建的子进程共享父进程的地址空间,也就是说子进程完全运行在父进程的地址空间上。子进程对该地址空间中任何数据的修改同样为父进程所见。
* 使用fork创建一个子进程时,哪个进程先运行取决于系统的调度算法。而vfork一个进程时,vfork保证子进程先运行,当它调用exec或exit后,父进程才可以被调度运行。如果在调用exec或exit之前子进程要依赖父进程的某几个行为,就会导致死锁。
fork创建一个子进程时,子进程要将父进程几乎每种资源都复制,所以fork是一个开销很大的系统调用,这些开销并不是所有情况都适用。比如fork一个进程后,立即调用exec执行另一个应用程序,那么fork过程中子进程对父进程地址空间的复制将是一个多余的过程。vfork不会拷贝父进程的地址空间,大大减少了系统开销。
我们看看下面这段程序。
代码
fork
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int qvar=5;
int main()
{
pid_t pid;
int var=1,i;
char str[]="I'm here";
printf("fork is different with vfork\n");
pid=fork();
//pid=vfork();
switch(pid)
{
case 0:
i=3;
while(i--)
{
printf("Child process is running\n");
qvar++;
var++;
sleep(1);
}
printf("Child's qvar = %d,var = %d,str = %s\n",qvar,var,str);
exit(0);
break;
case -1:
perror("failed\n");
exit(0);
break;
default:
i=5;
while(i-->0)
{
printf("Parent is running\n");
qvar++;
var++;
sleep(1);
}
printf("Parent's qvar = %d,var = %d,str = %s\n",qvar,var);
exit(0);
}
}
fork结果
分析
可见用fork创建子进程时,子进程继承了父进程的全剧变量和局部变量。而且根据子进程的qvar,var均增加了3。父进程的qvar,var也增加了3.且两者的值并未影响可知父子进程有各自独立的地址空间。
我们将上面vfork的注释去掉,然后再注释调fork。
vfork结果
分析
首先可见子进程要先于父进程运行,并且由qvar,var的值递增了8可见,vfork后子进程和父进程共享了一段地址空间。
注意
如果我们将vfork下子进程的exit(0)去掉,那么我们看看会发生什么情况
我们可以看到父进程的var成了随机值,str也找不到了。
这是因为vfork是这样的工作的:
(1)、保证子进程先执行。
(2)、当子进程调用exit()或exec()后,父进程往下执行。
当去掉子子进程下的exit后,程序默认return。而return会将栈释放,因此当子进程执行完,回到父进程时,父进程的栈已被子进程释放掉了(这就是赤裸裸的坑爹!),因此var为随机值。
因此当我们使用vfork时一定记得在子进程结束后用exit结束,不然会出现一些意想不到的结果。关于这点可以参考
用return和exit结束fork和vfork创建的子进程的思考