fork系统调用过程分析
fork函数分析
首先编写fork.c和fork-asm.c来了解fork系统调用的运行结果:
fork.c代码
int main()
{
pid_t fpid;
int count = 0;
fpid = fork();
if(fpid < 0)
printf("error in fork!\n");
else if(fpid == 0)
{
printf("it's a child process,my process id is %d\n",getpid());
count++;
}
else
{
printf("it's a parent process,my process id is %d\n",getpid());
count ++;
}
printf("count:%d\n",count);
return 0;
}
fork-asm.c代码
int main()
{
pid_t fpid;
int count = 0;
asm volatile(
"mov $0x02,%%eax\n\t"//fork对应系统调用表的第二项
"int $0x80\n\t" //这里是模拟实际的系统调用,进入sys_call函数
"mov %%eax,%0\n\t"
:"=m"(fpid)
);
if(fpid < 0)
printf("error in fork!\n");
else if(fpid == 0)
{
printf("it's a child process,my process id is %d\n",getpid());
count++;
}
else
{
printf("it's a parent process,my process id is %d\n",getpid());
count ++;
}
printf("count:%d\n",count);
return 0;
}
运行结果如下:
运行流程如下:
fork函数通过系统调用创建一个与原来进程几乎完全相同的进程,一个进程调用fork函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。
- 在父进程中,fork返回新创建子进程的进程ID
- 在子进程中,fork返回0
- 如果出现错误,fork返回一个负值
其实就相当于链表,进程形成了链表,父进程的fpid(p 意味point)指向子进程的进程id, 因为子进程没有子进程,所以其fpid为0.
fork系统调用过程
上面对我们对fork的执行过程进行解释一下,来看它究竟做了哪些操作。 当你调用fork函数时,linux底层究竟怎样进行怎样的操作?为此,我查看linux内核0.11版本的源码来理解。
代码路径:init/main.c
static inline _syscall0(int,fork)
内核通过内联操作,在调用fork函数时,实际上是执行到unistd.h中的宏函数syscall0中去。对应代码:
#define __NR_setup 0 /* used only by init, to get system going */
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
………
#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; \
}
首先进入_syscall0后,先执行:”0”(__NR_fork)是将fork在sys_call_table[]中对应的函数编号__NR_fork(也就是2)赋值给eax,(在sys_call_table[]中编号2即对应sys_fork函数)。然后执行int $0x80软中断,在set_system_gate(0x80,&system_call);(/linux/kernel/Sched.c中的sched_init函数里)中定义了中断0x80对应着system_call函数(再由iret翻转回到进程的3特权级),所以就会跳转到_system_call中继续执行。
fork系统调用执行流程:
sys_fork里面会做的事:
(1)find_empty_process():在task[64]中为进程申请一个空闲位置并获取进程号
(2)copy_process():
- 为新进程创建task_struct,将原先进程的task_struct的内容复制给新进程
- 给新进程分配页表,并复制原先进程的页表到新进程
- 共享原先进程的文件
- 设置新进程的GDT项
- 将新进程设置成就绪态,参与进程间的轮转
0x80中断
系统调用是要在0特权级下的完成的,也是为了其安全性,那么3特权级翻转到0特权级时发生了什么?
代码路径:\linux\include\asm\System.h
#define set_system_gate(n,addr) \
_set_gate(&idt[n],15,3,addr)
……
#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ ("movw %%dx,%%ax\n\t" \
"movw %0,%%dx\n\t" \
"movl %%eax,%1\n\t" \ //这里在拼接IDT表
"movl %%edx,%2" \
: \
: "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
"o" (*((char *) (gate_addr))), \
"o" (*(4+(char *) (gate_addr))), \
"d" ((char *) (addr)),"a" (0x00080000))
dpl为3表示系统调用可以由3特权级调用,所以set_system_gate可以在用户态调用,中断使CPU硬件自动将SS、ESP、EFLAGS、CS、EIP寄存器的数值压入内核栈,这里发生了内核态和用户态堆栈的切换,这两个操作都在system_call的一开始进行了,从system_call开始就是在内核态了。