[linux 0.11]写时复制的实现

                                                                                                                       相比较于2.4的代码,0.11的比较简洁

写时复制也即copy on write,这种思想相当简单:父进程和子进程共享页面而不是复制页面。然而,只要页面被共享,它们就不能被修改。无论父进程和子进程何时试图写一个共享的页面,就产生一个错误。
就从这个错误开始:当用户试图往一个一个共享页面(用户内存页面)上写时,就触发一次页面异常(int 14)--这是我假想的情况,以此进行分析。
于是cpu强制跳转到页异常中断入口处_page_fault:

 

[主要关注蓝色代码,黑色可跳过]

14 _page_fault:
15         xchgl %eax,(%esp)  #取堆栈中error_code到eax
16         pushl %ecx
17         pushl %edx
18         push %ds
19         push %es
20         push %fs
21         movl $0x10,%edx
22         mov %dx,%ds
23         mov %dx,%es
24         mov %dx,%fs
25         movl %cr2,%edx      #取address到edx
26         pushl %edx              #address-———┬—>调用do_wp_page的参数
27         pushl %eax              #error_code——┘
28         testl $1,%eax           #测试error_code的bit0(p)
29         jne 1f
30         call _do_no_page
31         jmp 2f
32 1:      call _do_wp_page
33 2:      addl $8,%esp
34         pop %fs
35         pop %es
36         pop %ds
37         popl %edx
38         popl %ecx
39         popl %eax
40         iret

 

当发生页异常时,cpu自动产生出错码(error_code)并将其压入堆栈,而异常时访问的线性地址就被保存在寄存器cr2中。
其中出错码是32位的长字。但只用最后3位--分别说明导致异常发生的原因:
bit0(P)--0表示不存在,1表示页保护
bit1(W/R)--0表示读操作,1写
bit2(U/S)--0超级用户下执行,1用户模式
这里我们的情况对应的是 bit0--1,bit1--1,bit--1 所以经过28行,经由29行直接跳转到32行--也即调用do_wp_page:

 

247 void do_wp_page(unsigned long error_code,unsigned long address)
248 {
 ...

255         un_wp_page((unsigned long *)
256                 (((address>>10) & 0xffc) + (0xfffff000 &
257                 *((unsigned long *) ((address>>20) &0xffc)))));
258
259 }

 

在0.11中,do_wp_page实际上只是调用了un_wp_page,而其参数:
(address>>20) &0xffc--页目录地址,
0xfffff000 & *(address>>20) &0xffc--页目录项,也即页表地址
(address>>10) & 0xffc--页表中的偏移地址
所以最终un_wp_page的参数是页表中表项的指针(如果对这个指针“*”取值操作将得到线性地址address对应的物理内存页面起始地址--4KB对齐)


221 void un_wp_page(unsigned long * table_entry)
222 {
223         unsigned long old_page,new_page;
224
225         old_page = 0xfffff000 & *table_entry;
226         if (old_page >= LOW_MEM && mem_map[MAP_NR(old_page)]==1) {
227                 *table_entry |= 2;
228                 invalidate();
229                 return;
230         }
231         if (!(new_page=get_free_page()))
232                 oom();
233         if (old_page >= LOW_MEM)            
234                 mem_map[MAP_NR(old_page)]--;
235         *table_entry = new_page | 7;            
236         invalidate();
237         copy_page(old_page,new_page);

238 }


old_page是address对应的物理地址所在物理内存页面--4KB对齐
由于这里是用户试图往共享页面进行写操作,所以mem_map[MAP_NR(old_page)]应该大于1,226行if条件不满足直接到231行。
通过get_free_page()获取一页物理内存(4KB),new_page即为该内存页的物理地址。
234行,由于是用户内存页面,所以页面复制后,原来使用页面计数自减。
235行,更新指定页表项为新页面地址,并置页表项为:用户级/可读写/存在于内存
然后刷性页变换告诉缓冲,最后调用copy_page完成“写时复制”的复制操作。

 

54 #define copy_page(from,to) /
55 __asm__("cld ; rep ; movsl"::"S" (from),"D" (to),"c" (1024):"cx","di","si")

 

可见copy_page是写时复制最底层的操作了,它将物理页面起始地址old_page上1个物理内存页面(4KB)的内容复制到物理页面new_page上。


整理一下:_page_fault--->do_wp_page--->un_wp_page--->copy_page

 

特别要说明下,当cpu从此次异常处理返回用户空间,将会重新执行 原先引发页异常(写时复制) 那条指令(应该是那条写指令)。
这也是 为什么应该叫页面异常,而不是页面中断的原因(中断返回后会执行引发中断的指令的下一条指令)。

但是整个copy_on_write对于用户程序时“透明”的,好像用户程序从一开始就可以正常地进行写操作,而不用顾忌页面是否可写。

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值