JOS fork函数 实现机制分析
简直有点小鸡冻哇... 介个地方之前困惑了好1阵...现在叨叨关于fork那些事儿
文章会侧重分析fork的两种实现策略:
1. 不使用COW 策略实现dumbfork (很暴力的拷贝)
2. 使用COW技术的fork(写时复制, parent process , child process任意1个进程对共同映照的空间有改动,就产生拷贝动作, 改动了哪页拷贝哪页, 不是全部user space空间的拷贝).
大家都知道, 在Unix类系统里面, 创建1个子进程最经常使用的就是fork.
而且有个很牛逼轰轰的技术,叫做COW(copy on write) 被利用在这上面.
首先说明1个连接器细节. 每一个linker在生成最后的可履行程序的时候,都会在bss段的末尾做个标记 -- end.
可以把这个end当作1个全局变量,是个指针,指向bss段的末尾.(bss本来就是所有段的末尾,那末这个指针指向的可履行程序的末尾..而bss段又几近不占空间的,因而其实又是指向数据段的末尾的.)
左侧的是 obj/kern/kernel.sym的部份截图 右侧的是 obj/user/dumbfork.sym
我们关注end标记就能够了. 每一个程序编译完了以后都会在 bss段后面加上 end.
在用户空间程序里面援用的就是用户空间这个end, 而不是内核那个
这是1个很有必要的background.下面我们来分析两种fork 策略
1. dumbfork.c (我不贴全部的代码,只做重要的理论分析, 全部代码可以去github看, 这样写出来的东西才成心义)
这里sys_exofork仅仅只是为新进程分配了1个新的 env结构体, 用来描写新的子进程.
而子进程的用户空间内存还没有分配.
d
之前我在这里恐慌了好久, 我很狐疑, 为何这里就敢给子进程的全局变量thisenv赋值呢? 后面可是会duppage把全部parent process的用户空间数据拷贝过去的啊. 不就覆盖了么. 这赋值操作不就白做了么? 我很当时很愁闷(年轻人啊, too young too naive啊...). 要知道这里子进程可还是没有运行的! 所以压根还不会产生thisenv的赋值操作, 子进程还没有运行, 等parent运行快完了, 才会把child 设置成 runable.以后才会运行子进程, 进而进入 if (envid == 0)
再继续看看,究竟怎样copy parent process到child process的.
调用duppage() 把从 UTEXT开始的地址处1直拷贝到end (客官如果忘记的话,往前翻)
其实这里UTEXT ~ end只有不是很大的1段用户空间. (建议自己去cprintf, 把这两个地址打印出来, 然后对比 memlayout.h去看, 瞬间就明白了. 我之前在这里被坑了几天, 各种毁3观)
最后, 我们把用户的可履行程序, 全局变量神马的都拷贝了(for循环里面的duppage).但是我们还没有拷贝栈啊. 栈的地址在 end的上面.
因而就有了 duppage(envid, ROUNDDOWN(& addr, PGSIZE));
这里又恐慌了好久, 由于我没有注意到他传入的是指针addr的地址, 而不是addr指向的地址.我伙呆, 由于这里addr是个函数局部变量, 是在栈上面的. 因而利用这个地址是个栈上地址, 再ROUNDDOWN就找到栈最低地址了,直接duppage. 因而就弄定了user space stack的拷贝. 也就完成了进程的拷贝.
2. lib/fork.c
要看懂这个fork实现1定要明白user space page fault handler机制.这个是N多策略的基础.
传送门:http://blog.csdn.net/cinmyheart/article/details/45271455
看前面和dumbfork还是很相似的,都是调用sys_exofork来取得1个新的struct env.
不同的是后面.究竟是怎样实现COW(copy on write)的呢?
后面两层for循环, 根据页目录也页表对存在的页(PTE_P), 除异常栈以外统统映照.
而后, 异常栈是两个进程,产生write操作之前, 唯1不同享的内存区域. 后面单独给异常栈映照内存.
还是duppage.
策略超赞. 首先不管原来的页面是不是是可以写的(PTE_W or PTE_COW), 都把当前进程的页面以
perm = PTE_U | PTE_P进行映照.
1. 如果有可以PTE_W或PTE_COW,
那末我们都以 perm = PTE_U | PTE_P | PTE_COW进行映照 ,注意不要给PTE_W权限了.
2.如果perm里面存在PTE_COW,那末就以perm = PTE_U | PTE_P | PTE_COW重新映照本身
等duppage完事的时候, 两个进程空间内, 相同的虚拟地址所有的权限都是1样的(还是除开异常栈).
两个进程中, 任意1个进程尝试对页面进行写操作的时候, 都会触发page fault, 由于没有 PTE_W权限.
而这里user space 的page fault handler则会PTE_P | PTE_W | PTE_U的权限重新申请1页物理内存去添加到对应进程中去.
哎, 感觉这么清楚直观的机制, 我之前怎样就死纠结捏.... 折腾好久了这个fork. 今天算是有个交代了~
上一篇 JDBC的使用