注册 登录  
 加关注
查看详情
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Mr. almost

Never mind,live goes on.On my way again.

 
 
 

日志

 
 

关于fork、vfork以及地址空间的一些问题  

2011-10-10 23:16:47|  分类: linux |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

文章1:

在《UNIX环境高级编程》一书的第八章中,有一道课后习题如下:

回忆图7-3典型的存储空间布局。由于对应于每个函数调用的栈帧通常存储在栈中,并在调用 vfork后,子进程运行在父进程的地址空间中,如果不是在main函数中而是在另一个函数中调用vfork,以后子进程从该函数返回时,将会发生什么情况?

作者Rich Stevens是一位大师,留下这么一题必有其深意,于是结合《深入理解计算机系统》中的知识,写了个程序验证了下,受益良多。

         首先回忆下程序运行的栈帧结构(见下图):

 

 

         从图中可知,如果一个函数调用用了另外一个函数,那么被调用者的栈帧则会被压入栈顶被设置为“当前帧”,首先执行被调用者,执行完成后,调用者的栈帧被弹出程序栈,然后从“返回地址”返回到调用者的地址空间中。

于是猜想,如果在main函数中,调用了一个函数foo,则“当前帧”为foo的栈帧,这时,若调用vfork创建一个子进程,那么根据vfork的语义,子进程不会完全复制父进程的地址空间,它会在父进程的地址空间中运行(这也是为什么vfork能保证子进程先运行,而fork不能保证。因为vfork创建的子进程是与父进程共享地址空间,为了避免竞争,所以就让子进程先运行,而父进程后运行;而fork创建的子进程是父进程的副本,所以不会带来竞争问题,谁先谁后也就无所谓了),所以它共享的是“当前帧”的地址空间,因此当子进程返回时,只会改变foo的数据,而不会改变main栈帧中的数据。

下面就来写个程序验证一下:


view plain
#include <stdio.h>  
#include <unistd.h>  
#include <sys/types.h>  
  
int glob = 88;               //a global var  
void foo(int);  
  
int main(int argc,char *arg[])  
{  
         int var = 100;            //a local var in main  
         foo(var);  
         if(printf("In main var:%d  glob:%d pid:%d/n",var,glob,getpid())<0)  
           perror("main printf");  
         exit(0);  
}  
  
void foo(int var)  
{  
         pid_t pid;  
         int loc = 66;                 //a local var in foo  
         printf("Before vfork/n");  
         if((pid = vfork())<0)  
           perror("vfork");  
         else if(pid == 0)            //child process  
         {  
                   loc++;  
                   var++;  
                   glob++;  
                   printf("pid:%d/n",getpid());  
                   exit(0);  
         }                   
  
         /*parent process continues here*/  
         printf("In foo var:%d  glob:%d  loc:%d  pid:%d/n",var,glob,loc,getpid());  
  
}  

运行此程序,得到结果为(见下图):

 

 

果然,可以看到在foo和main中,进程号都是一样的,也就说明foo和main在同一进程中。但是各个变量的值却有差异:在foo返回后mian函数中的局部变量var依然是初始值,并没有增加,推其原因,就是因为子进程共享的是foo的栈帧数据,而非main函数的栈帧,所以自然也就不会改变main栈帧中的数据。

书中正文中说:子进程不会完全复制父进程的地址空间,它会在父进程的地址空间中运行。因此可以进一步得出一个结论:vfork创建的子进程,共享的是父进程当前栈帧的地址空间。

原文链接:http://blog.csdn.net/litingli/article/details/5122853

 

文章2:

本文是涉及到fork,vfork,exec和进程通信,父子进程数据共享这几个方面的讨论。

第一点,

Linux中,创建进程的方式,只有一种,那就是调用fork(或者vfork)。 当然,系统的交换进程,init进程除外,它们是操作系统自举时用特殊方式创建的最初的进程。

第二点,

