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返回用户态继续执行被中断的代码。