2011年7月17日星期日

Linux 0.11 内核完全注释 笔记(三)

>2 fork 做什么?
>半斤回答:准备LDT,TSS,页目录,页表...

在Linux内核中,一个进程是用struct task_struct类型的变量来标记。所以fork首先是创建了一个新的struct task_struct类型的变量,对其进行相应的赋值。

重要的是,新建进程的TSS,LDT表,页目录和页表是全新创建的,在存储上和父进程完全没有关系,不同的两个实体。但新进程LDT所指向的数据段和代码段完全照抄父亲进程,指令指针也同父进程一致。也就是说,父子进程共享代码段,数据段和堆栈段都是相同的地址空间。好比两个锦囊装着同一妙计,嘿嘿!

插一句:从这里看fork的两次返回是那么的自然,两个进程指令指针(EIP)都一样,能没有两个返回吗?

更重要的事,fork过程中将父子进程所有页表都设置成了Read Only, 为啥子啊? 为了实现Copy On Write技术。由于页表项设置成了只读模式,每次写页表项所对应的页面时(比如函数调用把参数放入堆栈)都会引起写保护页异常,CPU会自动执行异常处理函数,进而调用 do_wp_page 函数来分配一个新的页面,挂载到引起写异常的页表项目,之后设置页表项可读可写。这个技术就叫Copy On Write。Copy On Write 完全是一种按需分配的技术,缺页/写保护页面异常是伟大的设计,因为它缔造了Copy On Write,缔造了虚拟内存技术!

这里还有一个问题值得思考,假设fork之后的程序马上会对数据段进行写动作,进而导致CPU进入写保护页面异常处理程序,谁的页表项会指向新分配的空间呢?父 or 子?

答案取决于谁先引起写保护页面异常。假设儿子引起了异常,那么儿子的页表项会指向新的分配空间;假设是父亲引起了异常,父亲的页表项会指向新空间,这样之前父亲占用的空间就归儿子所有了。很有趣的逻辑啊!

代码参考:
sys_fork -> copy_process
http://oldlinux.org/lxr/http/source/kernel/fork.c#L68
un_wp_page
http://oldlinux.org/lxr/http/source/mm/memory.c#L221