📄 5 forkϵͳ
字号:
fork系统调用是一个复杂而又重要的过程,主要涉及到进程管理和内存管理两个方面。
首先分配了一页内存作为进程控制块,然后完全把父进程的进程控制快中的内容(一整页,包括内核状态下的堆栈)全部复制给子进程,然后复制父进程的内存(这里用到了写时复制的特性,所以只复制了页目录和页表),然后在根据设置各种控制字段,最后加入到进程列表中参与调度。
父进程的内存过程是比较复杂的,主要由函数copy_page_table实现,mm/memory.c
复制了整个页目录,复制了前768项的页表的页表(如果已经映射),并把属性改为只读。而后边的256项页目录项是完全复制过来的。这样就基本上完成了fork的过程,但是这是父子进程对空间只能读而不能写,这时就形成了写时复制。因为父子进程访问的是同一块物理内存,并且属性为只读,因此当父进程或子进程对内存进行写操作时,会引起0X0E号写页面异常,这是我们可以根据页面异常的地址进行页面复制。系统异常处理的入口是_exc_handler,然后再根据异常号和错误码处理各种异常。具体由do_wp_page实现(mm/memory.c)
//取消地址addr页面的写保护,如果页面共享,则复制页面
void do_wp_page(u32 addr)
{
addr_t *table_item;
s32 page_index;
u32 index;
addr_t addr_in_page_dir;
addr_t err_addr;
//取消页面的写保护即
addr_in_page_dir = paddr_to_vaddr(current->tss.cr3) + ((addr >> 20) & 0x0FFC);
table_item = (addr_t *)(paddr_to_vaddr((*(addr_t *)(addr_in_page_dir) & 0xFFFFF000)
+ ((addr >> 10) & 0x0FFC)));
index = paddr_to_index(((*table_item) & 0xFFFFF000));
printk("mem_map[%d]:%d\n", index, mem_map[index]);
if (mem_map[index] == 1) { //如果页面引用数为1,则说明没有与之共享的页面了
*table_item |= PAGE_RW;
} else { //不然说明有多个进程共享页面
mem_map[index]--; //减少页面引用数
//获取空闲页面
page_index = get_free_page();
if (! page_index) {
panic("do_wp_page get_free_page\n");
}
//复制页面
err_addr = addr & 0xFFFFF000;
if (err_addr < PAGE_OFFSET) { //如果在user区域,则进行地址转换
printk("from: %XH, to: %XH\n", paddr_to_vaddr(*table_item) & 0xFFFFF000, index_to_vaddr(page_index));
memcpy((void *)index_to_vaddr(page_index), (void *)(paddr_to_vaddr(*table_item) & 0xFFFFF000), PAGE_SIZE);
} else {
//这里似乎不需要,因为内核不会发生写保护异常的
panic("kernel do_wp_page\n");
}
//复制页面后,建立映射
*table_item = index_to_paddr(page_index) | 7;
}
__flush_tlb(); //刷新TLB
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -