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

来源:互联网 发布:linux常用网络命令大全 编辑:程序博客网 时间:2024/05/19 04:53

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

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

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

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

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

int a;void main(){int b;int c=func();}int func(){int d;return 0;}
这个程序里哪些变量是存放在堆栈段里的呢?不考虑编译器优化,实际上变量b,c,d都是会存放在堆栈里的。而a则会存放在接下来说的数据段里。

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

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

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

void main(){pid_t pid;pid=fork();if(pid==0){//子进程任务}else if(pid>0){//父进程任务}}
执行过程是这样的:

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变量。它存放在堆栈段里。

原创粉丝点击