曾几何时,只是一直在调用库函数fork,只知道它创建一个子进程,并且子进程返回值是0,父进程返回值是子进程pid。但是一直没有深究内核代码,今天终于使用gdb调试了一把fork的内核代码,下面就让我们一探究竟吧。
1.创建进程的本质是什么?
根据目前理解,其实就是构造了PCB,这个PCB会唯一标示一个进程的存在,并且会构建子进程的页目录和页表等等。
2.为什么fork()调用会有2个返回值,并且父进程返回子进程pid,子进程返回0?
首先搞明白一点,GCC调用函数的时候,返回值是放在eax中的。既然fork创建了新进程,说明那么父进程和子进程都有返回值,那么自然有两个返回值,并且子进程通过设置p->tss.eax=0(返回值放在eax中喔),当调度到子进程执行时,eax内容代表返回值,所以子进程返回0。而父进程eax存放子进程的pid,那么父进程返回值也就是子进程的pid。
通过看上面的描述肯定是云里雾里,我们通过函数库fork()调用到最后返回,分析看看(有分析过内核的基础,不然好像也不会看懂下面的东西)
fork()->_sys_call0)int,fork)
调用fork函数,内部会调用到_sys_call0(int,fork),这是内核定义的宏:
#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name)); \
if (__res >= 0) \
return (type) __res; \
errno = -__res; \
return -1; \
}
调用到int 0x80就进入到系统调用入口处理程序_system_call:
call _sys_call_table(,%eax,4)
这样就会跳转到_sys_fork:
.align 2
_sys_fork:
call _find_empty_process
testl %eax,%eax
js 1f
push %gs
pushl %esi
pushl %edi
pushl %ebp
pushl %eax
call _copy_process
addl $20,%esp
1: ret
find_empty_process的作用:取得子进程的进程id,然后调用进入copy_process函数,在里面会创建子进程的PCB和页目录、页表等等工作。
最后退出系统调用的时候会返回到
if (__res >= 0) \
return (type) __res; \
errno = -__res; \
return -1; \
}
从if(__res>=0)开始,如果调度到父进程return _res=pid,如果调度到子进程,return _res=0。