分类: LINUX
2012-05-27 18:37:55
在“Linux页框级内存管理处理细节”一篇博文中,我们谈到内核调用alloc_pages等系列函数分配一个或一片连续的页框。这一系列函数本质上是使用伙伴算法从指定zone_t中取到一个或一片连续的空闲的页框。
正如我们将在以后重点博文“slab分配器”所看到的,内核经常请求和释放单个页框。为了提升系统性能,如果请求单个或释放单个页框时,内核在使用伙伴算法之前多添了一个步骤,即每CPU页框高速缓存。
每个内存管理区定义了一个“每CPU”页框高速缓存,所有“每CPU”高速缓存包含一些预先分配的页框,它们被用于满足本地CPU 发出的单个页内存请求。
更进一步,内核为每个内存管理区和每个CPU提供了两个高速缓存:一个热高速缓存,它存放的页框中所包含的内容很可能就在CPU 硬件高速缓存中;还有一个冷高速缓存。
如果内核或用户态进程在刚分配到页框后就立即向页框写,那么从热高速缓存中获得页框就对系统性能有利。什么意思?我们知道,CPU中的硬件高速缓存存在有
最近使用过的页框。而我们每次对页框存储单元的访问将都会导致原来一个存在于硬件高速缓存的一页被替换掉。当然,除非硬件高速缓存包含有一行:它映射刚被
访问的 “热”页框单元,那么我们称为“命中”。
反过来,如果页框将要被DMA操作填充,那么从冷高速缓存中获得页框是方便的。在这种情况下, 不会涉及到CPU,并且硬件高速缓存的行不会被修改。从冷高速缓存获得页框为其他类型的内存分配保存了热页框储备。
如果实在理解不了上面对热缓存和冷缓存的定义,那我们就干脆这样理解:热缓存跟CPU有关,要使用到对应CPU的高速缓存,当我们读写一个页面时,如果没有命中硬件高速缓存就替换一个页;冷缓存跟CPU无关,当我们读写一个页面时根本不去管有没有命中CPU的硬件缓存。
实现每CPU页框高速缓存的主要数据结构是存放在内存管理区描述符zone_t的pageset字段中的一个per_cpu_pageset数组数据结
构。该数组包含为每个CPU 提供的一个元素;这个元素依次由两个per_cpu_pages描述符组成,一个留给热高速缓存而另一个留给冷高速缓存。
具体数据结构如图所示:
内核使用两个位标来监视热高速缓存或冷高速缓存的大小:如果页框个数低于下界low,内核通过从伙伴系统中分配batch个单一页框来补充对应的高
速缓存;否则,如果页框个数高过上界high,内核从高速缓存中释放batch个页框到伙伴系统中。值batch、low和high本质上取决于内存管理
区中包含的页框个数。
buffered_rmqueue()函数在指定的内存管理区中分配页框。它使用每CPU页框高速缓存来处理单一页框请求。
参数为内存管理区描述符的地址,请求分配的内存大小的对数order,以及分配标志gfp_flags。如果gfp_flags中的_
_GFP_COLD标志被置位,那么页框应当从冷高速缓存中获取,否则它应从热高速缓存中获取(此标志只对单一页框请求有意义)。该函数本质上执行如下操
作:
1. 如果order 不等于0,每CPU 页框高速缓存就不能被使用:函数跳到第4 步。
2. 检查由_ _GFP_COLD标志所标识的内存管理区本地每CPU 高速缓存是否需要补充(per_cpu_pages描述符的count字段小于或等于low字段)。在这种情况下,它执行如下子步骤:
a) 通过反复调用_ _rmqueue()函数从伙伴系统中分配batch 个单一页框。
b) 将已分配页框的描述符插入高速缓存链表中。
c) 通过给count 增加实际被分配页框的个数来更新它。
3. 如果count值为正,则函数从高速缓存链表获得一个页框,count 减1 并跳到第5步。(注意,每CPU 页框高速缓存有可能为空,当在第2a 步调用_ _rmqueue()函数而分配页框失败时就会发生这种情况。)
4. 到这里,内存请求还没有被满足,或者是因为请求跨越了几个连续页框,或者是因为被选中的页框高速缓存为空。调用_ _rmqueue()函数从伙伴系统中分配所请求的页框。
5. 如果内存请求得到满足,函数就初始化(第一个)页框的页描述符:清除一些标志,将private 字段置0,并将页框引用计数器置1。此外,如果gfp_flags 中的_ _GPF_ZERO 标志被置位,则函数将被分配的内存区域填充0。
6. 返回(第一个)页框的页描述符地址,如果内存分配请求失败则返回NULL。
3 释放页框到每CPU 页框高速缓存
为了释放单个页框到每CPU 页框高速缓存,内核使用free_hot_page ( )
和free_cold_page()函数。它们都是free_hot_cold_page()函数的简单封装,接收的参数为将要释放的页框的描述符地址
page和cold标志(指定是热高速缓存还是冷高速缓存)。
free_hot_cold_page()函数执行如下操作:
1. 从page->flags 字段获取包含该页框的内存管理区描述符地址。
2. 获取由cold 标志选择的管理区高速缓存的per_cpu_pages 描述符的地址。
3.
检查高速缓存是否应该被清空:如果count值高于或等于high,则调用free_pages_bulk()函数,将管理区描述符、将被释放的页框个数
(batch字段)、高速缓存链表的地址以及数字0(为0 到order
个页框)传递给该函数。free_pages_bulkl()函数依次反复调用_
_free_pages_bulk()函数来释放指定数量的(从高速缓存链表获得的)页框到内存管理区的伙伴系统中。
4. 把释放的页框添加到高速缓存链表上,并增加count 字段。
应该注意的是,在当前的Linux 2.6内核版本中,从没有页框被释放到冷高速缓存中:至于硬件高速缓存,内核总是假设被释放的页框是热的。当然,这并不意味着冷高速缓存是空的:当达到下界时通过buffered_rmqueue()补充冷高速缓存。