进程篇—fork与vfork的恩恩怨怨

进程篇—fork与vfork的恩恩怨怨

标签:fork; vfork;vfork与fork区别

一、vfork函数
头文件:#include

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 int a=5;
  5 int main()
  6 {
  7     pid_t pid=vfork();
  8     if(pid < 0)
  9     {
 10         printf("error\n");
 11     }
 12     else if(pid==0)
 13     {
 14         a++;
 15         printf("I am children,process is %d,a=%d\n",getpid(),a);
 16     }
 17     else
 18     {
 19         printf("I am father,process is %d,a=%d\n",getppid(),a);
 20     }
 21     return 0;                                                                
 22 }
~     

结果:

I am children,process is 2950,a=6
I am father,process is 2829,a=6
段错误 (核心已转储)

出现了段错误,这是怎么回事?通过查资料以及分析可知:
结束子进程的调用是exit()而不是return,如果你在vfork中return了,那么,这就意味main()函数return了,注意因为函数栈父子进程共享,所以整个程序的栈就废了。
修改后程序:

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 int a=5;
  5 int main()
  6 {
  7     pid_t pid=vfork();
  8     if(pid < 0)
  9     {
 10         printf("error\n");
 11     }
 12     else if(pid==0)
 13     {
 14         a++;
 15         printf("I am children,process is %d,a=%d\n",getpid(),a);
 16     }
 17     else
 18     {
 19         printf("I am father,process is %d,a=%d\n",getppid(),a);
 20     }
 21     exit(0);                                                                
 22 }

结果:

I am children,process is 3912,a=6
I am father,process is 2829,a=6

接下来,我们来看看他们之间的
区别:
1. fork的返回值
2. 进程ID不同
3. 具有不同的父进程ID
4. 子进程的tms_utime、tms_stime、tms_cutime及tms_ustime均被设置为0
5. 父进程设置的文件锁不会被子进程继承
6. 子进程的未处理闹钟被清除
7. 子进程的未处理信号集被设置为空集 fork有下面两种用法:
(1) 一个父进程希望复制自己,使父子进程同时执行不同的代码段。如:父进程等待 客户端请求,生成子进程来处理请求。
(2) 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。
fork调用失败的原因: 1. 系统中有太多的进程 2. 实际用户的进程数超过了限制

3、fork()函数
vfork()函数用来创建一个新进程,而这个新进程的目的是exec一个新程序。它不会把父进程的地址空间复制到子进程中,因为子进程会立即调用exec函数。
在子进程调用exec或exit之前,它在父进程 的空间中运行,也就是说会更改父进程的数据段、栈和堆。vfork和fork另一区别在于 :vfork保证子进程先运行,在它调用exec或(exit)之后父进程才可能被调度运行。

三、fork和vfork的区别
1、fork特点:
fork创建进程时,子进程完全复制父进程的资源,子进程独立于进程,具有良好的并发性,二者之间需要专门的通信机制。
如果某进程fork出一个子进程只是为了调用exec执行另一个文件,那fork过程对于虚拟地址空间的复制是多余的过程。
fork优点:父子进程相互独立,子进程对父进程中同名变量进行修改并不会影响其在父进程中的值。

2、vfork特点:
vfork创建的子进程与父进程共享地址空间,即子进程完全运行在父进程的地址空间上,子进程对虚拟地址空间的修改同样为父进程所见,用vfork创建子进程后,父进程会被阻塞到子进程调用exec或exit。
vfork避免了(fork函数子进程被创建后,仅仅为调用exec执行另一个程序,它对地址空间的复制是多余的)这个问题,减少了不必要的开销。

vfork保证子进程先运行,它调用exec或exit后父进程才能调度运行,fork的父子进程运行顺序不定,取决于内核的调度算法。

父进程中的数据空间和堆、栈可能会产生副本,具体情况要看使用的是fork还是vfork,fork产生副本,vfork则共享这部分内存。

附录这么几个问题:

1 为什么需要vfork
上面提到了fork与exec配合使用的情况有一个缺点: 效率有点低。
fork会复制一份独立的数据段与堆栈,而exec的调用会产生新的数据段与堆栈。这种情况下,前面复制数据段与代码段的行为就没什么意义了。而复制又很浪费时间与空间。为了提高效率,产生了vfork。vfork不会复制数据段与堆栈,它产生的子进程会使用父进程的数据段与代码段。因此用vfork代替fork,从效率上看很划算。

  1. vfork的缺点
    vfork带来效率提升的同时,也引入了一些麻烦。首先是数据段与堆栈的共享。父子进程可能互相影响。为了降低风险,vfork调用后父进程会阻塞到子进程exec被调用。这样能够避免父进程影响子进程。而子进程对父进程的影响则要靠程序员自己控制。
    因为共享,子进程对堆栈与数据段的任何改动都会对父进程产生同样影响。所以调用vfork后,要尽快调用exec,以降低风险。vfork与exec之间的操作也要小心的设计。
    另外,vfork结束时必须调用exit,否则会导致父进程的异常,原因似乎是堆栈被改写了。
    互相影响的风险非常之大,所以能不用vfork,尽量不要用。

3.fork真的比vfork效率低吗
实际上,fork并不会立刻复制数据段与堆栈。而是在它们发生修改时才会复制,而且通过MMU的使用,每次复制只会复制4K。通过写时拷贝的方式,fork极大的提升了效率,而且安全性更高。

  1. vfork难道没用了吗
    个人觉得vfork还是有用的,但最好能对 vfork+exec 的使用加以限制。
    很多操作系统提供了Spawn的操作,这个类似于vfork+exec,但通过吧两个接口封装成一个函数的方式,有效的避免了子进程改写父进程的风险。所以在系统设计时,可以先考虑提供一组vfork加exec的封装。

猜你喜欢

转载自blog.csdn.net/Travelerwz/article/details/80195334