进程源码分析-以fork函数为例分析

进程源码分析-以fork函数为例分析

int main(int argc, char **argv)
{
    A();
    B();
    return 0;
}
void A() {fork();}

前提知识点:
1、每次在执行一个函数内部功能前会把函数的下一条指令压栈
2、每个栈都与每一层的函数接口相对应
3、分析汇编代码,要时刻关注栈的内容变化


在执行到fork函数时
  • 1、此时的用户栈的分布情况:
  • exit -> B

解释:1、当进入到main函数内部时,会立马把main函数的下一条指令也就是exit压入到栈中;2、接着进入到A函数内部时,会立马把A函数的下一条指令也就是B压入到栈中。


在执行fork函数内部功能时
// linux-0.11/init/main.c
static inline _syscall0(int,fork);
// linux-0.11/include/unistd.h
#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; \
}

1、从上面代码可以看出执行fork函数的顺序是先压栈fork后的一条指令,由于在A中fork后没有指令,所以不再压栈,继续执行fork函数内部代码。首先将寄存器eax填入__NR_fork表示的值。
2、接着执行中断指令int 0x80。在执行中断时,硬件立马将ss、sp、eflags、cs、ip依次入栈,此时cs、ip对应的地址存放的指令就是”mov __res, %eax“。此时进入到中断内部了,所以会有另一个栈(内核栈)来存放内容,与用户栈区分开。此时的当前线程的内核栈分布情况如下:ss(2) -> sp(2) -> eflags(4) -> cs(2) -> ip(2),其中ss、sp为用户栈的栈段地址和栈偏移地址,efalgs为用户线程的寄存器状态。
3、判断__res的值按条件返回。


执行int 0x80的中断调用

  • 进入中断时,由于系统初始化时下面set_system_gate的作用,将0x80的中断映射到system_call函数中执行。
// linux-0.11/kernel/sched.c
void sched_init(void)
{
    ...
    set_system_gate(0x80,&system_call);
    ...
}

  • system_call的前部分代码如下所示
// linux-0.11/kernel/system_call.s
system_call:
	cmpl $nr_system_calls-1,%eax
	ja bad_sys_call
	push %ds
	push %es
	push %fs
	pushl %edx
	pushl %ecx # push %ebx,%ecx,%edx as parameters
	pushl %ebx	# to the system call
	movl $0x10,%edx	# set up ds,es to kernel space
	mov %dx,%ds
	mov %dx,%es
	movl $0x17,%edx	# fs points to local data space
	mov %dx,%fs
    ...
  • 执行完上面的代码后,此时的栈内容为:
  • ss(2) -> sp(2) -> eflags(4) -> cs(2) -> ip(2) -> ds(2) -> es(2) -> fs(2) -> edx(4) -> ecx(4) -> ebx(4)
  • 将ds设置为0x10,进入内核空间。

  • system_call的后部分代码如下所示
// linux-0.11/kernel/system_call.s
system_call:
   ...
   call sys_call_table(,%eax,4)
   pushl %eax
   ...

1、__NR_fork的值就是此时eax寄存器中存放的值。
2、 执行系统调用表system_call_table时会找到sys_fork函数。
3、 在执行时call sys_call_table时,会进行压栈处理,此时的栈内容为: ss(2) -> sp(2) -> eflags(4) -> cs(2) -> ip(2) -> ds(2) -> es(2) -> fs(2) -> edx(4) -> ecx(4) -> ebx(4) -> ip(2)[pushl %eax]. 此处压入的ip地址对应的指令为“pushl %eax”


此时进入sys_fork函数

// linux-0.11/kernel/system_call.s
sys_fork:
	call find_empty_process  // the returned value is saved in eax.
	testl %eax,%eax
	js 1f
	push %gs
	pushl %esi
	pushl %edi
	pushl %ebp
	pushl %eax
	call copy_process
	addl $20,%esp
1:	ret

