内存回收算法总是会在一定的时间将一些内存回收, 内存回收算法是通过LRU链表对page页面进行管理的,对于那些新的页面会将其插入到LRU链表头,回收时将返回LRU链表末尾的元素,代表老化程度最高的页面
基本数据结构
typedef struct pglist_data {
...
/* Fields commonly accessed by the page reclaim scanner */
/*
* NOTE: THIS IS UNUSED IF MEMCG IS ENABLED.
*
* Use mem_cgroup_lruvec() to look up lruvecs.
*/
struct lruvec __lruvec;
...
内存页回收的管理是以node为单位的,pglist_data 中会包含一个struct lruvec数组
struct lruvec {
struct list_head lists[NR_LRU_LISTS];
...
这个struct lruvec数组包含了NR_LRU_LISTS个LRU链表元素,这些链表元素定义如下,每个链表将放置不同类别的page,相关类别由enum lru_list定义
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
};
//mm/swap.c
/*
* The following struct pagevec are grouped together because they are protected
* by disabling preemption (and interrupts remain enabled).
*/
struct lru_pvecs {
local_lock_t lock;
struct pagevec lru_add;
struct pagevec lru_deactivate_file;
struct pagevec lru_deactivate;
struct pagevec lru_lazyfree;
#ifdef CONFIG_SMP
struct pagevec activate_page;
#endif
};
static DEFINE_PER_CPU(struct lru_pvecs, lru_pvecs) = {
.lock = INIT_LOCAL_LOCK(lock),
};
内核同时定义了struct lru_pvecs , 它包含了5个struct pagevec变量
struct pagevec {
unsigned char nr;
bool percpu_pvec_drained;
struct page *pages[PAGEVEC_SIZE];
};
这些struct pagevec变量实际是用来管理具体的page页面的
如上相关结构体有如下的对应关系
lru_cache_add
/**
* 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)
{
struct pagevec *pvec;
VM_BUG_ON_PAGE(PageActive(page) && PageUnevictable(page), page);
VM_BUG_ON_PAGE(PageLRU(page), page);
get_page(page);
local_lock(&lru_pvecs.lock);
pvec = this_cpu_ptr(&lru_pvecs.lru_add);
if (!pagevec_add(pvec, page) || PageCompound(page))
__pagevec_lru_add(pvec);
local_unlock(&lru_pvecs.lock);
}
EXPORT_SYMBOL(lru_cache_add);
- pagevec_add:实际就是将页面加入到struct lru_pvecs的某个struct pagevec中,这里实际是加入到lru_pvecs.lru_add中,并返回当前struct pagevec可管理的剩余的page个数
/*
* Add a page to a pagevec. Returns the number of slots still available.
*/
static inline unsigned pagevec_add(struct pagevec *pvec, struct page *page)
{
pvec->pages[pvec->nr++] = page;
return pagevec_space(pvec);
}
- __pagevec_lru_add:如果struct pagevec可管理的剩余的page个数为0,则需要执行__pagevec_lru_add,它实际是将已经满的struct pagevec中的page页迁移到某个lru链表中去,至于迁移到哪个lru链表,由当前page所处的struct pagevec类别 以及page自身的flags变量决定,迁移时会考虑到各个页面的老化程度,越老的页面越会靠近lru链表的后面,经典内存回收算法将扫描lru链表选取相应的页面回收
lru_to_page
#define lru_to_page(head) (list_entry((head)->prev, struct page, lru))
将返回链表的最后一个元素,它代表老化程度最高的一个page