1.回收整体框图
__get_free_pages:返回的是虚拟地址;
alloc_pages:返回的是struct page*结构;
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
{
struct page *page;
/*
* __get_free_pages() returns a 32-bit address, which cannot represent
* a highmem page
*/
VM_BUG_ON((gfp_mask & __GFP_HIGHMEM) != 0);
page = alloc_pages(gfp_mask, order);
if (!page)
return 0;
return (unsigned long) page_address(page);
}
/*
* This is the 'heart' of the zoned buddy allocator.
*/
struct page *
__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,
struct zonelist *zonelist, nodemask_t *nodemask)
{
struct zoneref *preferred_zoneref;
struct page *page = NULL;
unsigned int cpuset_mems_cookie;
int alloc_flags = ALLOC_WMARK_LOW|ALLOC_CPUSET|ALLOC_FAIR; /* 先用low水位申请 */
gfp_t alloc_mask; /* The gfp_t that was actually used for allocation */
struct alloc_context ac = {
.high_zoneidx = gfp_zone(gfp_mask),/* 计算zone index */
.nodemask = nodemask,
.migratetype = gfpflags_to_migratetype(gfp_mask),
};
gfp_mask &= gfp_allowed_mask;
/* 第一次申请 */
/* First allocation attempt */
alloc_mask = gfp_mask|__GFP_HARDWALL;
page = get_page_from_freelist(alloc_mask, order, alloc_flags, &ac);
if (unlikely(!page)) {
alloc_mask = memalloc_noio_flags(gfp_mask);
/* 第二次申请,内部唤醒kswapd */
page = __alloc_pages_slowpath(alloc_mask, order, &ac);
}
return page;
}
2.快速回收
在get_page_from_freelist针对不满足的水位要求的zone进行回收。
处理流程如下:
1:判断LOW水位是否可以分配order内存;
2:如果不满足分配条件,先触发zone_reclaim,然后再次判断是否满足分配条件;
static struct page *
get_page_from_freelist(gfp_t gfp_mask, unsigned int order, int alloc_flags,
const struct alloc_context *ac)
{
for_each_zone_zonelist_nodemask(zone, z, zonelist, ac->high_zoneidx,
ac->nodemask) {
/* LOW水位 */
mark = zone->watermark[alloc_flags & ALLOC_WMARK_MASK];
/* 判断LOW水位是否可以分配 */
if (!zone_watermark_ok(zone, order, mark,
ac->classzone_idx, alloc_flags)) {
/* 回收 */
ret = zone_reclaim(zone, gfp_mask, order);
/* 再次判断 */
if (zone_watermark_ok(zone, order, mark,
ac->classzone_idx, alloc_flags))
{
}
}
page = buffered_rmqueue(ac->preferred_zone, zone, order,
gfp_mask, ac->migratetype);
}
}
struct scan_control {
/* How many pages shrink_list() should reclaim */
unsigned long nr_to_reclaim; /* 需要回收的页数 */
/* This context's GFP mask */
gfp_t gfp_mask; /* 如果是申请内存过程中进行页回收,这里是内存分配使用的标志 */
/* Allocation order */
int order; /* 内存分配时指定的order */
/*
* Nodemask of nodes allowed by the caller. If NULL, all nodes
* are scanned.
*/
nodemask_t *nodemask; /* 允许回收的内存节点 */
/*
* The memory cgroup that hit its limit and as a result is the
* primary target of this reclaim invocation.
*/
struct mem_cgroup *target_mem_cgroup;
/* Scan (total_size >> priority) pages at once */
int priority; /* 优先级,优先级越低,扫描页数越多,默认12 */
unsigned int may_writepage:1; /* 是否可以进行回写 */
/* Can mapped pages be reclaimed? */
unsigned int may_unmap:1; /* 是否进行unmap */
/* Can pages be swapped as part of reclaim? */
unsigned int may_swap:1; /* 是否可以页交换 */
/* Can cgroups be reclaimed below their normal consumption range? */
unsigned int may_thrash:1;
unsigned int hibernation_mode:1;
/* One of the zones is ready for compaction */
unsigned int compaction_ready:1; /* 是否进行内存压缩 */
/* Incremented by the number of inactive pages that were scanned */
unsigned long nr_scanned; /* 已经扫描的页数 */
/* Number of pages freed so far during a call to shrink_zones() */
unsigned long nr_reclaimed; /* 已经回收的页数 */
};
/*
* Try to free up some pages from this zone through reclaim.
*/
static int __zone_reclaim(struct zone *zone, gfp_t gfp_mask, unsigned int order)
{
/* Minimum pages needed in order to stay on node */
const unsigned long nr_pages = 1 << order;
struct task_struct *p = current;
struct reclaim_state reclaim_state;
struct scan_control sc = {
.nr_to_reclaim = max(nr_pages, SWAP_CLUSTER_MAX), /* 最少回收32页 */
.gfp_mask = (gfp_mask = memalloc_noio_flags(gfp_mask)),
.order = order,
.priority = ZONE_RECLAIM_PRIORITY, /* 优先级为4 */
.may_writepage = !!(zone_reclaim_mode & RECLAIM_WRITE), /* 是否可以回写,可以在配置文件中配置 */ /* “/proc/sys/vm/zone_reclaim_mode” */
.may_unmap = !!(zone_reclaim_mode & RECLAIM_SWAP), /* 是否可以unmap,可以在配置文件中配置 */
.may_swap = 1, /* 允许页交换 */
};
cond_resched();
/*
* We need to be able to allocate from the reserves for RECLAIM_SWAP
* and we also need to be able to write out pages for RECLAIM_WRITE
* and RECLAIM_SWAP.
*/
p->flags |= PF_MEMALLOC | PF_SWAPWRITE;
lockdep_set_current_reclaim_state(gfp_mask);
reclaim_state.reclaimed_slab = 0;
p->reclaim_state = &reclaim_state;
/*
* pgdat->min_unmapped_pages 是“/proc/sys/vm/min_unmapped_ratio”乘上总的页数。
* 页缓存中潜在可回收页数如果大于pgdat->min_unmapped_pages才做页回收
*/
if (zone_pagecache_reclaimable(zone) > zone->min_unmapped_pages) {
/*
* Free memory by calling shrink zone with increasing
* priorities until we have enough memory freed.
*/
do {
shrink_zone(zone, &sc, true); /* 根据sc控制进行回收 */
} while (sc.nr_reclaimed < nr_pages && --sc.priority >= 0); /* 回收的页面不满足就降低优先级,扫描更多的页 */
}
p->reclaim_state = NULL;
current->flags &= ~(PF_MEMALLOC | PF_SWAPWRITE);
lockdep_clear_current_reclaim_state();
return sc.nr_reclaimed >= nr_pages;
}
/*
* This is a basic per-zone page freer. Used by both kswapd and direct reclaim.
*/
static void shrink_lruvec(struct lruvec *lruvec, int swappiness,
struct scan_control *sc, unsigned long *lru_pages)
{
unsigned long nr[NR_LRU_LISTS];
unsigned long targets[NR_LRU_LISTS];
unsigned long nr_to_scan;
enum lru_list lru;
unsigned long nr_reclaimed = 0;
unsigned long nr_to_reclaim = sc->nr_to_reclaim;
struct blk_plug plug;
bool scan_adjusted;
/*
* 计算好每个lru链表需要扫描的个数保存到nr中
* 与sc->priority和swappiness有关系
*/
get_scan_count(lruvec, swappiness, sc, nr, lru_pages);
/* Record the original scan target for proportional adjustments later */
memcpy(targets, nr, sizeof(nr));
/*
* Global reclaiming within direct reclaim at DEF_PRIORITY is a normal
* event that can occur when there is little memory pressure e.g.
* multiple streaming readers/writers. Hence, we do not abort scanning
* when the requested number of pages are reclaimed when scanning at
* DEF_PRIORITY on the assumption that the fact we are direct
* reclaiming implies that kswapd is not keeping up and it is best to
* do a batch of work at once. For memcg reclaim one check is made to
* abort proportional reclaim if either the file or anon lru has already
* dropped to zero at the first pass.
*/
/*
* 是否将nr[]中的数量页数都扫描完才停止
* 如果是针对整个zone进行扫描,并且不是在kswapd内核线程中调用的,优先级为默认优先级,就会无视需要回收的页框数量,只有将nr[]中的数量页数都扫描完才停止
* 快速回收不会这样做(快速回收的优先级不是DEF_PRIORITY)
*/
scan_adjusted = (global_reclaim(sc) && !current_is_kswapd() &&
sc->priority == DEF_PRIORITY);
blk_start_plug(&plug);
/* 不包括LRU_ACTIVE_ANON, 这里原因大概是因为系统不太希望对匿名页lru链表中的页回收 */
while (nr[LRU_INACTIVE_ANON] || nr[LRU_ACTIVE_FILE] ||
nr[LRU_INACTIVE_FILE]) {
unsigned long nr_anon, nr_file, percentage;
unsigned long nr_scanned;
/*
* 以LRU_INACTIVE_ANON,LRU_INACTIVE_ANON,LRU_INACTIVE_FILE,LRU_ACTIVE_FILE这个顺序遍历lru链表
* 然后对遍历到的lru链表进行扫描,一次最多32个页框
*/
for_each_evictable_lru(lru) {
/* nr[lru类型]如果有页框需要扫描 */
if (nr[lru]) {
/*
* 获取本次需要扫描的页框数量,nr[lru]与SWAP_CLUSTER_MAX的最小值
* 也就是每一轮最多只扫描SWAP_CLUSTER_MAX(32)个页框
*/
nr_to_scan = min(nr[lru], SWAP_CLUSTER_MAX);
nr[lru] -= nr_to_scan;
/*
* 对此lru类型的lru链表进行内存回收
* 一次扫描的页框数是nr[lru]与SWAP_CLUSTER_MAX的最小值,也就是如果全部能回收,一次也就只能回收SWAP_CLUSTER_MAX(32)个页框
* 都是从lru链表末尾向前扫描
* 本次回收的页框数保存在nr_reclaimed中
*/
nr_reclaimed += shrink_list(lru, nr_to_scan,
lruvec, sc);
}
}
/*
* 没有回收到足够页框,或者需要忽略需要回收的页框数量,尽可能多的回收页框,则继续进行回收
* 当scan_adjusted为真时,扫描到nr[三个类型]数组中的数都为0为止,会忽略是否回收到足够页框,即使回收到足够页框也继续进行扫描
* 也就是尽可能的回收页框,越多越好,alloc_pages()会是这种情况
*/
if (nr_reclaimed < nr_to_reclaim || scan_adjusted)
continue;
/* 到这里,肯定已经达到要回收的要求了 */
/*
* For kswapd and memcg, reclaim at least the number of pages
* requested. Ensure that the anon and file LRUs are scanned
* proportionally what was requested by get_scan_count(). We
* stop reclaiming one LRU and reduce the amount scanning
* proportional to the original scan target.
*/
/* 扫描一遍后,剩余需要扫描的文件页数量和匿名页数量 */
nr_file = nr[LRU_INACTIVE_FILE] + nr[LRU_ACTIVE_FILE];
nr_anon = nr[LRU_INACTIVE_ANON] + nr[LRU_ACTIVE_ANON];
/*
* It's just vindictive to attack the larger once the smaller
* has gone to zero. And given the way we stop scanning the
* smaller below, this makes sure that we only make one nudge
* towards proportionality once we've got nr_to_reclaim.
*/
/* 已经扫描完成了,退出循环 */
if (!nr_file || !nr_anon)
break;
/*
* 下面就是计算再扫描多少页框,会对nr[]中的数进行相应的减少
* 调用到这里肯定是kswapd进程或者针对memcg的页框回收,并且已经回收到了足够的页框了
* 如果nr[]中还剩余很多数量的页框没有扫描,这里就通过计算,减少一些nr[]待扫描的数量
* 设置scan_adjusted,之后把nr[]中剩余的数量扫描完成
*/
if (nr_file > nr_anon) {
unsigned long scan_target = targets[LRU_INACTIVE_ANON] +
targets[LRU_ACTIVE_ANON] + 1;
lru = LRU_BASE;
percentage = nr_anon * 100 / scan_target;
} else {
unsigned long scan_target = targets[LRU_INACTIVE_FILE] +
targets[LRU_ACTIVE_FILE] + 1;
lru = LRU_FILE;
percentage = nr_file * 100 / scan_target;
}
/* Stop scanning the smaller of the LRU */
nr[lru] = 0;
nr[lru + LRU_ACTIVE] = 0;
/*
* Recalculate the other LRU scan count based on its original
* scan target and the percentage scanning already complete
*/
lru = (lru == LRU_FILE) ? LRU_BASE : LRU_FILE;
nr_scanned = targets[lru] - nr[lru];
nr[lru] = targets[lru] * (100 - percentage) / 100;
nr[lru] -= min(nr[lru], nr_scanned);
lru += LRU_ACTIVE;
nr_scanned = targets[lru] - nr[lru];
nr[lru] = targets[lru] * (100 - percentage) / 100;
nr[lru] -= min(nr[lru], nr_scanned);
scan_adjusted = true;
}
blk_finish_plug(&plug);
sc->nr_reclaimed += nr_reclaimed;
/*
* Even if we did not try to evict anon pages at all, we want to
* rebalance the anon lru active/inactive ratio.
*/
/* 非活动匿名页lru链表中页数量太少 */
/* 从活动匿名页lru链表中移动一些页去非活动匿名页lru链表,最多32个 */
if (inactive_anon_is_low(lruvec))
shrink_active_list(SWAP_CLUSTER_MAX, lruvec,
sc, LRU_ACTIVE_ANON);
/* 如果太多脏页进行回写了,这里就睡眠100ms */
throttle_vm_writeout(sc->gfp_mask);
}
/*
* 对lru链表进行处理
* lru: lru链表的类型
* nr_to_scan: 需要扫描的页框数量,此值 <= 32,当链表长度不足32时,就为链表长度
* lruvec: lru链表描述符,与lru参数结合就得出待处理的lru链表
* sc: 扫描控制结构
*/
static unsigned long shrink_list(enum lru_list lru, unsigned long nr_to_scan,
struct lruvec *lruvec, struct scan_control *sc)
{
/* 如果lru类型是活动lru(包括活动匿名页lru和活动文件页lru) */
if (is_active_lru(lru)) {
/* 如果此活动lru对应的非活动lru链表中维护的页框数量太少,则会从活动lru链表中移动一些到对应非活动lru链表中
* 这里需要注意,文件页和匿名页的非活动lru链表中是否少计算方式是不同的
* 匿名页的话,有一个经验值表示大概多少匿名页保存到非活动匿名页lru链表
* 文件页的话,大概非活动文件页数量要大于活动文件页
* 而如果遇到page->_count == 0的页,则会将它们释放到每CPU页框高速缓存中
*/
/* 从活动lru中移动一些页框到非活动lru中,移动nr_to_scan个,nr_to_scan <= 32,从活动lru链表末尾拿出页框移动到非活动lru链表头
* 只有代码段的页最近被访问了,会将其加入到活动lru链表头部,其他页即使最近被访问了,也移动到非活动lru链表
*/
if (inactive_list_is_low(lruvec, lru))
shrink_active_list(nr_to_scan, lruvec, sc, lru);
return 0;
}
/* 如果lru类似是非活动lru,那么会对此lru类型的lru链表中的页框进行回收 */
return shrink_inactive_list(nr_to_scan, lruvec, sc, lru);
}
1:从活动LRU链表尾部节点移动到非活动链表头部;
2:清除Access标志;未清除PG_referenced;(这个地方是关键)
3:(特殊情况)针对最近访问过的文件可执行page,移动到文件活动链表头部;其他的移动到非活动链表头部,不管是否访问过;
static void shrink_active_list(unsigned long nr_to_scan,
struct lruvec *lruvec,
struct scan_control *sc,
enum lru_list lru)
{
unsigned long nr_taken;
unsigned long nr_scanned;
unsigned long vm_flags;
LIST_HEAD(l_hold); /* The pages which were snipped off */
LIST_HEAD(l_active);
LIST_HEAD(l_inactive);
struct page *page;
struct zone_reclaim_stat *reclaim_stat = &lruvec->reclaim_stat;
unsigned long nr_rotated = 0;
isolate_mode_t isolate_mode = 0;
int file = is_file_lru(lru);
struct zone *zone = lruvec_zone(lruvec);
/* 将当前CPU的多个pagevec中的页都放入lru链表中 */
lru_add_drain();
if (!sc->may_unmap)
isolate_mode |= ISOLATE_UNMAPPED;
if (!sc->may_writepage)
isolate_mode |= ISOLATE_CLEAN;
spin_lock_irq(&zone->lru_lock);
/* 从lruvec中lru类型链表的尾部拿出一些页隔离出来,放入到l_hold中,lru类型一般是LRU_ACTIVE_ANON或LRU_ACTIVE_FILE
* 也就是从活动的lru链表中隔离出一些页,从活动lru链表的尾部依次拿出
* 当sc->may_unmap为0时,则不会将有进程映射的页隔离出来
* 当sc->may_writepage为0时,则不会将脏页和正在回写的页隔离出来
* 隔离出来的页会page->_count++
* nr_taken保存拿出的页的数量
*/
nr_taken = isolate_lru_pages(nr_to_scan, lruvec, &l_hold,
&nr_scanned, sc, isolate_mode, lru);
if (global_reclaim(sc))
__mod_zone_page_state(zone, NR_PAGES_SCANNED, nr_scanned);
reclaim_stat->recent_scanned[file] += nr_taken;
__count_zone_vm_events(PGREFILL, zone, nr_scanned);
__mod_zone_page_state(zone, NR_LRU_BASE + lru, -nr_taken);
__mod_zone_page_state(zone, NR_ISOLATED_ANON + file, nr_taken);
spin_unlock_irq(&zone->lru_lock);
/* 将l_hold中的页一个一个处理 */
while (!list_empty(&l_hold)) {
cond_resched();
page = lru_to_page(&l_hold);
list_del(&page->lru);
/* 如果页是unevictable(不可回收)的,则放回到LRU_UNEVICTABLE这个lru链表中,这个lru链表中的页不能被交换出去 */
if (unlikely(!page_evictable(page))) {
putback_lru_page(page);
continue;
}
if (unlikely(buffer_heads_over_limit)) {
if (page_has_private(page) && trylock_page(page)) {
if (page_has_private(page))
try_to_release_page(page, 0);
unlock_page(page);
}
}
/* 检查此页面最近是否有被访问过,通过映射了此页的页表项的Accessed进行检查,并且会清除页表项的Accessed标志
* 如果此页最近被访问过,返回的是Accessed为1的数量页表项数量
*/
if (page_referenced(page, 0, sc->target_mem_cgroup,
&vm_flags)) {
nr_rotated += hpage_nr_pages(page);
/*
* Identify referenced, file-backed active pages and
* give them one more trip around the active list. So
* that executable code get better chances to stay in
* memory under moderate memory pressure. Anon pages
* are not likely to be evicted by use-once streaming
* IO, plus JVM can create lots of anon VM_EXEC pages,
* so we ignore them here.
*/
/*
* 如果此页映射的是代码段,则将其放到l_active链表中,此链表之后会把页放入页对应的活动lru链表中
* 可以看出对于代码段的页,还是比较倾向于将它们放到活动文件页lru链表的
* 当代码段没被访问过时,也是有可能换到非活动文件页lru链表的
*/
if ((vm_flags & VM_EXEC) && page_is_file_cache(page)) {
list_add(&page->lru, &l_active);
continue;
}
}
/*
* 将页放到l_inactive链表中,清除Active标志
* 只有最近访问过的代码段的页不会被放入,其他即使被访问过了,也会被放入l_inactive
*/
ClearPageActive(page); /* we are de-activating */
list_add(&page->lru, &l_inactive);
}
/*
* Move pages back to the lru list.
*/
spin_lock_irq(&zone->lru_lock);
/*
* Count referenced pages from currently used mappings as rotated,
* even though only some of them are actually re-activated. This
* helps balance scan pressure between file and anonymous pages in
* get_scan_count.
*/
reclaim_stat->recent_rotated[file] += nr_rotated;
/* 将l_active链表中的页移动到lruvec->lists[lru]中,这里是将active的页移动到active的lru链表头部 */
move_active_pages_to_lru(lruvec, &l_active, &l_hold, lru);
/* 将l_inactive链表中的页移动到lruvec->lists[lru - LRU_ACITVE]中,这里是将active的页移动到inactive的lru头部 */
move_active_pages_to_lru(lruvec, &l_inactive, &l_hold, lru - LRU_ACTIVE);
__mod_zone_page_state(zone, NR_ISOLATED_ANON + file, -nr_taken);
spin_unlock_irq(&zone->lru_lock);
mem_cgroup_uncharge_list(&l_hold);
/* 剩下的页的处理,剩下的都是page->_count为0的页,作为冷页放回到伙伴系统的每CPU单页框高速缓存中 */
free_hot_cold_page_list(&l_hold, true);
}
/*
* shrink_inactive_list() is a helper for shrink_zone(). It returns the number
* of reclaimed pages
*/
static noinline_for_stack unsigned long
shrink_inactive_list(unsigned long nr_to_scan, struct lruvec *lruvec,
struct scan_control *sc, enum lru_list lru)
{
LIST_HEAD(page_list);
unsigned long nr_scanned;
unsigned long nr_reclaimed = 0;
unsigned long nr_taken;
unsigned long nr_dirty = 0;
unsigned long nr_congested = 0;
unsigned long nr_unqueued_dirty = 0;
unsigned long nr_writeback = 0;
unsigned long nr_immediate = 0;
isolate_mode_t isolate_mode = 0;
/* 是否是文件页 */
int file = is_file_lru(lru);
struct zone *zone = lruvec_zone(lruvec);
struct zone_reclaim_stat *reclaim_stat = &lruvec->reclaim_stat; /* 回收状态统计 */
while (unlikely(too_many_isolated(zone, file, sc))) {
congestion_wait(BLK_RW_ASYNC, HZ/10);
/* We are about to die and free our memory. Return now. */
if (fatal_signal_pending(current))
return SWAP_CLUSTER_MAX;
}
/* 当前的cpu的pagevec中的页放入lru链表中 */
lru_add_drain();
if (!sc->may_unmap)
isolate_mode |= ISOLATE_UNMAPPED; /* 不回收mmap */
if (!sc->may_writepage)
isolate_mode |= ISOLATE_CLEAN; /* 不回写 */
spin_lock_irq(&zone->lru_lock);
/*
* 从lruvec这个lru链表描述符的lru类型的lru链表中隔离最多nr_to_scan个页出来,隔离时是从lru链表尾部开始拿,然后放到page_list
* 返回隔离了多少个此非活动lru链表的页框
*/
nr_taken = isolate_lru_pages(nr_to_scan, lruvec, &page_list,
&nr_scanned, sc, isolate_mode, lru);
__mod_zone_page_state(zone, NR_LRU_BASE + lru, -nr_taken);
__mod_zone_page_state(zone, NR_ISOLATED_ANON + file, nr_taken);
if (global_reclaim(sc)) {
__mod_zone_page_state(zone, NR_PAGES_SCANNED, nr_scanned);
if (current_is_kswapd())
__count_zone_vm_events(PGSCAN_KSWAPD, zone, nr_scanned);
else
__count_zone_vm_events(PGSCAN_DIRECT, zone, nr_scanned);
}
spin_unlock_irq(&zone->lru_lock);
/* 隔离出页数为0,没有可回收的页面,直接返回 */
if (nr_taken == 0)
return 0;
/* 正式回收函数,page_list为待回收链表页 */
nr_reclaimed = shrink_page_list(&page_list, zone, sc, TTU_UNMAP,
&nr_dirty, &nr_unqueued_dirty, &nr_congested,
&nr_writeback, &nr_immediate,
false);
spin_lock_irq(&zone->lru_lock);
reclaim_stat->recent_scanned[file] += nr_taken;
if (global_reclaim(sc)) {
if (current_is_kswapd())
__count_zone_vm_events(PGSTEAL_KSWAPD, zone,
nr_reclaimed);
else
__count_zone_vm_events(PGSTEAL_DIRECT, zone,
nr_reclaimed);
}
putback_inactive_pages(lruvec, &page_list);
__mod_zone_page_state(zone, NR_ISOLATED_ANON + file, -nr_taken);
spin_unlock_irq(&zone->lru_lock);
mem_cgroup_uncharge_list(&page_list);
free_hot_cold_page_list(&page_list, true);
/*
* If reclaim is isolating dirty pages under writeback, it implies
* that the long-lived page allocation rate is exceeding the page
* laundering rate. Either the global limits are not being effective
* at throttling processes due to the page distribution throughout
* zones or there is heavy usage of a slow backing device. The
* only option is to throttle from reclaim context which is not ideal
* as there is no guarantee the dirtying process is throttled in the
* same way balance_dirty_pages() manages.
*
* Once a zone is flagged ZONE_WRITEBACK, kswapd will count the number
* of pages under pages flagged for immediate reclaim and stall if any
* are encountered in the nr_immediate check below.
*/
if (nr_writeback && nr_writeback == nr_taken)
set_bit(ZONE_WRITEBACK, &zone->flags);
/*
* memcg will stall in page writeback so only consider forcibly
* stalling for global reclaim
*/
if (global_reclaim(sc)) {
/*
* Tag a zone as congested if all the dirty pages scanned were
* backed by a congested BDI and wait_iff_congested will stall.
*/
if (nr_dirty && nr_dirty == nr_congested)
set_bit(ZONE_CONGESTED, &zone->flags);
/*
* If dirty pages are scanned that are not queued for IO, it
* implies that flushers are not keeping up. In this case, flag
* the zone ZONE_DIRTY and kswapd will start writing pages from
* reclaim context.
*/
if (nr_unqueued_dirty == nr_taken)
set_bit(ZONE_DIRTY, &zone->flags);
/*
* If kswapd scans pages marked marked for immediate
* reclaim and under writeback (nr_immediate), it implies
* that pages are cycling through the LRU faster than
* they are written so also forcibly stall.
*/
if (nr_immediate && current_may_throttle())
congestion_wait(BLK_RW_ASYNC, HZ/10);
}
/*
* Stall direct reclaim for IO completions if underlying BDIs or zone
* is congested. Allow kswapd to continue until it starts encountering
* unqueued dirty pages or cycling through the LRU too quickly.
*/
if (!sc->hibernation_mode && !current_is_kswapd() &&
current_may_throttle())
wait_iff_congested(zone, BLK_RW_ASYNC, HZ/10);
trace_mm_vmscan_lru_shrink_inactive(zone->zone_pgdat->node_id,
zone_idx(zone),
nr_scanned, nr_reclaimed,
sc->priority,
trace_shrink_flags(file));
return nr_reclaimed;
}
- 正在回写的页,无法回收,要么移动到非活动链表头部,要么等待回写完成;
- 该页被访问过的前提下:如果该页被多次访问(Access置位),或者PG_referenced置位,则移动到活动链表头部;如果该页最近被访问过,且是可执行程序,则移动到活动链表头部;其他被访问过的页保留在非活动链表头部;
- 如果是匿名页且不再swapcache中,则加入到swapcache中,设置page dirty;
- 如果是脏页,触发回写,设置回收标志,放到非活动链表头部;
- 其他正常释放page,释放buffer_head
/*
* shrink_page_list() returns the number of reclaimed pages
*/
static unsigned long shrink_page_list(struct list_head *page_list,
struct zone *zone,
struct scan_control *sc,
enum ttu_flags ttu_flags,
unsigned long *ret_nr_dirty,
unsigned long *ret_nr_unqueued_dirty,
unsigned long *ret_nr_congested,
unsigned long *ret_nr_writeback,
unsigned long *ret_nr_immediate,
bool force_reclaim) /* 正常进来为false */
{
LIST_HEAD(ret_pages);
LIST_HEAD(free_pages);
int pgactivate = 0;
unsigned long nr_unqueued_dirty = 0;
unsigned long nr_dirty = 0;
unsigned long nr_congested = 0;
unsigned long nr_reclaimed = 0;
unsigned long nr_writeback = 0;
unsigned long nr_immediate = 0;
cond_resched();
while (!list_empty(page_list)) {
struct address_space *mapping;
struct page *page;
int may_enter_fs;
enum page_references references = PAGEREF_RECLAIM_CLEAN;
bool dirty, writeback;
cond_resched();
/* 链表尾部取出一个page,从链表中删除 */
page = lru_to_page(page_list);
list_del(&page->lru);
/* 尝试对该页上锁,如果上锁失败,说明正在被其他地方使用,该页不回收,跳转到keep */
if (!trylock_page(page))
goto keep;
/* 走到这表示锁住了该页!!! */
/* 回收的只可能是当前zone且inactive的 */
VM_BUG_ON_PAGE(PageActive(page), page);
VM_BUG_ON_PAGE(page_zone(page) != zone, page);
/* 扫描页数++ */
sc->nr_scanned++;
if (unlikely(!page_evictable(page)))
goto cull_mlocked;
/* 如果本次sc不支持unmap,并且此页被映射到页表中,该页不回收 */
if (!sc->may_unmap && page_mapped(page))
goto keep_locked;
/* Double the slab pressure for mapped and swapcache pages */
if (page_mapped(page) || PageSwapCache(page))
sc->nr_scanned++;
/* 本次回收是否允许执行IO操作 */
/*
* 扫描到的非活动匿名页lru链表中的页如果还没有加入到swapcache中,需要有__GFP_FS标记才允许加入swapcache和回写。
* 活动匿名页lru链表中的页如果已经加入到了swapcache中,需要有__GFP_IO才允许进行回写。
* 活动文件页lru链表中的页需要有__GFP_FS才允许进行回写。
*/
may_enter_fs = (sc->gfp_mask & __GFP_FS) ||
(PageSwapCache(page) && (sc->gfp_mask & __GFP_IO));
/*
* The number of dirty pages determines if a zone is marked
* reclaim_congested which affects wait_iff_congested. kswapd
* will stall and start writing pages if the tail of the LRU
* is all dirty unqueued pages.
*/
/*
* 检查是否是脏页还有此页是否正在回写到磁盘
* 匿名页:dirty = false,writeback = false;
* 文件页:这里面主要判断页描述符的PG_dirty和PG_writeback两个标志(匿名页当加入swapcache后,就会被标记PG_dirty)
* 如果文件所属文件系统有特定is_dirty_writeback操作,则执行文件系统特定的is_dirty_writeback操作
*/
page_check_dirty_writeback(page, &dirty, &writeback);
/* 脏页或者正在回写的页,脏页数量++ */
if (dirty || writeback)
nr_dirty++;
/* 是脏页,但是还没有写回 */
if (dirty && !writeback)
nr_unqueued_dirty++;
/*
* Treat this page as congested if the underlying BDI is or if
* pages are cycling through the LRU so quickly that the
* pages marked for immediate reclaim are making it to the
* end of the LRU a second time.
*/
/* 获取此页对应的address_space,如果此页是匿名页,则为NULL */
mapping = page_mapping(page);
if (((dirty || writeback) && mapping &&
bdi_write_congested(inode_to_bdi(mapping->host))) ||
(writeback && PageReclaim(page)))
nr_congested++;
/*
* If a page at the tail of the LRU is under writeback, there
* are three cases to consider.
*
* 1) If reclaim is encountering an excessive number of pages
* under writeback and this page is both under writeback and
* PageReclaim then it indicates that pages are being queued
* for IO but are being recycled through the LRU before the
* IO can complete. Waiting on the page itself risks an
* indefinite stall if it is impossible to writeback the
* page due to IO error or disconnected storage so instead
* note that the LRU is being scanned too quickly and the
* caller can stall after page list has been processed.
*
* 2) Global reclaim encounters a page, memcg encounters a
* page that is not marked for immediate reclaim or
* the caller does not have __GFP_IO. In this case mark
* the page for immediate reclaim and continue scanning.
*
* __GFP_IO is checked because a loop driver thread might
* enter reclaim, and deadlock if it waits on a page for
* which it is needed to do the write (loop masks off
* __GFP_IO|__GFP_FS for this reason); but more thought
* would probably show more reasons.
*
* Don't require __GFP_FS, since we're not going into the
* FS, just waiting on its writeback completion. Worryingly,
* ext4 gfs2 and xfs allocate pages with
* grab_cache_page_write_begin(,,AOP_FLAG_NOFS), so testing
* may_enter_fs here is liable to OOM on them.
*
* 3) memcg encounters a page that is not already marked
* PageReclaim. memcg does not have any dirty pages
* throttling so we could easily OOM just because too many
* pages are in writeback and there is nothing else to
* reclaim. Wait for the writeback to complete.
*/
/* 这个页正在回写,正在回写的页无法回收,必须等待回写完成。什么时候设置Writeback????? */
if (PageWriteback(page)) {
/* Case 1 above */
/*
* 当前处于kswapd内核进程,并且此页正在进行回收(可能在等待IO),然后zone也表明了很多页正在进行回写
* 说明此页是已经在回写到磁盘,并且也正在进行回收的,本次回收不需要对此页进行回收
* 正在回写,且已经设置回收标志
*/
if (current_is_kswapd() &&
PageReclaim(page) &&
test_bit(ZONE_WRITEBACK, &zone->flags)) {
/* 增加nr_immediate计数,此计数说明此页准备就可以回收了 */
nr_immediate++;
goto keep_locked;
/* Case 2 above */
/* 此页正在进行正常的回写(不是因为要回收此页才进行的回写)
* 两种情况会进入这里:
* 1.本次是针对整个zone进行内存回收的
* 2.本次回收不允许进行IO操作
* 那么就标记这个页要回收,本次回收不对此页进行回收,会放到非活动链表头部(当此页回写完成后,会判断这个PG_reclaim标记,如果置位了,把此页放到非活动lru链表末尾)
* 快速回收会进入这种情况
*/
} else if (global_reclaim(sc) ||
!PageReclaim(page) || !(sc->gfp_mask & __GFP_IO)) {
/*
* This is slightly racy - end_page_writeback()
* might have just cleared PageReclaim, then
* setting PageReclaim here end up interpreted
* as PageReadahead - but that does not matter
* enough to care. What we do want is for this
* page to have PageReclaim set next time memcg
* reclaim reaches the tests above, so it will
* then wait_on_page_writeback() to avoid OOM;
* and it's also appropriate in global reclaim.
*/
/* 正在回写的页设置回收标志 */
SetPageReclaim(page);
/* 回写计数++ */
nr_writeback++;
goto keep_locked;
/* Case 3 above */
} else {
/* 睡眠等待回写完成 */
wait_on_page_writeback(page);
}
}
/*
* 如果回收时非强制进行回收,那要先判断此页需不需要移动到活动lru链表 ; 如果此次是强制回收,则不管是否被访问过;
* 1:如果是匿名页,只要最近此页被进程访问过,则将此页移动到活动lru链表头部,否则回收
* 2:如果是映射可执行文件的文件页,只要最近被进程访问过,就放到活动lru链表,否则回收
* 3:如果是其他的文件页,如果最近被多个进程访问过,移动到活动lru链表,如果只被1个进程访问过,但是PG_referenced置位了,也放入活动lru链表,其他情况回收
*/
if (!force_reclaim)
references = page_check_references(page, sc);
switch (references) {
/* 将页放到活动lru链表头部中 */
case PAGEREF_ACTIVATE:
goto activate_locked;
/* 页继续保存在非活动lru链表头部中 */
case PAGEREF_KEEP:
goto keep_locked;
case PAGEREF_RECLAIM:
case PAGEREF_RECLAIM_CLEAN:
; /* try to reclaim the page below */
}
/*
* Anonymous process memory has backing store?
* Try to allocate it some swap space here.
*/
/*
* page为匿名页,但是又不处于swapcache中,这里会尝试将其加入到swapcache中,这个swapcache作为swap缓冲区
* 当页被换出或换入时,会先加入到swapcache,当完全换出或者完全换入时,才会从swapcache中移除
* 有了此swapcache,当一个页进行换出时,一个进程访问此页,可以直接从swapcache中获取此页的映射,然后swapcache终止此页的换出操作,这样就不用等页要完全换出后,再重新换回来
*/
if (PageAnon(page) && !PageSwapCache(page)) {
/* 如果本次回收禁止io操作,则跳转到keep_locked,让此匿名页继续在非活动lru链表中 */
if (!(sc->gfp_mask & __GFP_IO))
goto keep_locked;
if (!add_to_swap(page, page_list))/* 加入成功后,会设置该页Dirty */
goto activate_locked;
may_enter_fs = 1;
/* Adding to swap updated mapping */
mapping = page_mapping(page);
}
/*
* The page is mapped into the page tables of one or more
* processes. Try to unmap it here.
*/
/*
* 这里是要对所有映射了此page的页表进行设置
* 匿名页会把对应的页表项设置为之前获取的swp_entry_t
*/
if (page_mapped(page) && mapping) {
/*
* 1:如果是匿名页,那么page->private中是一个带有swap页槽偏移量的swp_entry_t,此后这个swp_entry_t可以转为页表项
* 执行完此后,匿名页在swapcache中,而对于引用了此页的进程而言,此页已经在swap中
* 但是当此匿名页还没有完全写到swap中时,如果此时有进程访问此页,会将此页映射到此进程页表中,并取消此页放入swap中的操作,放回匿名页的lru链表(在缺页中断中完成)
* 2:而对于文件页,只需要清空映射了此页的进程页表的页表项,不需要设置新的页表项
* 每一个进程unmap此页,此页的page->_count--
* 如果反向映射过程中page->_count == 0,则释放此页
*/
switch (try_to_unmap(page, ttu_flags)) {
case SWAP_FAIL:
goto activate_locked;
case SWAP_AGAIN:
goto keep_locked;
case SWAP_MLOCK:
goto cull_mlocked;
case SWAP_SUCCESS:
; /* try to free the page below */
}
}
/*
* 如果页为脏页,有两种页
* 一种是当匿名页加入到swapcache中时,就被标记为了脏页,需要写入
* 一种是脏的文件页
*/
if (PageDirty(page)) {
/*
* Only kswapd can writeback filesystem pages to
* avoid risk of stack overflow but only writeback
* if many dirty pages have been encountered.
*/
/*
* 只有kswapd内核线程能够进行文件页的回写操作(kswapd中不会造成栈溢出?),但是只有当zone中有很多脏页时,kswapd也才能进行脏文件页的回写
* 此标记说明zone的脏页很多,在回收时隔离出来的页都是没有进行回写的脏页时设置
* 也就是此zone脏页不够多,kswapd不用进行回写操作
* 当短时间内多次对此zone执行内存回收后,这个ZONE_DIRTY就会被设置,这样做的理由是: 优先回收匿名页和干净的文件页,说不定回收完这些zone中空闲内存就足够了,不需要再进行内存回收了
* 而对于匿名页,无论是否是kswapd都可以进行回写
*/
if (page_is_file_cache(page) &&
(!current_is_kswapd() ||
!test_bit(ZONE_DIRTY, &zone->flags))) {
/*
* Immediately reclaim when written back.
* Similar in principal to deactivate_page()
* except we already have the page isolated
* and know it's dirty
*/
/*
* 设置此页需要回收,这样当此页回写完成后,就会被放入到非活动lru链表尾部
* 不过可惜这里只能等kswapd内核线程对此页进行回写,要么就等系统到期后自动将此页进行回写,非kswapd线程都不能对文件页进行回写
*/
inc_zone_page_state(page, NR_VMSCAN_IMMEDIATE);
SetPageReclaim(page);
/* 让页移动到非活动lru链表头部,如上所说,当回写完成后,页会被移动到非活动lru链表尾部,而内存回收是从非活动lru链表尾部拿页出来回收的 */
goto keep_locked;
}
if (references == PAGEREF_RECLAIM_CLEAN)
goto keep_locked;
if (!may_enter_fs)
goto keep_locked;
if (!sc->may_writepage)
goto keep_locked;
/* 将页进行回写到磁盘,这里只是将页加入到块层,调用结束并不是代表此页已经回写完成
* 主要调用page->mapping->a_ops->writepage进行回写,对于匿名页,也是swapcache的address_space->a_ops->writepage
* 页被加入到块层回写队列后,会置位页的PG_writeback,回写完成后清除PG_writeback位,所以在同步模式回写下,结束后PG_writeback位是0的,而异步模式下,PG_writeback很可能为1
* 此函数中会清除页的PG_dirty标志
* 会标记页的PG_reclaim
* 成功将页加入到块层后,页的PG_lock位会清空
* 也就是在一个页成功进入到回收导致的回写过程中,它的PG_writeback和PG_reclaim标志会置位,而它的PG_dirty和PG_lock标志会清除
* 而此页成功回写后,它的PG_writeback和PG_reclaim位都会被清除
*/
/* Page is dirty, try to write it out here */
switch (pageout(page, mapping, sc)) {
case PAGE_KEEP:
/* 页会被移动到非活动lru链表头部 */
goto keep_locked;
case PAGE_ACTIVATE:
/* 页会被移动到活动lru链表 */
goto activate_locked;
case PAGE_SUCCESS:
if (PageWriteback(page))
goto keep;
if (PageDirty(page))
goto keep;
/*
* A synchronous write - probably a ramdisk. Go
* ahead and try to reclaim the page.
*/
if (!trylock_page(page))
goto keep;
if (PageDirty(page) || PageWriteback(page))
goto keep_locked;
mapping = page_mapping(page);
/* 这个页不是脏页,不需要回写,这种情况只发生在文件页,匿名页当加入到swapcache中时就被设置为脏页 */
case PAGE_CLEAN:
; /* try to free the page below */
}
}
/* 这里的情况只有页已经完成回写后才会到达这里,比如同步回写时(pageout在页回写完成后才返回),异步回写时,在运行到此之前已经把页回写到磁盘
* 没有完成回写的页不会到这里,在pageout()后就跳到keep了
* 或者是干净的页
*/
/*
* If the page has buffers, try to free the buffer mappings
* associated with this page. If we succeed we try to free
* the page as well.
*
* We do this even if the page is PageDirty().
* try_to_release_page() does not perform I/O, but it is
* possible for a page to have PageDirty set, but it is actually
* clean (all its buffers are clean). This happens if the
* buffers were written out directly, with submit_bh(). ext3
* will do this, as well as the blockdev mapping.
* try_to_release_page() will discover that cleanness and will
* drop the buffers and mark the page clean - it can be freed.
*
* Rarely, pages can have buffers and no ->mapping. These are
* the pages which were not successfully invalidated in
* truncate_complete_page(). We try to drop those buffers here
* and if that worked, and the page is no longer mapped into
* process address space (page_count == 1) it can be freed.
* Otherwise, leave the page on the LRU so it is swappable.
*/
/* 通过页描述符的PAGE_FLAGS_PRIVATE标记判断是否有buffer_head,这个只有文件页有
* 这里不会通过page->private判断,原因是,当匿名页加入到swapcache时,也会使用page->private,而不会标记PAGE_FLAGS_PRIVATE
* 只有文件页会使用这个PAGE_FLAGS_PRIVATE,这个标记说明此文件页的page->private指向struct buffer_head链表头
*/
if (page_has_private(page)) {
/* 因为页已经回写完成或者是干净不需要回写的页,释放page->private指向struct buffer_head链表,释放后page->private = NULL
* 释放时必须要保证此页的PG_writeback位为0,也就是此页已经回写到磁盘中了
*/
if (!try_to_release_page(page, sc->gfp_mask)) /* 释放page和buffer_head */
/* 释放失败,把此页移动到活动lru链表 */
goto activate_locked;
if (!mapping && page_count(page) == 1) {
unlock_page(page);
if (put_page_testzero(page)) /* 引用计数为0 ,可以释放了 */
goto free_it;
else {
/*
* rare race with speculative reference.
* the speculative reference will free
* this page shortly, so we may
* increment nr_reclaimed here (and
* leave it off the LRU).
*/
nr_reclaimed++;
continue;
}
}
}
if (!mapping || !__remove_mapping(mapping, page, true))
goto keep_locked;
/*
* At this point, we have no other references and there is
* no way to pick any more up (removed from LRU, removed
* from pagecache). Can use non-atomic bitops now (and
* we obviously don't have to worry about waking up a process
* waiting on the page lock, because there are no references.
*/
__clear_page_locked(page);
free_it:
nr_reclaimed++;
/*
* Is there need to periodically free_page_list? It would
* appear not as the counts should be low
*/
list_add(&page->lru, &free_pages);
continue;
cull_mlocked:
if (PageSwapCache(page))
try_to_free_swap(page);
unlock_page(page);
putback_lru_page(page);
continue;
activate_locked:
/* Not a candidate for swapping, so reclaim swap space. */
if (PageSwapCache(page) && vm_swap_full())
try_to_free_swap(page);
VM_BUG_ON_PAGE(PageActive(page), page);
SetPageActive(page);
pgactivate++;
keep_locked:
unlock_page(page);
keep:
list_add(&page->lru, &ret_pages);
VM_BUG_ON_PAGE(PageLRU(page) || PageUnevictable(page), page);
}
mem_cgroup_uncharge_list(&free_pages);
free_hot_cold_page_list(&free_pages, true); /* 释放到伙伴系统,这里有冷热页区分 */
list_splice(&ret_pages, page_list);
count_vm_events(PGACTIVATE, pgactivate);
*ret_nr_dirty += nr_dirty;
*ret_nr_congested += nr_congested;
*ret_nr_unqueued_dirty += nr_unqueued_dirty;
*ret_nr_writeback += nr_writeback;
*ret_nr_immediate += nr_immediate;
return nr_reclaimed;
}
2.直接回收
static inline struct page *
__alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,
struct alloc_context *ac)
{
const gfp_t wait = gfp_mask & __GFP_WAIT;
struct page *page = NULL;
int alloc_flags;
unsigned long pages_reclaimed = 0;
unsigned long did_some_progress;
enum migrate_mode migration_mode = MIGRATE_ASYNC;
bool deferred_compaction = false;
int contended_compaction = COMPACT_CONTENDED_NONE;
/*
* In the slowpath, we sanity check order to avoid ever trying to
* reclaim >= MAX_ORDER areas which will never succeed. Callers may
* be using allocators in order of preference for an area that is
* too large.
*/
if (order >= MAX_ORDER) {
WARN_ON_ONCE(!(gfp_mask & __GFP_NOWARN));
return NULL;
}
/*
* GFP_THISNODE (meaning __GFP_THISNODE, __GFP_NORETRY and
* __GFP_NOWARN set) should not cause reclaim since the subsystem
* (f.e. slab) using GFP_THISNODE may choose to trigger reclaim
* using a larger set of nodes after it has established that the
* allowed per node queues are empty and that nodes are
* over allocated.
*/
if (IS_ENABLED(CONFIG_NUMA) &&
(gfp_mask & GFP_THISNODE) == GFP_THISNODE)
goto nopage;
/*
* 1:先用LOW水位尝试分配内存
* 2:如果设置了ALLOC_NO_WATERMARKS,则不管水位,强行分配
* 3:尝试直接回收,然后在分配
*/
retry:
if (!(gfp_mask & __GFP_NO_KSWAPD)) /* 唤醒所有node节点的kswapd进行回收 */
wake_all_kswapds(order, ac);
/*
* OK, we're below the kswapd watermark and have kicked background
* reclaim. Now things get more complex, so set up alloc_flags according
* to how we want to proceed.
*/
alloc_flags = gfp_to_alloc_flags(gfp_mask); /* ALLOC_WMARK_MIN */
/*
* Find the true preferred zone if the allocation is unconstrained by
* cpusets.
*/
if (!(alloc_flags & ALLOC_CPUSET) && !ac->nodemask) {
struct zoneref *preferred_zoneref;
preferred_zoneref = first_zones_zonelist(ac->zonelist,
ac->high_zoneidx, NULL, &ac->preferred_zone);
ac->classzone_idx = zonelist_zone_idx(preferred_zoneref);
}
/* 用MIN水位在尝试分配一次 */
/* This is the last chance, in general, before the goto nopage. */
page = get_page_from_freelist(gfp_mask, order,
alloc_flags & ~ALLOC_NO_WATERMARKS, ac);
if (page)
goto got_pg;
/* 强制分配,不需要管水位情况 */
/* Allocate without watermarks if the context allows */
if (alloc_flags & ALLOC_NO_WATERMARKS) {
/*
* Ignore mempolicies if ALLOC_NO_WATERMARKS on the grounds
* the allocation is high priority and these type of
* allocations are system rather than user orientated
*/
ac->zonelist = node_zonelist(numa_node_id(), gfp_mask);
page = __alloc_pages_high_priority(gfp_mask, order, ac);
if (page) {
goto got_pg;
}
}
/* Atomic allocations - we can't balance anything */
if (!wait) {
/*
* All existing users of the deprecated __GFP_NOFAIL are
* blockable, so warn of any new users that actually allow this
* type of allocation to fail.
*/
WARN_ON_ONCE(gfp_mask & __GFP_NOFAIL);
goto nopage;
}
/* Avoid recursion of direct reclaim */
if (current->flags & PF_MEMALLOC)
goto nopage;
/* Avoid allocations with no watermarks from looping endlessly */
if (test_thread_flag(TIF_MEMDIE) && !(gfp_mask & __GFP_NOFAIL))
goto nopage;
/*
* Try direct compaction. The first pass is asynchronous. Subsequent
* attempts after direct reclaim are synchronous
*/
page = __alloc_pages_direct_compact(gfp_mask, order, alloc_flags, ac,
migration_mode,
&contended_compaction,
&deferred_compaction);
if (page)
goto got_pg;
/* Checks for THP-specific high-order allocations */
if ((gfp_mask & GFP_TRANSHUGE) == GFP_TRANSHUGE) {
/*
* If compaction is deferred for high-order allocations, it is
* because sync compaction recently failed. If this is the case
* and the caller requested a THP allocation, we do not want
* to heavily disrupt the system, so we fail the allocation
* instead of entering direct reclaim.
*/
if (deferred_compaction)
goto nopage;
/*
* In all zones where compaction was attempted (and not
* deferred or skipped), lock contention has been detected.
* For THP allocation we do not want to disrupt the others
* so we fallback to base pages instead.
*/
if (contended_compaction == COMPACT_CONTENDED_LOCK)
goto nopage;
/*
* If compaction was aborted due to need_resched(), we do not
* want to further increase allocation latency, unless it is
* khugepaged trying to collapse.
*/
if (contended_compaction == COMPACT_CONTENDED_SCHED
&& !(current->flags & PF_KTHREAD))
goto nopage;
}
/*
* It can become very expensive to allocate transparent hugepages at
* fault, so use asynchronous memory compaction for THP unless it is
* khugepaged trying to collapse.
*/
if ((gfp_mask & GFP_TRANSHUGE) != GFP_TRANSHUGE ||
(current->flags & PF_KTHREAD))
migration_mode = MIGRATE_SYNC_LIGHT;
/* 尝试直接回收,然后在分配 */
/* Try direct reclaim and then allocating */
page = __alloc_pages_direct_reclaim(gfp_mask, order, alloc_flags, ac,
&did_some_progress);
if (page)
goto got_pg;
/* Check if we should retry the allocation */
pages_reclaimed += did_some_progress;
if (should_alloc_retry(gfp_mask, order, did_some_progress,
pages_reclaimed)) {
/*
* If we fail to make progress by freeing individual
* pages, but the allocation wants us to keep going,
* start OOM killing tasks.
*/
if (!did_some_progress) {
page = __alloc_pages_may_oom(gfp_mask, order, ac,
&did_some_progress);
if (page)
goto got_pg;
if (!did_some_progress)
goto nopage;
}
/* Wait for some write requests to complete then retry */
wait_iff_congested(ac->preferred_zone, BLK_RW_ASYNC, HZ/50);
goto retry;
} else {
/*
* High-order allocations do not necessarily loop after
* direct reclaim and reclaim/compaction depends on compaction
* being called after reclaim so call directly if necessary
*/
page = __alloc_pages_direct_compact(gfp_mask, order,
alloc_flags, ac, migration_mode,
&contended_compaction,
&deferred_compaction);
if (page)
goto got_pg;
}
nopage:
warn_alloc_failed(gfp_mask, order, NULL);
got_pg:
return page;
}
static int
__perform_reclaim(gfp_t gfp_mask, unsigned int order,
const struct alloc_context *ac)
{
struct reclaim_state reclaim_state;
int progress;
cond_resched();
/* We now go into synchronous reclaim */
cpuset_memory_pressure_bump();
current->flags |= PF_MEMALLOC;
lockdep_set_current_reclaim_state(gfp_mask);
reclaim_state.reclaimed_slab = 0;
current->reclaim_state = &reclaim_state;
/* 回收函数 */
progress = try_to_free_pages(ac->zonelist, order, gfp_mask,
ac->nodemask);
current->reclaim_state = NULL;
lockdep_clear_current_reclaim_state();
current->flags &= ~PF_MEMALLOC;
cond_resched();
return progress;
}
unsigned long try_to_free_pages(struct zonelist *zonelist, int order,
gfp_t gfp_mask, nodemask_t *nodemask)
{
unsigned long nr_reclaimed;
struct scan_control sc = {
.nr_to_reclaim = SWAP_CLUSTER_MAX, /* 32页 */
.gfp_mask = (gfp_mask = memalloc_noio_flags(gfp_mask)),
.order = order,
.nodemask = nodemask,
.priority = DEF_PRIORITY, /* 优先级12 */
.may_writepage = !laptop_mode, /* /proc/sys/vm/laptop_mode */
.may_unmap = 1, /* 允许unmap */
.may_swap = 1, /* 允许页交换 */
};
/*
* Do not enter reclaim if fatal signal was delivered while throttled.
* 1 is returned so that the page allocator does not OOM kill at this
* point.
*/
if (throttle_direct_reclaim(gfp_mask, zonelist, nodemask))
return 1;
trace_mm_vmscan_direct_reclaim_begin(order,
sc.may_writepage,
gfp_mask);
/* 回收 */
nr_reclaimed = do_try_to_free_pages(zonelist, &sc);
trace_mm_vmscan_direct_reclaim_end(nr_reclaimed);
return nr_reclaimed;
}
static unsigned long do_try_to_free_pages(struct zonelist *zonelist,
struct scan_control *sc)
{
int initial_priority = sc->priority;
unsigned long total_scanned = 0;
unsigned long writeback_threshold;
bool zones_reclaimable;
retry:
delayacct_freepages_start();
if (global_reclaim(sc))
count_vm_event(ALLOCSTALL);
do {
vmpressure_prio(sc->gfp_mask, sc->target_mem_cgroup,
sc->priority);
sc->nr_scanned = 0;
/* 回收,所有zone都回收一次,然后再判断 */
zones_reclaimable = shrink_zones(zonelist, sc);
total_scanned += sc->nr_scanned;
/* 回收了足够的页数,最为跳出循环 */
if (sc->nr_reclaimed >= sc->nr_to_reclaim)
break;
if (sc->compaction_ready)
break;
/*
* If we're getting trouble reclaiming, start doing
* writepage even in laptop mode.
*/
if (sc->priority < DEF_PRIORITY - 2) /* 优先级低的时候,配置写回,这样能回收更多的页 */
sc->may_writepage = 1;
/*
* Try to write back as many pages as we just scanned. This
* tends to cause slow streaming writers to write data to the
* disk smoothly, at the dirtying rate, which is nice. But
* that's undesirable in laptop mode, where we *want* lumpy
* writeout. So in laptop mode, write out the whole world.
*/
writeback_threshold = sc->nr_to_reclaim + sc->nr_to_reclaim / 2;
if (total_scanned > writeback_threshold) {
wakeup_flusher_threads(laptop_mode ? 0 : total_scanned,
WB_REASON_TRY_TO_FREE_PAGES);
sc->may_writepage = 1;
}
} while (--sc->priority >= 0); /* 降低优先级,继续扫描更多的页 */
delayacct_freepages_end();
if (sc->nr_reclaimed)
return sc->nr_reclaimed; /* 返回回收的页数 */
/* Aborted reclaim to try compaction? don't OOM, then */
if (sc->compaction_ready)
return 1;
/* Untapped cgroup reserves? Don't OOM, retry. */
if (!sc->may_thrash) {
sc->priority = initial_priority;
sc->may_thrash = 1;
goto retry;
}
/* Any of the zones still reclaimable? Don't OOM. */
if (zones_reclaimable)
return 1;
return 0;
}
3.kwapd回收
两种触发模式:
- 主动回收
- 被动唤醒回收
static void wake_all_kswapds(unsigned int order, const struct alloc_context *ac)
{
struct zoneref *z;
struct zone *zone;
for_each_zone_zonelist_nodemask(zone, z, ac->zonelist,
ac->high_zoneidx, ac->nodemask)
wakeup_kswapd(zone, order, zone_idx(ac->preferred_zone));
}
void wakeup_kswapd(struct zone *zone, int order, enum zone_type classzone_idx)
{
pg_data_t *pgdat;
if (!populated_zone(zone))
return;
if (!cpuset_zone_allowed(zone, GFP_KERNEL | __GFP_HARDWALL))
return;
pgdat = zone->zone_pgdat;
if (pgdat->kswapd_max_order < order) {
/* 重新设置kswapd_max_order classzone_idx */
pgdat->kswapd_max_order = order;
pgdat->classzone_idx = min(pgdat->classzone_idx, classzone_idx);
}
if (!waitqueue_active(&pgdat->kswapd_wait))
return;
if (zone_balanced(zone, order, 0, 0))
return;
trace_mm_vmscan_wakeup_kswapd(pgdat->node_id, zone_idx(zone), order);
wake_up_interruptible(&pgdat->kswapd_wait);
}
/*
* The background pageout daemon, started as a kernel thread
* from the init process.
*
* This basically trickles out pages so that we have _some_
* free memory available even if there is no other activity
* that frees anything up. This is needed for things like routing
* etc, where we otherwise might have all activity going on in
* asynchronous contexts that cannot page things out.
*
* If there are applications that are active memory-allocators
* (most normal use), this basically shouldn't matter.
*/
static int kswapd(void *p)
{
unsigned long order, new_order;
unsigned balanced_order;
int classzone_idx, new_classzone_idx;
int balanced_classzone_idx;
pg_data_t *pgdat = (pg_data_t*)p;
struct task_struct *tsk = current;
struct reclaim_state reclaim_state = {
.reclaimed_slab = 0,
};
const struct cpumask *cpumask = cpumask_of_node(pgdat->node_id);
lockdep_set_current_reclaim_state(GFP_KERNEL);
if (!cpumask_empty(cpumask))
set_cpus_allowed_ptr(tsk, cpumask);
current->reclaim_state = &reclaim_state;
/*
* Tell the memory management that we're a "memory allocator",
* and that if we need more memory we should get access to it
* regardless (see "__alloc_pages()"). "kswapd" should
* never get caught in the normal page freeing logic.
*
* (Kswapd normally doesn't need memory anyway, but sometimes
* you need a small amount of memory in order to be able to
* page out something else, and this flag essentially protects
* us from recursively trying to free more memory as we're
* trying to free the first piece of memory in the first place).
*/
/*
* 设置三个标志分配表示:内存回收过程中可能有内存分配,如果发生内存分配的情况将忽略掉所有限制条件尽最大可能尝试内存分配;
* 当前进程也可能将交换页写入交换分区;当前进程是内存回收进程
*/
tsk->flags |= PF_MEMALLOC | PF_SWAPWRITE | PF_KSWAPD;
set_freezable();
/* 初始回收order */
order = new_order = 0;
balanced_order = 0;
classzone_idx = new_classzone_idx = pgdat->nr_zones - 1;
balanced_classzone_idx = classzone_idx;
for ( ; ; ) {
bool ret;
/*
* wakeup_kswapd 唤醒时设置kswapd_max_order 以及 classzone_idx
* 如果是kswapd自己执行,则kswapd_max_order=0 classzone_idx= pgdat->nr_zones - 1
*/
/*
* If the last balance_pgdat was unsuccessful it's unlikely a
* new request of a similar or harder type will succeed soon
* so consider going to sleep on the basis we reclaimed at
*/
if (balanced_classzone_idx >= new_classzone_idx &&
balanced_order == new_order) {
new_order = pgdat->kswapd_max_order;
new_classzone_idx = pgdat->classzone_idx;
pgdat->kswapd_max_order = 0;
pgdat->classzone_idx = pgdat->nr_zones - 1;
}
if (order < new_order || classzone_idx > new_classzone_idx) {
/*
* Don't sleep if someone wants a larger 'order'
* allocation or has tigher zone constraints
*/
order = new_order;
classzone_idx = new_classzone_idx;
} else {
kswapd_try_to_sleep(pgdat, balanced_order,
balanced_classzone_idx);
/* 如果被唤醒,这个值被唤醒的地方设置 */
order = pgdat->kswapd_max_order;
classzone_idx = pgdat->classzone_idx;
new_order = order;
new_classzone_idx = classzone_idx;
/* 重新设置。默认为kswapd参数 */
pgdat->kswapd_max_order = 0;
pgdat->classzone_idx = pgdat->nr_zones - 1;
}
ret = try_to_freeze();
if (kthread_should_stop())
break;
/*
* We can speed up thawing tasks if we don't call balance_pgdat
* after returning from the refrigerator
*/
if (!ret) {
trace_mm_vmscan_kswapd_wake(pgdat->node_id, order); /* 跟踪点 */
balanced_classzone_idx = classzone_idx;
balanced_order = balance_pgdat(pgdat, order,
&balanced_classzone_idx);
}
}
tsk->flags &= ~(PF_MEMALLOC | PF_SWAPWRITE | PF_KSWAPD);
current->reclaim_state = NULL;
lockdep_clear_current_reclaim_state();
return 0;
}
static void kswapd_try_to_sleep(pg_data_t *pgdat, int order, int classzone_idx)
{
long remaining = 0;
DEFINE_WAIT(wait);
if (freezing(current) || kthread_should_stop())
return;
prepare_to_wait(&pgdat->kswapd_wait, &wait, TASK_INTERRUPTIBLE); //设置等待队列
/* Try to sleep for a short interval */
if (prepare_kswapd_sleep(pgdat, order, remaining, classzone_idx)) { /* pgdat_balanced 则睡眠100ms */
remaining = schedule_timeout(HZ/10);
finish_wait(&pgdat->kswapd_wait, &wait);
prepare_to_wait(&pgdat->kswapd_wait, &wait, TASK_INTERRUPTIBLE);
}
/*
* After a short sleep, check if it was a premature sleep. If not, then
* go fully to sleep until explicitly woken up.
*/
/* remaining:上面如果睡眠了100ms才被超时唤醒,则值为0;如果没有100ms被提前唤醒,则非0;
* 如果remaining != 0被提前唤醒了, prepare_kswapd_sleep 返回false ,退出函数执行回收流程;
* 如果remaining == 0 && pgdat_balanced,则调度出去
*/
if (prepare_kswapd_sleep(pgdat, order, remaining, classzone_idx)) {
trace_mm_vmscan_kswapd_sleep(pgdat->node_id);
/*
* vmstat counters are not perfectly accurate and the estimated
* value for counters such as NR_FREE_PAGES can deviate from the
* true value by nr_online_cpus * threshold. To avoid the zone
* watermarks being breached while under pressure, we reduce the
* per-cpu vmstat threshold while kswapd is awake and restore
* them before going back to sleep.
*/
set_pgdat_percpu_threshold(pgdat, calculate_normal_threshold);
/*
* Compaction records what page blocks it recently failed to
* isolate pages from and skips them in the future scanning.
* When kswapd is going to sleep, it is reasonable to assume
* that pages and compaction may succeed so reset the cache.
*/
reset_isolation_suitable(pgdat);
if (!kthread_should_stop())
schedule();
set_pgdat_percpu_threshold(pgdat, calculate_pressure_threshold);
} else {
if (remaining)
count_vm_event(KSWAPD_LOW_WMARK_HIT_QUICKLY);
else
count_vm_event(KSWAPD_HIGH_WMARK_HIT_QUICKLY);
}
finish_wait(&pgdat->kswapd_wait, &wait);
}
static bool prepare_kswapd_sleep(pg_data_t *pgdat, int order, long remaining,
int classzone_idx)
{
/* If a direct reclaimer woke kswapd within HZ/10, it's premature */
/* 剩余时间为0,立即退出,不需要回收 */
if (remaining)
return false;
/*
* The throttled processes are normally woken up in balance_pgdat() as
* soon as pfmemalloc_watermark_ok() is true. But there is a potential
* race between when kswapd checks the watermarks and a process gets
* throttled. There is also a potential race if processes get
* throttled, kswapd wakes, a large process exits thereby balancing the
* zones, which causes kswapd to exit balance_pgdat() before reaching
* the wake up checks. If kswapd is going to sleep, no process should
* be sleeping on pfmemalloc_wait, so wake them now if necessary. If
* the wake up is premature, processes will wake kswapd and get
* throttled again. The difference from wake ups in balance_pgdat() is
* that here we are under prepare_to_wait().
*/
if (waitqueue_active(&pgdat->pfmemalloc_wait))
wake_up_all(&pgdat->pfmemalloc_wait);
/*
* order=0, 所有zone都要平衡,即神域页数大于high_order;,否则不平衡,需要回收
* order>0, 所有平衡zone管理的页数要大于总的管理页数的25%,否则不平衡,需要回收
*/
return pgdat_balanced(pgdat, order, classzone_idx);
}
4.内存回收的参数
扫描到的非活动匿名页lru链表中的页如果还没有加入到swapcache中,需要有__GFP_IO标记才允许加入swapcache和回写。
扫描到的非活动匿名页lru链表中的页如果已经加入到了swapcache中,需要有__GFP_FS才允许进行回写。
扫描到的非活动文件页lru链表中的页需要有__GFP_FS才允许进行回写。
/proc/sys/vm/zone_reclaim_mode
这个参数只会影响快速内存回收,其值有三种,
0x1:开启zone的内存回收
0x2:开启zone的内存回收,并且允许回写
0x4:开启zone的内存回收,允许进行unmap操作
当此参数为0时,会导致快速内存回收只会对最优zone附近的几个需要进行内存回收的zone进行内存回收(说快速内存会解释),而只要不为0,就会对zonelist中所有应该进行内存回收的zone进行内存回收。
当此参数为0x1(001)时,就如上面一行所说,允许快速内存回收对zonelist中所有应该进行内存回收的zone进行内存回收。
当此参数为0x2(010)时,在0x1的基础上,允许快速内存回收进行匿名页lru链表中的页的回写操作。
当此参数0x4(100)时,在0x1的基础上,允许快速内存回收进行页的unmap操作。
/proc/sys/vm/laptop_mode
此参数只会影响直接内存回收,只有两个值:
0:允许直接内存回收对匿名页lru链表中的页进行回写操作,并且允许直接内存回收唤醒flush内核线程
非0:直接内存回收不会对匿名页lru链表中的页进行回写操作
/proc/sys/vm/swapiness
此参数影响进行内存回收时,扫描匿名页lru链表和扫描文件页lru链表的比例,范围是0~200,系统默认是30:
接近0:进行内存回收时,更多地去扫描文件页lru链表,如果为0,那么就不会去扫描匿名页lru链表。
接近200:进行内存回收时,更多地去扫描匿名页lru链表。
问题
1:水位值设置
/*
* Initialise min_free_kbytes.
*
* For small machines we want it small (128k min). For large machines
* we want it large (64MB max). But it is not linear, because network
* bandwidth does not increase linearly with machine size. We use
*
* min_free_kbytes = 4 * sqrt(lowmem_kbytes), for better accuracy:
* min_free_kbytes = sqrt(lowmem_kbytes * 16)
*
* which yields
*
* 16MB: 512k
* 32MB: 724k
* 64MB: 1024k
* 128MB: 1448k
* 256MB: 2048k
* 512MB: 2896k
* 1024MB: 4096k
* 2048MB: 5792k
* 4096MB: 8192k
* 8192MB: 11584k
* 16384MB: 16384k
*/
int __meminit init_per_zone_wmark_min(void)
{
unsigned long lowmem_kbytes;
int new_min_free_kbytes;
lowmem_kbytes = nr_free_buffer_pages() * (PAGE_SIZE >> 10);
new_min_free_kbytes = int_sqrt(lowmem_kbytes * 16);
if (new_min_free_kbytes > user_min_free_kbytes) {
min_free_kbytes = new_min_free_kbytes;
if (min_free_kbytes < 128)
min_free_kbytes = 128;
if (min_free_kbytes > 65536)
min_free_kbytes = 65536;
} else {
pr_warn("min_free_kbytes is not updated to %d because user defined value %d is preferred\n",
new_min_free_kbytes, user_min_free_kbytes);
}
setup_per_zone_wmarks();
refresh_zone_stat_thresholds();
setup_per_zone_lowmem_reserve();
setup_per_zone_inactive_ratio();
return 0;
}
static void __setup_per_zone_wmarks(void)
{
unsigned long pages_min = min_free_kbytes >> (PAGE_SHIFT - 10);
unsigned long lowmem_pages = 0;
struct zone *zone;
unsigned long flags;
/* Calculate total number of !ZONE_HIGHMEM pages */
for_each_zone(zone) {
if (!is_highmem(zone))
lowmem_pages += zone->managed_pages;
}
for_each_zone(zone) {
zone->watermark[WMARK_LOW] = min_wmark_pages(zone) + (tmp >> 2);
zone->watermark[WMARK_HIGH] = min_wmark_pages(zone) + (tmp >> 1);
}
/* update totalreserve_pages */
calculate_totalreserve_pages();
}
在这种系统中,总的"min"值约等于所有zones可用内存的总和乘以16再开平方的大小,可通过"/proc/sys/vm/min_free_kbytes"查看和修改。假设可用内存的大小是4GiB,那么其对应的"min"值就是8MiB
low = min +min/4;
high = min + min/2;
pages free 2827
min 64
low 80
high 96
spanned 4095
present 3997
managed 3976
protection: (0, 2710, 2710, 2710, 2710)
nr_free_pages 2827
nr_zone_inactive_anon 0
nr_zone_active_anon 0
nr_zone_inactive_file 0
nr_zone_active_file 1141
nr_zone_unevictable 0
nr_zone_write_pending 0
nr_mlock 0
nr_page_table_pages 0
nr_kernel_stack 0
nr_bounce 0
nr_zspages 0
nr_free_cma 0
numa_hit 5197
numa_miss 0
numa_foreign 0
numa_interleave 0
numa_local 5197
numa_other 0
2:新分配的页加入哪里
- 进程堆栈、数据段中使用的新的匿名页 —加入匿名活动链表头部
- shmem共享内存使用的新页 —加入匿名非活动链表头部
- 匿名mmap映射的共享内存 —加入匿名非活动链表头部
- 匿名mmap映射的文件 —加入文件非活动链表头部
- 新的磁盘文件数据的文件页 —加入文件非活动链表头部