fork()源码分析(linux0.11)

linux0.11中,fork函数通过宏定义的方式被调用,宏定义声明如下:

#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \ //执行0x80中断程序,由于权限发生了变化,所以会切换到内核栈
	: "=a" (__res) \            //参见intel手册的卷3第六章中断描述符部分
	: "0" (__NR_##name)); \  //系统调用号放进eax寄存器
if (__res >= 0) \
	return (type) __res; \
errno = -__res; \
return -1; \
}

实际负责创建进程的sys_fork()函数

进入中断程序后,紧接着调用sys_fork函数开始创建子进程

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				#pop stack quickly
1:	ret

 sys_fork首先调用find_empty_process函数(在kernel/fork.c文件)获取一个可用的pid,并从进程数组task里找一个空位用来承载即将创建的进程

之后将寄存器压栈,用于后续填充新进程的tss,调用copy_process(在kernel/fork.c文件)函数,开始为新的进程填充内容。

复制进程copy_process()函数

首先,通过get_free_page()函数申请了一页内存,用作新进程的PCB(process control block)和内核栈。

申请物理页内存get_free_page()函数

unsigned long get_free_page(void)
{
register unsigned long __res asm("ax");

__asm__("std ; repne ; scasb\n\t" # 从后往前在mem_map中寻找可用的页号
	"jne 1f\n\t"
	"movb $1,1(%%edi)\n\t"        # 将mem_map[(ecx - 1) + 1]对应的项设为1,表示已被使用了
	"sall $12,%%ecx\n\t"          # 算数左移12位获取物理页相对偏移地址
	"addl %2,%%ecx\n\t"           # +LOW_MEM得到物理页的物理地址
	"movl %%ecx,%%edx\n\t"
	"movl $1024,%%ecx\n\t"
	"leal 4092(%%edx),%%edi\n\t"  # 将申请到的内存页初始化为0
	"rep ; stosl\n\t"
	"movl %%edx,%%eax\n"          # 申请到的物理页首地址赋给eax
	"1:"
	:"=a" (__res)
	:"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),
	"D" (mem_map+PAGING_PAGES-1)
	);
return __res;
}

%2为可用内存起始地址(1MB开始及后面部分),%3ecx设为总页数,edi设为mem_map的最后一项

PCB复制

获取完物理内存后,将父进程的PCB所有内容都复制给子进程。之后对子进程的一些属性进行处理。其中新进程内核栈ss0的栈顶esp0被设为物理页地址+PAGE_SIZE,指定了子进程返回值eax为0等。

页表复制copy_mem()函数

该函数在经过一系列检查后,确定代码段和数据段的基址一样。

根据进程号获取到该进程的代码段和数据段的线性基地址,每个进程都有64MB的线性地址空间

随后调用了copy_page_table函数用于创建新进程的页表

  • copy_page_table()函数
    int copy_page_tables(unsigned long from,unsigned long to,long size)
    {
    	unsigned long * from_page_table;
    	unsigned long * to_page_table;
    	unsigned long this_page;
    	unsigned long * from_dir, * to_dir;
    	unsigned long nr;
    
    	if ((from&0x3fffff) || (to&0x3fffff))
    		panic("copy_page_tables called with wrong alignment");
    	from_dir = (unsigned long *) ((from>>20) & 0xffc); //父进程线性基地址对应的页目录
    	to_dir = (unsigned long *) ((to>>20) & 0xffc);     //子进程线性基地址对应的页目录
    	size = ((unsigned) (size+0x3fffff)) >> 22;         //需要用到的页目录个数
    	for( ; size-->0 ; from_dir++,to_dir++) {
    		if (1 & *to_dir)
    			panic("copy_page_tables: already exist");
    		if (!(1 & *from_dir))
    			continue;
    		from_page_table = (unsigned long *) (0xfffff000 & *from_dir);
    		if (!(to_page_table = (unsigned long *) get_free_page()))
    			return -1;	/* Out of memory, see freeing */
    		*to_dir = ((unsigned long) to_page_table) | 7;
    		nr = (from==0)?0xA0:1024;
            //将父进程的页表项映射到子进程的页表中
    		for ( ; nr-- > 0 ; from_page_table++,to_page_table++) {
    			this_page = *from_page_table;
    			if (!(1 & this_page))
    				continue;
    			this_page &= ~2;
    			*to_page_table = this_page;
    			if (this_page > LOW_MEM) {
    				*from_page_table = this_page;
    				this_page -= LOW_MEM;
    				this_page >>= 12;
    				mem_map[this_page]++;
    			}
    		}
    	}
    	invalidate();
    	return 0;
    }

    接下来设置进程的ldt,tss段描述符后,又修改新进程的状态,随后return last_pid

接下来执行ret_from_sys_call函数,进行处理信号等工作,最后iret返回用户态继续执行被中断的代码。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值