页框回收算法(PFRA)
通过请求调页机制,只要用户态进程继续执行,他们就能获得页框,然而,请求调页没有办法强制进程释放不再使用的页框。因此,迟早所有空闲内存将被分配给进程和高速缓存。linux内核的页框回收算法采取从用户态进程和内核高速缓存“窃取”页框的办法补充伙伴系统的空闲块列表。页框回收算法的目标之一就是保存最少的空闲页框池以便内核可以安全地从“内存紧缺”的情形中恢复过来。
选择目标页
页框回收算法(PFRA)的目标就是获得页框并使之空闲。PFRA按照页框所含内容,以不同的方式处理页框。我们将他们区分成:不可回收页,可交换页,可同步页和可丢弃页:
页类型 | 说明 | 回收操作 |
---|---|---|
不可回收页 | 空闲页(包含子伙伴系统列表中) 保留页(PG_reserved标志置位) 内核动态分配页 进程内核态堆栈页 临时锁定页(PG_locked标志置位) 内存锁定页(在先行区中且VM_LOCKED标志置位) | 不允许也无需回收 |
可回收页 | 用户态地址空间的匿名页 Tmpfs文件系统的映射页(如IPC共享内存的页) | 将页的内容保存在交换区 |
可同步页 | 用户态地址空间的映射页 存有磁盘文件数据且在页高速缓存中的页 块设备缓冲区页 某些磁盘高速缓存的页(如索引节点高速缓存) | 必要时,与磁盘镜像同步这些页 |
可丢弃页 | 内存高速缓存中的未使用页(如slab分配器高速缓存) 目录项高速缓存的未使用页 | 无需操作 |
PFRA设计
PFRA采用的几个原则:
-
首先释放“无害”页:在进程用户态地址空间的页回收之前,必须先回收没有被任何进程使用的磁盘与内存高速缓存中的页。
-
将用户态进程的所有页定为可回收页:除了锁定页,PFRA必须能够窃得任何用户态进程页,包括匿名页。这样,睡眠较长时间的进程将逐渐失去所有页框。
-
同时取消引用一个共享页框的所有页表项的映射,就可以回收该共享页框:当PFRA要释放几个进程共享的页框时,它就清空引用该页框的所有页表项,然后回收该页框。
-
只回收“未用”页:使用简化的最近最少使用(LRU)置换算法,PFRA将页分为“在用”和“未用”。如果某页很长时间没有被访问,那么它将来被访问的可能性较小,就可以将它看作未用;另一方面,如果某页最近被访问过,那么它将来被访问的可能性较大,就必须将它看作在用。PFRA只回收未用页。
PFRA实现
页框回收算法的执行有三种基本情形:
-
内存紧缺回收:内核发现内存紧缺
-
睡眠回收
-
周期回收:周期性激活内核线程执行内存回收算法。
最近最少使用(LRU)链表
LRU 链表
属于进程用户态地址空间或页高速缓存的所有页被分为两组:活动链表和非活动链表。它们统称LRU链表。活动链表用于存放最近被访问过的页,非活动链表存放有一段时间没有被访问过的页。显然,页必须从非活动链表中窃取。LRU链表根据页面类型又分为LRU_ANON和LRU_FILE。所以内核中一共有5个LRU链表,在<include/linux/mmzon.h>中定义如下:
#define LRU_BASE 0
#define LRU_ACTIVE 1
#define LRU_FILE 2
enum lru_list {
LRU_INACTIVE_ANON = LRU_BASE,
LRU_ACTIVE_ANON = LRU_BASE + LRU_ACTIVE,
LRU_INACTIVE_FILE = LRU_BASE + LRU_FILE,
LRU_ACTIVE_FILE = LRU_BASE + LRU_FILE + LRU_ACTIVE,
LRU_UNEVICTABLE,
NR_LRU_LISTS
};
- LRU_INACTIVE_ANON:不活跃匿名页面链表
- LRU_ACTIVE_ANON:活跃匿名页面链表
- LRU_INACTIVE_FILE:不活跃文件映射页面链表
- LRU_ACTIVE_FILE:活跃文件映射页面链表
- LRU_UNEVICTABLE:不可回收页面链表
LRU链表之所以要这样分类,是因为当内存紧缺时总是优先换出page cache页面,而不是匿名页面。因为大多数情况下page cache页面不需要回写磁盘,除非页面内容被修改了,匿名页面总是要被写入交换分区才能被换出。LRU链表按照zone来配置,每个zone中都有一整套LRU链表,zone中的lruvec指向这些链表。struct lruvec数据结构中定义了上述各种LRU类型的链表:
struct lruvec {
struct list_head lists[NR_LRU_LISTS];
struct zone_reclaim_stat reclaim_stat;
#ifdef CONFIG_MEMCG
struct zone *zone;
#endif
};
函数lru_cache_add将页面加入到LRU链表,代码如下:
/**
* lru_cache_add - add a page to a page list
* @page: the page to be added to the LRU.
*
* Queue the page for addition to the LRU via pagevec. The decision on whether
* to add the page to the [in]active [file|anon] list is deferred until the
* pagevec is drained. This gives a chance for the caller of lru_cache_add()
* have the page added to the active list using mark_page_accessed().
*/
void lru_cache_add(struct page *page)
{
VM_BUG_ON_PAGE(PageActive(page) && PageUnevictable(page), page);
VM_BUG_ON_PAGE