Linux-vfork与fork简单对比分析

####fork相关问题:
#####一、fork基础了解
#####fork作用为创建一个子进程,在使用了fork命令后,内核会分配新的内存块和数据结构给子进程,并且将父进程的部分数据结构内容拷贝到子进程,最后再将子进程添加到系统进程列表中,添加完成后fork返回,开始调度。

头文件:#include < unistd.h >
函数原型:pid_t fork( )
**返回值:**返回值大于0则当前进程为父进程,等于0代表为子进程,小于零代表创建子进程失败。

#####通过一个例子来了解:

  1 #include 
  2 #include 
  3 
  4     
  5 int main()
  6 {
  7     int tmp = 5;
  8     pid_t res = fork();
  9     if(res < 0){
 10         //fork失败
 11         perror("fork");
 12     }else if(res == 0){
 13         //该进程为子进程
 14         printf("im child[%d],fasther is %d,tmp is %d.\n",getpid(),getppid(),tmp++);
 15     }else{
 16         //该进程为父进程
 17         printf("im father[%d],tmp is %d.\n",getpid(),tmp++);
 18     }
 19     printf("tmp = %d\n",tmp);
 20     return 0;
 21 }            

#####运行结果:
im father[3128],tmp is 5.
tmp = 6
im child[3129],fasther is 1,tmp is 5.
tmp = 6
#####相关问题小结:
#####通过结果很明显的能看出本次调用中,先执行父进程,对应pid为3128,在父进程中tmp++,所以输出为6;关键问题在于子进程,有两个关键点。
#####**①为什么结果中子进程父亲pid为1:**通过输出我们能看出父进程先执行完成后才执行的子进程,也就是说当子进程执行时父进程已结束,此时该子进程相当于一个孤儿进程,被pid为1也就是Init进程所管理,所以子进程的ppid为1;
#####②为什么子进程最后输出tmp值还为6: fork进程采用的是写时拷贝,父子进程一开始共享一片内存区域,但是只有有一方要对数据进行修改,则再开辟一块空间,防止相互修改影响。所以在上述代码中,虽说是一个tmp,其实内存中各自保留了一份值。


#####二、关于fork过程中写时拷贝:
Linux-vfork与fork简单对比分析_第1张图片
Linux-vfork与fork简单对比分析_第2张图片
#####这下就不难看出,父子进程数据段和代码段开始时是共享一块对应的内存,当一方尝试写入时,便产生了写时拷贝。**需要注意的是:fork之前,父进程独立执行,fork之后,父子两个执行流分别执行,至于谁先执行,由调度器决定。**可通过下面例子很明显的看出是从fork之后才分别执行。


  1 #include 
  2 #include 
  3 
  4 
  5 int main()
  6 {
  7     int tmp = 5;
  8     printf("There is fork before\n");
  9     pid_t res = fork();
 10     if(res < 0){
 11         //fork失败
 12         perror("fork");
 13     }else if(res == 0){
 14         //该进程为子进程
 15         printf("im child[%d],tmp is %d.\n",getpid(),tmp++);
 16     }else{
 17         //该进程为父进程
 18         printf("im father[%d],tmp is %d.\n",getpid(),tmp++);
 19     }
 20     printf("tmp = %d\n",tmp);
 21     return 0;
 22 }
~      

#####输出结果:
#####There is fork before
#####im father[3625],tmp is 5.
#####tmp = 6
#####im child[3626],tmp is 5.
#####tmp = 6


#####三、fork调用失败的原因:
#####①系统中已经存在太多进程,无法再创建新的进程。可通过ulimit -a命令查看当前所有的资源限制。
#####②内存不足,由于开辟每个新的进程都要分配一个PCB,并为新进程分配资源,内存都不足也就别提还想着再创建进程了。


####vfork相关问题:
#####一、vfork基础了解
#####<1>vfork创建新进程的主要目的在于用exec函数执行另外的程序,实际上,在没调用exec或_exit之前**子进程与父进程共享数据段。**在vfork调用中,**子进程先运行,父进程挂起,**直到子进程调用exec或_exit,在这以后,父子进程的执行顺序不再有限制。

头文件:#include < unistd.h >
函数原型:pid_t vfork( )
**返回值:**返回值大于0则当前进程为父进程,等于0代表为子进程,小于零代表创建子进程失败。


#####通过一个例子来了解:

  1 #include 
  2 #include 
  3 
  4 int tmp = 3;
  5 
  6 int main()
  7 {
  8     pid_t res = vfork();
  9     if(res < 0){
 10         perror("vfork");
 11         _exit(1);
 12     }else if(res == 0){
 13         tmp = 10;
 14         printf("child res = %d\n",tmp);
 15         _exit(0);
 16     }else{
 17         printf("father res = %d\n",tmp);
 18     }
 19 
 20     return 0;
 21 }

#####输出结果:
#####child res = 10
#####father res = 10
#####结果分析:正如上面所说的,子进程直接公用父进程的页表,改变子进程的数据也会影响到父进程。
Linux-vfork与fork简单对比分析_第3张图片


#####<2>vfork用处:
#####vfork()跟fork()类似,都是创建一个子进程,这两个函数的的返回值也具有相同的含义。但是vfork()创建的子进程基本上只能做一件事,那就是立即调用_exit()函数或者exec函数族成员,调用任何其它函数(包括exit())、修改任何数据(除了保存vfork()返回值的那个变量)、执行任何其它语句(包括return)都是不应该的。更需要注意的是:调用vfork()之后,父进程会一直阻塞,直到子进程调用_exit()终止,或者调用exec函数族成员。


#####<3>为什么只能用_exit退出:
#####exit()是对_exit()的封装,它自己在调用_exit()前会做很多清理工作,其中包括刷新并关闭当前进程使用的流缓冲(比如stdio.h里面的printf等),由于vfork()的子进程完全共享了父进程地址空间,子进程里面的流也是共享的父进程的流,所以子进程里面是不能做这些事的。直接return就更不行了,子进程return以后,会从当前函数的外部调用点后面继续执行,这后面子进程可能将会执行很多语句,结果就没法预料了。在man手册中也强调了这一点,必须使用_exit退出。


####总结对比:
#####一、父子进程调用先后顺序不同:fork()由调度决定,vfork先子进程,后父进程;
#####二、对父进程页表的操作不同:fork()会复制父进程的页表,而vfork()不会复制,直接让子进程共用父进程的页表;

你可能感兴趣的:(Linux)