举个例子,父进程A 创建子进程B 后,进程B 就拥有了A 的所有数据(包括父进程的数据空间、堆和栈)的相同副本,并且共享代码片段(正文段)。父子进程的运行路线仅靠fork的返回值来区分。子进程从调用fork 之后的代码行继续执行,这点很重要。它意味着,之前的代码只有父进程会执行,fork之后的代码靠其返回值导向不同的流向。当然,如果fork 外部嵌套了结构控制语句,情况可能会更麻烦一点。这种情况下,父子进程可能仍有机会在跳出fork语句块之后,执行一段相同的代码。

第三点,

我们之所以用fork 调用,大多数情况是在子进程中调用exec 函数来启动另一个新的程序。即运行另一个可能是其他人所编写的程序(含有main函数的完整程序)。这时,子进程B 若调用了exec,那么就意味着进程B 被kill了,进程B 从进程A 那里继承的代码从调用exec 那行开始都无效,所有的数据也被清空。所以,在调用exec 后所编写的代码都是毫无意义的。唯一保留的是B的pid号,exec 所调用的那个程序会继承B 的pid 继续执行,直到结束。

第四点,

有了上面的这些概念,下面就开始说说vfork和父子进程的数据共享。这是我现在所关心的。首先,vfork产生的子进程共享父进程的所有地址空间(无论是正文段还是数据段),这就是程序中我使用了vfork的重要原因。因为,你要知道,若不是这样,父子进程之间的通信和数据共享的代码就又够我折腾一阵子了。不用vfork的话,我可能就不得不借助于管道通信,共享内存,乃至多线程的编写来解决这个问题。另外,vfork和fork之间的另一个区别是:vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行(exec之后父进程还是可以运行的)。有一点要提醒的是,网上有人说,在Linux中vfork已经丧失了其功能,变得和fork功能一样,还好,在我实验的RedHat 和Debian版本中vfork 还保留着共享父进程数据的能力。(实际上,UNIX系统都还保留着两者的不同之处)

这篇文章是自己在编写一个Linux小程序时,遇到问题有感而发的。

vfork用于创建一个新进程,而该新进程的目的是exec一个新进程,vfork和fork一样都创建一个子进程,但是它并不将父进程的地址空间完全复制到子进程中,不会复制页表。因为子进程会立即调用exec,于是也就不会存放该地址空间。不过在子进程中调用exec或exit之前,他在父进程的空间中运行。

为什么会有vfork,因为以前的fork当它创建一个子进程时,将会创建一个新的地址空间,并且拷贝父进程的资源,而往往在子进程中会执行exec调用,这样,前面的拷贝工作就是白费力气了,这种情况下,聪明的人就想出了vfork,它产生的子进程刚开始暂时与父进程共享地址空间(其实就是线程的概念了),因为这时候子进程在父进程的地址空间中运行,所以子进程不能进行写操作,并且在儿子“霸占”着老子的房子时候,要委屈老子一下了,让他在外面歇着(阻塞),一旦儿子执行了exec或者exit后,相当于儿子买了自己的房子了,这时候就相当于分家了(子进程有了自己的地址空间,不再用父进程的地址空间)

vfork和fork之间的另一个区别是: vfork保证子进程先运行,在她调用exec或exit之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。

由此可见,这个系统调用是用来启动一个新的应用程序。其次,子进程在vfork()返回后直接运行在父进程的栈空间,并使用父进程的内存和数据。这意味着子进程可能破坏父进程的数据结构或栈,造成失败。

为了避免这些问题,需要确保一旦调用vfork(),子进程就不从当前的栈框架中返回,并且如果子进程改变了父进程的数据结构就不能调用exit函数。子进程还必须避免改变全局数据结构或全局变量中的任何信息,因为这些改变都有可能使父进程不能继续。

通常,如果应用程序不是在fork()之后立即调用exec(),就有必要在fork()被替换成vfork()之前做仔细的检查。

用fork函数创建子进程后,子进程往往要调用一种exec函数以执行另一个程序,当进程调用一种exec函数时,该进程完全由新程序代换,而新程序则从其main函数开始执行,因为调用exec并不创建新进程,所以前后的进程id 并未改变,exec只是用另一个新程序替换了当前进程的正文,数据,堆和栈段。

原文链接:http://www.linuxidc.com/Linux/2011-09/43401.htm

 

 

  评论这张
 
阅读(250)| 评论(3)
推荐

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018