在执行到call copy_process时,系统会进行再次压栈处理,此时的栈内容为:ss(2) -> sp(2) -> eflags(4) -> cs(2) -> ip(2) -> ds(2) -> es(2) -> fs(2) -> edx(4) -> ecx(4) -> ebx(4) -> ip(2)[pushl %eax] -> ip(2)[addl $20, %esp]-> gs(2) -> esi(4) -> edi(4) -> ebp(4) -> eax(4)


下面会进入copy_process程序
// linux-0.11/kerrnel/fork.c
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)
{
	struct task_struct *p;
	int i;
	struct file *f;

	p = (struct task_struct *) get_free_page();
	if (!p)
		return -EAGAIN;
	task[nr] = p;  // the nr is the empty process id
	*p = *current;	/* NOTE! this doesn't copy the supervisor stack */
	p->state = TASK_UNINTERRUPTIBLE;
	p->pid = last_pid;
	p->father = current->pid;
	p->counter = p->priority;
	p->signal = 0;

	p->alarm = 0;
	p->leader = 0;		/* process leadership doesn't inherit */
	p->utime = p->stime = 0;
	p->cutime = p->cstime = 0;
	p->start_time = jiffies;
	p->tss.back_link = 0;
	p->tss.esp0 = PAGE_SIZE + (long) p;
	p->tss.ss0 = 0x10;
	p->tss.eip = eip;  // set eip as the same with the old process for the new process
	p->tss.eflags = eflags;
	p->tss.eax = 0;  // set eax as 0 for the new process
	p->tss.ecx = ecx;
	p->tss.edx = edx;
	p->tss.ebx = ebx;
	p->tss.esp = esp;
	p->tss.ebp = ebp;
	p->tss.esi = esi;
	p->tss.edi = edi;
	p->tss.es = es & 0xffff;
	p->tss.cs = cs & 0xffff;
	p->tss.ss = ss & 0xffff;
	p->tss.ds = ds & 0xffff;
	p->tss.fs = fs & 0xffff;

	p->tss.gs = gs & 0xffff;
	p->tss.ldt = _LDT(nr);
	p->tss.trace_bitmap = 0x80000000;
	if (last_task_used_math == current)
		__asm__("clts ; fnsave %0"::"m" (p->tss.i387));
	if (copy_mem(nr,p)) {
		task[nr] = NULL;
		free_page((long) p);
		return -EAGAIN;
	}
	for (i=0; i<NR_OPEN;i++)
		if ((f=p->filp[i]))
			f->f_count++;
	if (current->pwd)
		current->pwd->i_count++;
	if (current->root)
		current->root->i_count++;
	if (current->executable)
		current->executable->i_count++;
	set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
	set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
	p->state = TASK_RUNNING;	/* do this last, just in case */
	return last_pid;
}

代码执行到此时已经创建一个新的进程,准备返回ret,出栈执行“addl $20, %esp”指令

执行完“addl $20, %esp”指令

1、此条指令的意思是将esp的指针向上偏移20个字节
2、这样栈中的内容就变为:
ss(2) -> sp(2) -> eflags(4) -> cs(2) -> ip(2) -> ds(2) -> es(2) -> fs(2) -> edx(4) -> ecx(4) -> ebx(4) -> ip(2)[pushl %eax]
3、然后执行ret出栈指令,执行pushl %eax,此时的eax里面存放的要么对应新进程0,要么对应旧进程非0。


此时的代码又回到system_call中
// linux-0.11/kernel/system_call.s
system_call:
    ...
    movl current,%eax  # 将当前的esp保存在eax中
    cmpl $0,state(%eax)		# 比较当前进程运行状态
    jne reschedule  # 不满足条件则进入进程调度,或许可以切换到新的进程中,一般情况下,父进程在这里堵塞,等待子进程返回后父进程再返回
    cmpl $0,counter(%eax)		# counter
    je reschedule

1、如果返回0则是子进程,返回大于0则是父进程
2、返回值是通过eax传递的
3、进程如何调度的,请看下回分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值