申请分配物理页的时候,页分配器首先尝试使用低水线分配页;如果使用低水线分配失败,说明内存轻微不足,页分配器将会唤醒内存节点的页回收内核线程,异步回收页,然后尝试使用最低水线分配页;如果使用最低水线分配失败,说明内存严重不足,页分配器将会直接回收页;
针对不同的物理页,采用不同的回收策略;物理页根据是否有存储设备支持分为两类:
1)可以被交换的页:没有存储设备支持的物理页,包括匿名页,以及tmpfs文件系统(内存中的文件系统)的文件页和进程在修改私有的文件映射时复制生成的匿名页;
2)存储设备支持的文件页;
针对不同的物理页,采用不同的回收策略;
1)可以被交换的页:采用页交换的方法,先把页的数据写到交换区,然后释放物理页;
2)存储设备支持的文件页;
针对不同得物理页,采用不同得回收策略:
1)可以被交换的页:采用页交换的方法,先把页的数据写到交换区,然后释放物理页;
2)存储设备支持的文件页:如果是干净的页,即把文件从存储设备读到内存以后没有修改过,可以直接释放页;如果是脏页,即把文件从存储设备读到后修改过,那么先写回到存储设备,然后释放物理页;
页回收算法还会回收slab缓存;使用专用slab缓存的内核模块可以使用函数register_shrinker注册收缩器,页回收算法调用所有收缩器的函数以释放对象;
**根据什么原则选择待回收的物理页?**内核使用LRU(Least Recently Used,最近最少使用)算法选择最近最少使用的物理页,来进行回收;
回收物理页的时候,如果物理页被映射到进程的虚拟地址空间,那么需要从页表中删除虚拟页到物理页的映射;怎么知道物理页被映射到哪些虚拟页?需要通过反向映射的数据结构,虚拟页映射到物理页是正向映射,物理页映射到虚拟页是反向映射;
数据结构
LRU链表
页回收算法使用LRU算法选择待回收的物理页;如下图所示,每个内存节点的pglist_data实例有一个成员lruvec,成为LRU向量;
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
};
struct lruvec {
struct list_head lists[NR_LRU_LISTS];
struct zone_reclaim_stat reclaim_stat;
/* Evictions & activations on the inactive file list */
atomic_long_t inactive_age;
/* Refaults at the time of last reclaim cycle */
unsigned long refaults;
#ifdef CONFIG_MEMCG
struct pglist_data *pgdat;
#endif
};
typedef struct pglist_data {
struct zone node_zones[MAX_NR_ZONES];
struct zonelist node_zonelists[MAX_ZONELISTS];
int nr_zones;
#ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */
struct page *node_mem_map;
......
/* Fields commonly accessed by the page reclaim scanner */
struct lruvec lruvec;
......
}
LRU向量包含5条LRU链表;
- 不活动匿名页LRU链表,用来链接不活动的匿名页,即最近访问频率低的匿名页;
- 活动匿名页LRU链表,用来链接活动的匿名页,即最近访问频率高的匿名页;
- 不活动文件页LRU链表,用来链接不活动的文件页,即最近访问频率低的文件页;
- 活动文件页LRU链表,用来链接活动的文件页,即最近访问频率高的文件页;
- 不可回收LRU链表,用来链接使用mlock锁定在内存中、不允许回收的物理页;
在LRU链表中,物理页的页描述符(struct page)的flags成员会被设置如下:
- flags被设置PG_lru标志位,表示物理页在LRU链表中;
- 页描述符通过lru成员加入到LRU链表;
- 如果是可以被交换的物理页,flags会被设置PG_swapbacked标志位;
- 如果是活动的物理页,flags会被设置PG_active标志位;
- 如果是不可回收的物理页,flags会被设置PG_unevictable标志位;
每条LRU链表中的物理页按访问时间从小到大排序,链表头节点的物理页的访问时间离当前最近,物理页从LRU链表的头部插入;页回收算法从不活动LRU链表的尾部取物理页进行回收,从活动LRU链表的尾部取物理页并移动到不活动LRU链表中;
如何确定页的活动程度?
- 如果是页表映射的匿名页或文件页,根据页表项中的访问标志位确定页的活动程度;
- 如果是没有页表映射的文件页,进程通过文件系统read或write访问文件,文件系统在文件的页缓存中查找文件页,为文件页的页描述符设置访问标志位(PG_referenced);
反向映射
回收页表映射的匿名页或文件页时,需要从页表中删除映射,内核需要知道物理页被映射到哪些进程的虚拟地址空间,需要实现物理页到虚拟页的反向映射;
页描述符中和反向映射相关的成员如下:
struct page {
...
union {
struct address_space *mapping; /* If low bit clear, points to
* inode address_space, or NULL.
* If page mapped as anonymous
* memory, low bit is set, and
* it points to anon_vma object:
* see PAGE_MAPPING_ANON below.
*/
...
};
/* Second double word */
union {
pgoff_t index; /* Our offset within mapping. */
...
};
union {
...
struct {
union {
/*
* Count of ptes mapped in mms, to show when
* page is mapped & limit reverse map searches.
*
* Extra information about page type may be
* stored here for pages that are never mapped,
* in which case the value MUST BE <= -2.
* See page-flags.h for more details.
*/
atomic_t _mapcount;
...
};
...
};
};
...
};
mapping
,最低两位用来作用页映射标志,PAGE_MAPPING_ANON表示匿名页;
index
,是在映射里面的偏移,单位是页;如果是匿名映射,那么index是物理页对应的虚拟页在虚拟内存中的页偏移;如果是文件映射,那么index是物理页存储的数据在文件页中的页偏移;
mapcount
,映射计数,反映物理页物理页被映射到多少个虚拟内存区域;初始值是-1,加上1以后才是真实的映射计数;
本文中只介绍匿名页的反向映射;文件页的反向映射请查阅相关书籍;
匿名页的反向映射
匿名页的反向映射的数据结构如下图:
1)struct page的成员mapping指向一个anon_vma实例,并设置了PAGE_MAPPING_ANON标志位;
2)struct anon_vma用来组织匿名页被映射到的所有虚拟内存区域;
3)anon_vma_chain用来关联anon_vma实例和vm_area_struct实例;
4)一个匿名页可能被映射到多个虚拟内存区域,比如fork子进程;anon_vma实例通过中介anon_vma_chain把所有vm_area_struct实例和anon_vma实例加入到链表中,关联起来;
页回收过程
如下图所示,申请分配页的时候,页分配器首先尝试使用低水线分配页;如果使用低水线分配失败,说明内存轻微不足,页分配器将会唤醒所有符合分配条件的内存节点的页回收线程,异步回收页,然后尝试使用最低水线分配页;如果分配失败,说明内存严重不足,页分配器将会直接回收页;如果直接回收页失败,那么判断是否应该重新尝试页回收;
异步回收页和直接回收页,最后都是调用函数shrink_node来回收内存节点的页;