内存回收算法总是会在一定的时间将一些内存回收, 内存回收算法是通过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 - 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链表选取相应的页面回收
#define lru_to_page(head) (list_entry((head)->prev, struct page, lru))
将返回链表的最后一个元素,它代表老化程度最高的一个page