[linux 0.11]写时复制的实现
2010-01-26 21:22
211 查看
相比较于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对于用户程序时“透明”的,好像用户程序从一开始就可以正常地进行写操作,而不用顾忌页面是否可写。
写时复制也即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对于用户程序时“透明”的,好像用户程序从一开始就可以正常地进行写操作,而不用顾忌页面是否可写。
相关文章推荐
- 在linux下实现文件复制的功能
- Linux0.11内核--汇编代码实现C函数
- hadoop基础-------虚拟机(三)-----VMware虚拟机下linux系统的与windows主机实现复制粘贴
- 用nasm语言重新实现linux-0.11 键盘驱动程序(us)(博古以通今) (开发版,未精简)
- linux实现cp(复制)功能的小函数
- linux编程初探之 实现文件复制
- 设置固定ip地址并实现远程访问linux --实现如果自己linux出现问题,自己不懂无法复制的问题
- linux两台服务器实现复制
- ubuntu下安装vmware-tools 实现linux与windows的互相复制与粘贴
- linux上使用amoeba实现MySql集群,以及读写分离,主从复制
- (转)Ubuntu 12.04 LTS安装VMware Tools实现linux和window 互相复制:无法找到kernel header path的问题
- linux下VmwareTools安装 实现文件粘贴复制 跨平台操作
- linux系统中使用openssl实现mysql主从复制
- linux下c语言实现多线程文件复制
- linux下c语言实现多线程文件复制
- linux 0.11 虚拟内存管理的实现
- Linux系统调用实现文件复制
- Linux下C语言实现多线程文件复制
- 用Linux命令行实现删除和复制指定类型的文件
- 【转】实现虚拟机VMware上linux与windows互相复制与粘贴