Copy-On-Write

 

写时复制

写时复制技术最初产生于Unix 系统,用于实现一种傻瓜式的进程创建:当发出fork(  ) 系统调用时,内核原样复制父进程的整个地址空间并把复制的那一份分配给子进程。这种行为是非常耗时的,因为它需要:

 

·      为子进程的页表分配页面

·      为子进程的页分配 页面

·      初始化子进程的页表

·      把父进程 的页复制到子进程相应的页中

 

创建一个地址空间的这种方法涉及许多内存访问,消耗许多CPU 周期,并且完全破坏了高速缓存中的内容。在大多数情况下,这样做常常 是毫无意义的,因为许多子进程通过装入一个新的程序开始它们的执行,这样就完全丢弃了所继承的地址空间。

 

现在的Unix 内核(包括Linux ),采用一种更为有效的方法称之为写时复制(或COW )。 这种思想相当简单:父进程和子进程共享页面而不是复制页面。然而,只要页面被共享,它们就不能被修改。无论父进程和子进程何时试图写一个共享的页面,就产 生一个错误,这时内核就把这个页复制到一个新的页面中并标记为可写。原来的页面仍然是写保护的:当其它进程试图写入时,内核检查写进程 是否是这个页面的唯一属主;如果是,它把这个页面标记为对这个进程是可写的。

 

Page 结构的count 域用于 跟踪共享相应页面的进程数目。只要进程释放一个页面或者在它上面执行写时复制,它的count 域就递减;只有当count 变为NULL 时,这个页面才被释放。

现在我们讲述Linux 怎样实现写时复制(COW )。当handle_pte_fault(  ) 确定“缺页”错误是由请求写一个页面所引起的时(这个页面存在于内存中且是写保护的),它执行以下语句:

if (pte_present(pte)) {

    entry = pte_mkyoung(entry);

    set_pte( pte, entry);

    flush_tlb_page( vma, address);

    if (write_access) {

        if (!pte_write(entry))

            return do_wp_page(tsk, vma, address, pte);

        entry = pte_mkdirty(entry);

        set_pte( pte, entry);

         flush_tlb_page( vma, address);

        }

    return 1;

}

 

首先,调用pte_mkyoung(  ) set_pte(  ) 函数来设置引起错误的页所对应 页表项的访问位。这个设置使页“年轻” 并减少它被交换到磁盘上的机会。如果错误由违背写保护而引起的,handle_pte_fault(  ) 返回由do_wp_page(  ) 函数产生的值;否则,则已检测到某一错误情况(例如,用户态地址空间中的页,其User/Supervisor 标志为0 ),且函数返回1

do_wp_page(  ) 函数首先把page_table 参数所引用的页表 项装入局部变量pte ,然后再获得一个新页面:

pte = *page_table;

new_page = __get_free_page( GFP_USER);

 

由于页面的分配可能阻塞进程,因此,一旦获得页面,这个函数就在页表 项上执行下面的一致性检查:

 

·      当进程等待一个空闲的页面时,这个页是否 已经被交换出去(pte *page_table 的值不相同)

·      这个 页是否 已不在物理内存中(页表 项中页的 Present 标志为0

·      页现在 是否可写(页项中页 Read/Write 标志为1

  如果这些情况中的任意一个发生,do_wp_page(  ) 释放以前所获得的页面,并返回1

 

现在,函数更新次级缺页的数目,并把引起错误的页的页描述符指针保存到page_map 局部变量中。

tsk ->min_flt++;

page_map = mem_map + MAP_NR( old_page);

 

接下来,函数必须确定是否必须真的把这个页复制一份。如果仅有一个进程使用这个页,就无须应用写时复制技术,而且进程应该能够自由地写这个页。因此,这个页面被标记为可写,这样当试图写入的时候就不会再次引起“缺页”错误,以前分配的新的页面也被释放,函数结束并返回1 。这种检查是通过读取page 结构的count 域而进行的:

if (page_map->count == 1) {

    set_pte( page_table, pte_mkdirty(pte_mkwrite(pte)));

    flush_tlb_page( vma, address);

    if (new_page)

        free_page( new_page);

    return 1;

}

 

 

相反,如果这个页面由两个或多个进程所共享,函数把旧页面(old_page) 的内容复制到新分配的页面(new_page) 中:

if (old_page == ZERO_PAGE)

    memset( (void *) new_page, 0, PAGE_SIZE);

else

    memcpy( (void *) new_page, (void *) old_page, PAGE_SIZE);

set_pte( page_table, pte_mkwrite(pte_mkdirty(

        mk_pte( new_page, vma->vm_page_prot))));

flush_tlb_page( vma, address);

__free_page( page_map);

return 1;

 

 

如果旧页面是零页面,就使用memset 宏把新的页面填充为0 。否则,使用memcpy 宏复制页面的内容。不要求一定要对零页作特殊的处理,但是特殊处理确实能够提高系统的性能,因为它使用很少的地址而保护了微处理器的硬件高速缓存。

然后,用新页面的物理地址更新页表的表项,并把新页面标记为可写和脏。 最后,函数调用__free_pages(  ) 减小对旧页面的引用计数。

6.5.6 对本节的几点说明

1. 通过fork ()建立进程,开始时只有一个页目录和一页左右的可执行页 ,于是缺页异常会 频繁发生。

2. 虚拟地址映射到物理地址,只有在请页时 才完成,这时要建立页表和更新页表(页表是动态建立的)。页表不可被换出,不记年龄,它们被内核中保留,只有在exit 时清除。

3. 在处理页故障的过程中,因为要涉及到磁盘访问等耗时操作,因此操作系统会选择另外一个进程进入执行状态,即进行新一轮调度。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值