图3.4 free_area数据结构
在图3.4中,当系统中有大小为两个页面块的请求发出时,第一个4页面大小的内存块(从页面框号4开始)将分成两个2页面大小的块。前一个,从页面框号4开始的,将分配出去返回给请求者,而后一个,从页面框号6开始,将被添加到free_area数组中表示两个页面大小的空闲块的元素1中。
3.4.2 页面回收
将大的页面块打碎进行分配将增加系统中零碎空闲页面块的数目。页面回收代码在适当时机下要将这些页面结合起来形成单一大页面块。事实上页面块大小决定了页面重新组合的难易程度。
当页面块被释放时,代码将检查是否有相同大小的相邻或者buddy内存块存在。如果有,则将它们结合起来形成一个大小为原来两倍的新空闲块。每次结合完之后,代码还要检查是否可以继续合并成更大的页面。最佳情况是系统的空闲页面块将和允许分配的最大内存一样大。
在图3.4中,如果释放页面框号1,它将和空闲页面框号0结合作为大小为2个页面的空闲块排入free_area的第一个元素中。
3.5 内存映射
映象执行时,可执行映象的内容将被调入进程虚拟地址空间中。可执行映象使用的共享库同样如此。然而可执行文件实际上并没有调入物理内存,而是仅仅连接到进程的虚拟内存。当程序的其他部分运行时引用到这部分时才把它们从磁盘上调入内存。将映象连接到进程虚拟地址空间的过程称为内存映射。
图3.5 虚拟内存区域
每个进程的虚拟内存用一个mm_struct来表示。它包含当前执行的映象(如BASH)以及指向vm_area_struct 的大量指针。每个vm_area_struct数据结构描叙了虚拟内存的起始与结束位置,进程对此内存区域的存取权限以及一组内存操作函数。这些函数都是Linux在操纵虚拟内存区域时必须用到的子程序。其中一个负责处理进程试图访问不在当前物理内存中的虚拟内存(通过页面失效)的情况。此函数叫nopage。它用在Linux试图将可执行映象的页面调入内存时。
可执行映象映射到进程虚拟地址时将产生一组相应的vm_area_struct数据结构。每个vm_area_struct数据结构表示可执行映象的一部分:可执行代码、初始化
数据(变量)、未初始化数据等等。Linux支持许多标准的虚拟内存操作函数,创建vm_area_struct数据结构时有一组相应的虚拟内存操作函数与之对应。
3.6 请求换页
当可执行映象到进程虚拟地址空间的映射完成后,它就可以开始运行了。由于只有很少部分的映象调入内存,所以很快就会发生对不在物理内存中的虚拟内存区域的访问。当进程访问无有效页表入口的虚拟地址时,处理器将向Linux报告一个页面错误。
页面错误带有失效发生的虚拟地址及引发失效的访存方式。Linux必须找到表示此区域的vm_area_struct结构。对vm_area_struct数据结构的搜寻速度决定了处理页面错误的效率,而所有vm_area_struct结构是通过一种
AVL(Adelson-Velskii and Landis) 树结构连在一起的。如果无法找到
vm_area_struct与此失效虚拟地址的对应关系,则系统认为此进程访问了非法虚拟地址。这时Linux将向进程发送SIGSEGV信号,如果进程没有此信号的处理过程则终止运行。
如果找到此对应关系,Linux接下来检查引起该页面错误的访存类型。如果进程以非法方式访问内存,比如对不可写区域进行写操作,系统将产生内存错误的信号。
如果Linux认为页面出错是合法的,那么它需要对这种情况进行处理。 首先Linux必须区分位于交换文件中的页面和那些位于磁盘上的可执行映象。Alpha AXP的页表中有可能存在有效位没有设置但是在PFN域中有非0值的页表入口。在这种情况下,PFN域指示的是此页面在交换文件中的位置。如何处理交换文件中的页面将在下章讨论。
不是所有的vm_area_struct数据结构都有一组虚拟内存操作函数,它们有的甚至没有nopage函数。这是因为 Linux通过分配新的物理页面并为其创建有效的页表入口来修正这次访问。如果这个内存区域存在nopage操作函数,Linux将调用它。
一般Linux nopage函数被用来处理内存映射可执行映象,同时它使用页面cache将请求的页面调入物理内存中去。
当请求的页面调入物理内存时,处理器页表也必须更新。更新这些入口必须进行相关硬件操作,特别是处理器使用TLB时。这样当页面失效被处理完毕后,进程将从发生失效虚拟内存访问的位置重新开始运行。
3.7 Linux页面cache
图3.6 Linux页面Cache
Linux使用页面cache的目的是加快对磁盘上文件的访问。内存映射文件以每次一页的方式读出并将这些页面存储在页面cache中。图3.6表明页面cache由page_hash_table,指向mem_map_t数据结构的指针数组组成。
Linux中的每个文件通过一个VFS inode(在文件系统一章中讲叙)数据结构来标识并且每个VFS inode都是唯一的,它可以并仅可以描叙一个文件。页表的索引从文件的VFS inode和文件的偏移中派生出来。
从一个内存映射文件中读出页面,例如产生换页请求时要将页面读回内存中,系统尝试从页面cache来读出。如果页面在cache中,则返回页面失效处理过程一个指向mem_map_t数据结构;否则此页面将从包含映象的文件系统中读入内存并为之分配物理页面。
在映象的读入与执行过程中,页面cache不断增长。当不再需要某个页面时,即不再被任何进程使用时,它将被从页面cache中删除。
3.8 换出与丢弃页面
当系统中物理内存减少时,Linux内存管理子系统必须释放物理页面。这个任务由核心交换后台进程(kswapd )来完成。
核心交换后台进程是一种特殊的核心线程。它是没有虚拟内存的进程,在物理地址空间上以核心态运行。核心交换后台进程的名字容易使人误解,其实它完成的
工作比仅仅将页面交换到系统的交换文件中要多得多。其目标是保证系统中有足够的空闲页面来维持内存管理系统运行效率。
此进程由核心的init进程在系统启动时运行,被核心交换定时器周期性的调用。 当定时器到时后,交换后台进程将检查系统中的空闲页面数是否太少。它使用两个变量:free_pages_high 和free_page_low来判断是否该释放一些页面。只要系统中的空闲页面数大于free_pages_high,核心交换后台进程不做任何工作;它将睡眠到下一次定时器到时。在检查中,核心交换后台进程将当前被写到交换文件中的页面数也计算在内,它使用nr_async_pages来记录这个数值;当有页面被排入准备写到交换文件队列中时,它将递增一次,同时当写入操作完成后递减一次。如果系统中的空闲页面数在free_pages_high甚至 free_pages_low以下时,核心交换后台进程将通过三个途径来减少系统中使用的物理页面的个数: 减少缓冲与页面cache的大小,
? 将系统V类型的内存页面交换出去, ? 换出或者丢弃页面。
?
如果系统中空闲页面数低于free_pages_low,核心交换后台进程将在下次运行之前释放6个页面。否则它只释放3个。以上三种方法将依次使用直到系统释放出足够的空闲页面。当核心交换后台进程试图释放物理页面时它将记录使用的最后一种方法。下一次它会首先运行上次最后成功的算法。
当释放出足够页面后,核心交换后台进程将再次睡眠到下次定时器到时。如果导致核心交换后台进程释放页面的原因是系统中的空闲页面数小于
free_pages_low,则它只睡眠平时的一半时间。一旦空闲页面数大于 free_pages_low则核心交换进程的睡眠时间又会延长。
3.8.1 减少Page Cache和Buffer Cache的大小
Page Cache和Buffer cache中的页面将被优先考虑释放到free_area数组中。Page Cache中包含的是内存映射文件的页面,其中有些可能是不必要的,它们浪费了系统的内存。而Buffer Cache中包含的是从物理设备中读写的缓冲数据,有些可能也是不必要的。当系统中物理页面开始耗尽时,从这些cache中丢弃页面比较简单(它不需要象从内存中交换一样,无须对物理设备进行写操作)。除了会使对物理设备及内存映射文件的访问速度降低外,页面丢弃策略没有太多的副作用。如果策略得当,则所有进程的损失相同。 每次核心交换后台进程都会尝试去压缩这些cache。
它首先检查mem_map页面数组中的页面块看是否有可以从物理内存中丢弃出去的。当系统中的空闲页面数降低 到一个危险水平时,核心后台交换进程频繁进行交换,则检查的页面块一般比较大。检查的方式为轮转,每次试图压缩内存映