调用fork时发生了什么

89 阅读2分钟

整体流程

当用户空间执行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寻找是否存在一个空闲的任务结构,如果存在,则将gs、esi、edi、ebp、eax寄存器的值压入调用栈,并且调用copy_process, 当执行完了之后,将esp栈顶指针往高地址移动20,即将前面压入栈中的参数弹出,回到函数调用之前

寻找空的任务

代码如下,主要就是在task列表中找到一个空的位置,并将该位置的索引返回出去

NR_TASKS定义了最大的任务数为64

int find_empty_process(void)
{
	int i;

	repeat:
		if ((++last_pid)<0) last_pid=1;
		for(i=0 ; i<NR_TASKS ; i++)
			if (task[i] && task[i]->pid == last_pid) goto repeat;
	for(i=1 ; i<NR_TASKS ; i++)
		if (!task[i])
			return i;
	return -EAGAIN;
}

复制任务

首先看copy_process的函数签名如下

int 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)

结合前面的sys_fork汇编代码,会有一个疑惑,为什么sys_fork只压入了5个参数,而这里有一大堆呢

原因在于在进入系统调用, 触发了中断后,CPU 会自动帮我们做如下压栈操作, 并且system_call自动压入了一些参数

  • ss:堆栈段
  • esp:栈顶指针
  • eflags:标志位
  • cs:代码段
  • eip:指令寄存器
  • ds:数据段
  • es:额外扩展段
  • fs:栈帧段
  • edx:数据寄存器
  • ecx:数据寄存器
  • ebx:数据寄存器
  • eax:数据寄存器

在copy_process函数中主要的工作就是下面的部分,分配一个task_struct并复制一些状态信息,并且将该进程的tss信息以及ldt表存储在gdt对应的位置,方便通过gdt寻找这个进程的相关信息

struct task_struct *p;
p = (struct task_struct *) get_free_page();
*p = *current;	/* NOTE! this doesn't copy the supervisor stack */
p->state = TASK_UNINTERRUPTIBLE;
// ...复制一些状态信息
set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));