date: 2014-10-03 19:09
1 页面交换策略
所谓内存页面的周转有两方面的含义:其一是物理页面的分配、使用和回收,并不一定涉及页面的盘区交换;其二才是盘区交换,盘区交换的最终目的也是为了页面回收。并非所有的内存页面都可以交换出去,只有映射到用户空间的内存页面才会被换成,而内核即系统空间的页面不在此列。需要指出,内核可以访问所有的内存页面,换言之,所有物理页面(HIGH_MEM区的高端物理内存除外)在系统空间中都有映射的,所谓“用户空间的页面”,是指在至少一个进程的用户空间中有映射的页面,反之就是内核使用的页面。
显然最简单的页面交换策略就是,每次缺页异常时就分配一个内存页面,并从磁盘页面上读入内容;如果没有空闲的内存页面可用分配,就设法将一个或多个内存页面换出到磁盘此面上,从而腾出一些页面来。但这种消极应对的策略有一个缺陷,这种临时抱佛脚总发生在系统忙碌的时候(系统忙着分配页面哩)而没有调度的余地。比较积极的办法是定期地,最好是在系统空闲时,挑选一些内存页面换出而腾出依稀内存空间,保持一定的空闲内存供应量,使得在缺页异常发生时,有页面可分配。至于挑选的准则,一般都是LRU即“最近最少使用”准则。但这种策略实施起来比较困难,因为实际上并不存在一种准确预测页面访问的方法,可能上一秒刚刚把一个内存页面换出,下一秒,系统又要访问这个物理页面,只好又把它赶快换进来,造成所谓的页面“抖动”。
为了防止这种情况发生,页面的换出与释放分两步走。当系统挑选出若干页面准备换出时,将这些页面的内容写入磁盘页面,相应的页面表项pte_t也变身为swp_entry_t(P表中为0,表示页面不再内存中),但所占据的内存页面并不立即释放(内存页面上的内容仍保留),而是将其page结构留在一个缓冲队列中,使其从“活跃状态”变为“不活跃状态”。就像NBA球员从“首发”变为“替补”,至于是否要让他去看“饮水机”即内存页面的最终释放,则要继续考察一段时间再说。这样如果一个页面被换出后又被立即访问从而触发页面异常,就可以从缓冲队列(替补席)里找到对应的内存页面,再次为之建立映射(swp_entry_t变成pte_t),从“不活跃状态”变为“活跃状态”。由于此页面尚未被释放,其上的内容仍是有效的,就不需要从磁盘页面上读入内容了。反之,如果经过一段时间的老化,一个不活跃的页面还是没有受到访问,此时已经不再有(用户空间中的)虚存页面映射到该内存页面上了,该内存页面到了最后释放的时候了。
这种策略显然可以减少抖动,但还是有改进的余地。首先,在页面换出时,如果自从上次页面换出内存页面中的内容并没有改变,那么这个页面就是“干净”的,也就是与磁盘页面上的内容一致。这样的页面当然不用费事再写到磁盘页面上。其次,即使是“脏”页面,也不必立即写出去,而可以先从页面映射表断开,经过一段时间的“冷却”或“老化”后再写出去,从而变成“干净”的页面。而至于“干净”的页面,也不用着急立即释放,可以继续缓冲直到真有必要时再释放,因为回收一个干净的页面,代价是很小的。
2 一个内存页面的旅行
物理页面周转要点如下图所示:
- ①空闲。page结构通过list链入到内存管理区的空闲队列free_area中,其使用计数count为0;
- ②分配。page结构从空闲队列中脱链链,其链表头list空闲,使用计数count为1;
- ③活跃状态。page结构通过链表头lru链入全局的活跃页面队列active_list,并通过链表头list链入swapper_space中干净页面队列clean_pages;
- ④不活跃脏状态。page结构通过链表头lru链入全局的不活跃脏页面列表inactive_dirty_list,并通过链表头list链入swapper_space中脏页面列表dirty_pages。原则上不再有任何进程的页面表项指向该页面,每次断开页面映射时都使page结构的引用计数减1;
- ⑤不活跃干净状态。将不活跃脏页面的内容写入磁盘页面,内存页面就被“洗白”了,进入不活跃干净状态。page结构通过链表头lru链入管理区的不活跃干净页面队列inactive_clean_list;
- ⑥如果在转入不活跃状态一段时间内页面受到访问,则又转入活跃状态(状态③)并恢复映射;
- ⑦当有需要时,就从不活跃干净的页面队列中回收页面,或者退回到空闲队列中(图中的状态⑦),或者直接分配出去(图中状态②)
图中涉及到结构及变量说明如下:
- active_list和inactive_dirty_list是两个全局性的LRU队列。此外内核还在每个内存管理区设置了一个inactive_clean_list。page通过链表头lru链入这三个LRU队列之一,根据page在LRU队列中的位置,就可以知道page的“冷却”或“老化”程度,为页面回收提供依据。为什么active_list和inactive_dirty_list是全局队列而inactive_clean_list却率属于某个内存管理区?原因是系统在挑选要换出的内存页面时“一视同仁”,不区分内存页面所在的管理区。而当页面进入不活跃干净状态时,该页面有可能被回收到其所在管理区的空闲队列中,或者是再被分配出去,这两种情况都和内存管理区相关,所以莫不如将不活跃干净页面归到各自的内存管理区名下。
- 因为页面page可以在三个LRU队列之间转移,所以需要page结构中设置标志(flag字段),来表示当前在哪个LRU队列中,这三个标志分别是:PG_active、PG_inactive_dirty和PG_inactive_clean。
- 此外,内核还通过一个全局address_space结构swapper_space来管理所有可交换的内存页面,每个可交换的内存页面都通过page结构中链表头list链入其中的一个队列。address_space结构以及全局变量swapper_space的定义如下:
<include/linux/fs.h>
struct address_space {
struct list_head clean_pages; /* list of clean pages */
struct list_head dirty_pages; /* list of dirty pages */
struct list_head locked_pages; /* list of locked pages */
unsigned long nrpages; /* number of total pages */
struct address_space_operations *a_ops; /* methods */
struct inode *host; /* owner: inode, block_device */
struct vm_area_struct *i_mmap; /* list of private mappings */
struct vm_area_struct *i_mmap_shared; /* list of shared mappings */
spinlock_t i_shared_lock; /* and spinlock protecting it */
};
<mm/swap_state.c>
static struct address_space_operations swap_aops = {
writepage: swap_writepage,
sync_page: block_sync_page,
};
//swapper_space的定义如下:
struct address_space swapper_space = {
LIST_HEAD_INIT(swapper_space.clean_pages),
LIST_HEAD_INIT(swapper_space.dirty_pages),
LIST_HEAD_INIT(swapper_space.locked_pages),
0, /* nrpages */
&swap_aops,
};
swapper_space.a_ops被初始化成swap_aops,swap_aops定义的换出到磁盘页面的函数为swap_writepage。
还记得page结构中有一个address_space结构成员mapping吗?当把一个page加入到swapper_space中的某个队列时,page. mapping就被赋值成swapper_space(的地址)。