【学习&理解】对fork系统调用的理解
这是我在学习Linux0.11内核时做的笔记,以作为以后复习使用。本文解释为什么fork()函数会调用一次,返回两次。以及为什么返回给父进程的是子进程的pid,而返回给子进程的是0。
大致过程
用户程序调用fork()函数(标准库)->中断处理程序(system_call.s)->sys_fork()(system_call.s)->find_empty_process()(fork.c)->copy_process()(fork.c)
详细过程
首先,由用户程序来调用标准库提供的API -- fork()函数以进入中断处理程序(不懂的可以看之前的笔记)。
然后中断处理程序会根据传进来的系统调用号参数来得知要执行的系统调用函数,即sys_fork()。在进入中断处理程序前,就会先压入ss、esp、eflags、cs和eip寄存器(调用系统调用的必要操作,由硬件完成)。在中断处理程序中也会先压入当前进程的一些寄存器,这些寄存器都是在这里刚好用来当成copy_process()函数的参数(不懂为什么的可以先看最后的文章后面的解释)。被中断处理程序压入的寄存器有如下:
push ds ;// 保存原段寄存器值。
push es
push fs
push edx ;// ebx,ecx,edx 中放着系统调用相应的C 语言函数的调用参数。
push ecx ;// push %ebx,%ecx,%edx as parameters
push ebx ;// to the system call
然后再进行一些操作之后,就会使用如下语句来调用系统调用:
call [_sys_call_table+eax*4] ;// eax就是保存着系统调用号
接下来就是进入sys_fork(),这个是由汇编语言编写于system_call.s文件中。该函数第一步就是寻找能存放新进程信息的进程表空位和空闲PID值,通过如下语句:
call _find_empty_process ;// 调用find_empty_process()(kernel/fork.c,135)。
在sys_fork()函数中会压入当前一些寄存器用来当成copy_process()函数的参数,语句如下:
push gs
push esi
push edi
push ebp
push eax ;// 这里的eax就是刚刚在find_empty_process()函数中返回的进程表空位的索引(不懂的可以看文章后面的解释)
接下来就是调用copy_process()函数了,传入该函数的参数非常多,按照顺序来如下:
int nr, long ebp, long edi, long esi, long gs, long none,long ebx, long ecx, long edx,long fs, long es, long ds,long eip, long cs, long eflags, long esp, long ss
函数中,先申请一页空间来存放进程的信息,而这个空间地址到时候由进程表来索引查找。先设置子进程的状态为不可中断等待状态,以防被错误调度。在该空间中填入进程的信息,这些信息基本都是来自之前父进程通过压栈得来。其中子进程需要在该空间中记下自己的pid、父进程的pid、设置时间片、各种寄存器(几乎所有都跟父进程的一致,除了内核栈地址esp0、堆栈段选择符ss0以及eax,该eax设置为0,这就是子进程返回值为0的原因)、分配新的ldt给子进程。由于Linux0.11内核已经有写时复制机制,所以接下来子进程只复制父进程的页表项,这样就可以做到父子进程共享同样的进程空间了。子进程当然也要对父进程打开的各种文件描述符的打开次数增1。子进程要在GDT 中设置新任务的TSS 和LDT 描述符项。这时还要对子进程的状态再做一次设置,设置为就绪状态,等待调度。函数的最后就是返回子进程的pid。
解释
·函数的参数是怎么来的?其实就是取栈中的参数。用户在调用函数的时候,传入的参数是被压入栈中的,而且是从右往左。
如:int func(int a, int b); //b会先被压入栈,然后再是a。函数在取实参的时候就是从栈顶开始取,即从a开始。
·函数的返回值都是被保存在寄存器eax中的。
如:return a; //寄存器eax就保存着a的值。
讲到这里,基本讲完了,现在就可以来解决开头的两个问题了。
问题
·为什么fork()函数会调用一次,返回两次?
首先,我们回忆一下,子进程的寄存器几乎与父进程的一样,也就是说其cs、eip也和父进程的一样。而父进程在进入中断处理程序的时候压入了cs、eip,这两个的组合就是指向fork()函数中从中断处理程序返回之后的下一条语句。父进程在调用完系统调用返回之后是去执行cs:eip指向的语句,而子进程也是如此。该函数执行到最后是要返回的,所以两个进程各返回一次。
·为什么返回给父进程的是子进程的pid,而返回给子进程的是0?
父子进程的返回值不同,就是因为他们的eax存放的值不同。在copy_process()函数最后的return last_pid;是父进程执行的,也就是子进程的pid被保存在了父进程的eax中;而我刚刚前面提到,子进程的eax被设置为0。这就是不同返回值的原因。
欢迎各位发现错误后指出,本人一定及时改正并致以最诚恳的感谢!