Linux0.11写时复制机制详解
当执行fork时,子进程除了通过get_free_page申请到一个页面用来存放内核栈和任务数据结构,子进程复制父进程的页表指向父进程空间,父进程空间被设置写保护,当父进程或子进程发生写的操作时,产生一个写保护中断,这时系统会再次申请一个空闲的页面,将原来的页面复制过来并重新映射到进程空间。
fork的核心是copy_mem,其作用是设置子进程的LDT并对父进程空间进行复制。
kernel/fork.c
可执行文件结构
如果data_limit小于data_limit或者code_base不等于data_base则报错死机。在linux0.11中代码段基地址和数据段基地址必须是相等的,并且必须满足条件data_limit = code_limit+数据段长度,这一点理解起来有点奇怪。
设置子进程代码段和数据段基地址为进程基地址(64MB对齐)。
接下来就是最重要的步骤复制页表copy_page_tables。这里仅仅是复制父进程的页表,并没有整个复制父进程的进程空间,父子进程共享原来父进程的空间,只有当其中一个进程发生写页面操作的时候才会对要写的页面复制出一个副本对这个副本进行写操作,这样以来可以极大地节约内存空间和减少创建进程时的开销。
mm/memory.c
copy_page_tables接收三个参数,from源地址,也就是我们父进程的基地址;to目的地址,也就是子进程的基地址 = nr × 64MB;size表示要复制多少。
mm/memory.c
copy_page_tables首先检查源地址是否64MB对齐,如果不是则报错死机。根据源地址和目的地址分别计算出对应的页目录项(取线性地址高10位×4),计算要复制多少个页表(一个页表指向4MB空间)。
mm/memory.c
接下来有两个循环,最外面的for循环复制页表,里面嵌套的for复制页表项。如果子进程的页目录项有效(P=1)意味着要覆盖一个有效的页目录,报错死机。当这个循环结束后子进程就会得到一个和父进程一模一样的页目录和页表,也就是父子进程共享了原来父进程的空间,共享空间的页面引用数+1。
注意第177~178行将父子进程中页表项都设置为只读,这样共享空间就被保护起来,当父进程或子进程尝试对其进行写操作就会进入写保护异常处理函数,进而发生写时复制(COW)。
mm/memory.c
最后刷新cpu页缓冲器保存以上的修改。
对共享空间写会导致写保护异常,cpu就会执行写保护异常处理函数do_wp_page。
mm/memory.c
do_wp_page虽然有两个参数,但是有用的只有address,就是发生异常的地址,当执行完异常处理函数还要回到发生异常的语句处重新执行。do_wp_page实际上里面只有一个un_wp_page函数,他的参数是发生写保护异常的页目录项。
mm/memory.c
un_wp_page作用是解除写保护,他分两种情况处理。第一种情况是未共享页面,也就是页面引用数为1的页面,直接修改页表项属性;第二种是对共享页面处理,方法是申请一个空闲页面设置为可读可写,并把这个新页面映射到原来的线性地址,把共享页面内容复制过来,然后接下来对这个副本进行操作。注意,第二种情况中还要把页面引用数-1,当页面引用数为1时就直接修改页表项不用再复制页面了,算是一个以逸待劳的方法!
所以真正的写时复制是发生在un_wp_page中。
- End -
看雪ID: 极目楚天舒
https://bbs.pediy.com/user-741085.htm
本文由看雪论坛 极目楚天舒 原创
转载请注明来自看雪社区
热门图书推荐:
热门技术文章:
公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com