理解fork()的一次调用两次执行

原文地址:http://blog.csdn.net/songxueyu/article/details/9115393

fork()函数是linux里多进程编程的基础,为linux成为强大的多用户操作系统提供了强有力的支持。

但是对于很多初学者而言,虽然知道怎么写多进程的程序,知道怎么fork()出一个子进程,却很少有人能够理解fork()的最有特点的一个性质:一次调用,两次执行。

进程在内存里有三部分的数据——代码段、堆栈段和数据段。这三个部分是构成一个完整的执行序列的必要的部分。

代码段——存放了程序代码的内存空间。这个最容易理解,不就是程序在机器内的表示而已嘛。注意假如机器中有数个进程运行相同的一个程序,那么它们就可以使用相同的代码段。也就是说如果fork()出来了一个子进程,子进程和父进程实际上使用的是相同的代码段

堆栈段——存放的就是子程序的返回地址、子程序的参数以及程序的局部变量。比如说写了这样一个程序:

[cpp]  view plain  copy
  1. int a;  
  2. void main()  
  3. {  
  4.     int b;  
  5.     int c=func();  
  6. }  
  7.   
  8. int func()  
  9. {  
  10.     int d;  
  11.     return 0;  
  12. }  
这个程序里哪些变量是存放在堆栈段里的呢?不考虑编译器优化,实际上变量b,c,d都是会存放在堆栈里的。而a则会存放在接下来说的数据段里。

数据段——存放程序的全局变量,常数以及动态数据分配的数据空间(比如用malloc之类的函数取得的空间)。

好了,知道了上面三个段之后有什么用呢?用处可大了,这就说明了系统中的每一个进程都需要由这三个段来组成。不管是父进程还是fork()出来的子进程。但是上面也提到,由于子进程和父进程运行的是同样的程序(只是程序里的不同部分),它们使用相同的代码段,但是会拥有各自的数据段和堆栈段。

我们通常会这样写一个程序:

[cpp]  view plain  copy
  1. void main()  
  2. {  
  3.     pid_t pid;  
  4.     pid=fork();  
  5.     if(pid==0)  
  6.     {  
  7.         //子进程任务  
  8.     }  
  9.     else if(pid>0)  
  10.     {  
  11.         //父进程任务  
  12.     }  
  13. }  
执行过程是这样的:

1.操作系统分配内存给父进程,包括上面提到的三个段,就是会在堆栈段里有一块空间是用来存放pid变量的。

2.接着内核调度父进程执行fork()函数(这个函数里实际上使用了系统调用),这时候子进程才会出现,内核会将父进程的数据段和堆栈段作一个拷贝给子进程,注意这时子进程的堆栈段里一定会有一个空间用来存放pid变量!然后系统调用成功,内核给父进程堆栈段里的pid变量赋上子进程的pid号,而给子进程堆栈段里的pid变量赋上0。

3.接下来还是交给内核调度决定执行的是子进程还是父进程(一般内核会先给子进程执行)。如果是父进程,它的下一句代码就是判断pid变量的大小,它会去它的堆栈段里存放pid变量的地方取出pid来进行比较,它会发现pid>0,所以接下来它就去执行——父进程任务;如果是子进程,由于同样的代码段,它也会去比较它自己的pid变量,发现pid=0,所以接下来它会去执行——子进程任务。

注:左边父进程,右边子进程

这样,fork()函数就实现了一次调用,两次执行。关键就是在于父子进程拥有不同的堆栈段,而内核给这两个堆栈段里的pid赋上不同的值。

最后,我们来看看编译后的汇编程序,就能证实我的说法,也能更好地理解。

esp是堆栈指针寄存器,可以看到,在调用fork()函数之后将28(%esp)和0进行了比较,显然,28(%esp)就是pid变量。它存放在堆栈段里。

猜你喜欢

转载自blog.csdn.net/xulingxin/article/details/53439551
今日推荐