6.5.4 请求调页

术语请求调页指的是一种动态内存分配技术,它把页面的分配推迟到不能再推迟为止,也就是说,一直推迟到进程要访问的页不在物理内存时为止,由此引起一个缺页错误。

 

  请求调页技术的引入主要是因为进程开始运行的时候并不访问其地址空间中的全部地址;事实上,有一部分地址也许进程永远不使用。此外,程序的局部性原理保证了在程序执行的每个阶段,真正使用的进程页只有小部分,因此临时用不着的页所在的物理页面可以由其它进程来使用。因此,对于全局分配(一开始就给进程分配所需要的全部页面,直到程序结束才释放这些页面)来说,请求调页是首选的,因为它增加了系统中的空闲页面的平均数,从而更好地利用空闲内存。从另一个观点来看,在内存总数保持不变的情况下,请求调页从总体上能使系统有更大的吞吐量。

 

为这一切优点付出的代价是系统额外的开销:由请求调页所引发的每个“缺页”错误必须由内核处理,这将浪费CPU的周期。幸运的是,局部性原理保证了一旦进程开始在一组页上运行,在接下来相当长的一段时间内它会一直停留在这些页上而不去访问其它的页:这样我们就可以认为“缺页”错误是一种稀有事件。

基于以下原因,被寻址的页可以不在主存中:

·      进程永远也没有访问到这个页。内核能够识别这种情况,这是因为页表相应的表项被填充为0,也就是说,pte_none宏返回1

·      进程已经访问过这个页,但是这个页的内容被临时保存在磁盘上。内核能够识别这种情况,这是因为页表相应表项没被填充为0(然而,由于页面不存在物理内存中,Present0)。

 

handle_pte_fault(  )函数通过检查与address相关的页表项来区分这两种情况:

entry = *pte;

if (!pte_present(entry)) {

    if (pte_none(entry))

        return do_no_page(tsk, vma, address, write_access,

                          pte);

    return do_swap_page(tsk, vma, address, pte, entry,

                        write_access);

}

 

我们将在交换机制一节检查页被保存到磁盘上的这种情况(do_swap_page(  ) 函数)

 在其它情况下,当页从未被访问时则调用do_no_page(  )函数。有两种方法装入所缺的页,这取决于这个页是否被映射到磁盘文件。该函数通过检查vma虚拟区描述符的nopage域来确定这一点,如果页与文件建立起了映射关系,则nopage域就指向一个把所缺的页从磁盘装入到RAM的函数。因此,可能的情况是:

·      vma->vm_ops->nopage域不为NULL。在这种情况下,某个虚拟区映射一个磁盘文件,nopage域指向从磁盘读入的函数。这种情况涉及到磁盘文件的低层操作。

·      或者vm_ops域为NULL,或者vma->vm_ops->nopage域为NULL。在这种情况下,虚拟区没有映射磁盘文件,也就是说,它是一个匿名映射。因此,do_no_page(  )调用do_anonymous_page(  )函数获得一个新的页面:

    if (!vma->vm_ops || !vma->vm_ops->nopage)

          return do_anonymous_page(tsk, vma, page_table,

write_access);

 

  do_anonymous_page(  )函数分别处理写请求和读请求:

    if (write_access) {

        page = __get_free_page(GFP_USER);

        memset((void *)(page), 0, PAGE_SIZE)

       entry = pte_mkwrite(pte_mkdirty(mk_pte(page,

                vma->vm_page_prot)));

        vma->vm_mm->rss++;

        tsk->min_flt++;

        set_pte(pte, entry);

         return 1;

} 

 

当处理写访问时,该函数调用__get_free_page(  )分配一个新的页面,并利用memset宏把新页面填为0。然后该函数增加tskmin_flt域以跟踪由进程引起的次级缺页(这些缺页只需要一个新页面)的数目,再增加进程的内存区结构vma->vm_mmrss域以跟踪分配给进程的页面数目。然后页表相应的表项被设为页面的物理地址,并把这个页面标记为可写和脏两个标志。

 

相反,当处理读访问时,页的内容是无关紧要的,因为进程正在对它进行第一次寻址。给进程一个填充为0的页要比给它一个由其它进程填充了信息的旧页更为安全。Linux在请求调页方面做得更深入一些。没有必要立即给进程分配一个填充为零的新页面,由于我们也可以给它一个现有的称为零页的页,这样可以进一步推迟页面的分配。零页在内核初始化期间被静态分配,并存放在empty_zero_page变量中(一个有1024个长整数的数组,并用0填充);它存放在第六个页面中(从物理地址0x00005000开始),并且可以通过ZERO_PAGE宏来引用。

因此页表项被设为零页的物理地址:

   entry = pte_wrprotect(mk_pte(ZERO_PAGE, vma->vm_page_prot));

   set_pte(pte, entry);

   return 1;

 

由于这个页被标记为不可写,如果进程试图写这个页,则写时复制机制被激活。当且仅当在这个时候,进程才获得一个属于自己的页并对它进行写。这种机制在下一部分进行描述。