前沿
往篇回顾
在上一篇中主要分析了内存回收的3个主要入口,以及他们在调用
shrink_zone()
前的处理流程
内核线程kswapd定时触发内存回收(unmap,swap)
目标:使每个zone的水位都高于high level
-
对每个zone,首先回收该zone上超过soft_limit最多的mem_cgroup在该zone上mem_cgroup_per_zone对应的lru链表
-
再触发
shrink_zones
回收内存 -
执行完node中所有zone的内存回收后,尝试唤醒队列
pfmemalloc_wait
中的进程,如果需要则进行内存压缩
由进程快速分配(low level)失败触发内存回收(swap)
目标:收集到足够的空闲页框max(nr_pages, SWAP_CLUSTER_MAX),但是分析来看并不能保证这些页面是块状的?
由进程慢速分配(min level)失败触发内存回收(unmap,swap)
目标:回收到SWAP_CLUSTER_MAX个空闲page或者内存压缩后能满足内存分配要求,为什么是和快速分配不一样?
-
首先使用throttle_direct_reclaim()判断当前内存申请进程是否要进入等待队列
-
触发
shrink_zones
回收内存 -
达到目标后返回
本篇主要内容
学习内存回收核心流程
代码分析
shrink_zone
/* 对zone进行内存回收:
* 1. 首先从root_memcg遍历zone内的memcg
a. 获取memcg的lru链表描述符lruvec
b. 获取memcg的swapiness
c. 调用shrink_lruvec()对此memcg的lru链表进行处理
d. 如果本zone是zonelist最优,则触发shrink_slab遍历shrinker链表,对所有注册了shrinker函数的磁盘缓存进行处理
e. 如果回收任务非全局zone回收,则判断是否已经回收到足够内存-->跳出循环
2. 完成memcg遍历后,检测是否需要对zone再次回收
a. 没有回收到比目标order值多一倍的数量页框,并且非活动lru链表中的页框数量 > 目标order多一倍的页
b. 此zone不满足内存压缩的条件,则继续对此zone进行内存回收
*/
static bool shrink_zone(struct zone *zone, struct scan_control *sc,
bool is_classzone)
{
struct reclaim_state *reclaim_state = current->reclaim_state;
unsigned long nr_reclaimed, nr_scanned;
bool reclaimable = false;
do {
/* 当内存回收是针对整个zone时,sc->target_mem_cgroup为NULL */
struct mem_cgroup *root = sc->target_mem_cgroup;
struct mem_cgroup_reclaim_cookie reclaim = {
.zone = zone,
.priority = sc->priority,
};
unsigned long zone_lru_pages = 0;
struct mem_cgroup *memcg;
/* 记录本次回收开始前回收到的页框数量
* 第一次时是0
*/
nr_reclaimed = sc->nr_reclaimed;
/* 记录本次回收开始前扫描过的页框数量
* 第一次时是0
*/
nr_scanned = sc->nr_scanned;
/* 获取最上层的memcg
* 如果没有指定开始的root,则默认是root_mem_cgroup
* root_mem_cgroup管理的每个zone的lru链表就是每个zone完整的lru链表
*/
memcg = mem_cgroup_iter(root, NULL, &reclaim);
do {
unsigned long lru_pages;
unsigned long scanned;
struct lruvec *lruvec;
int swappiness;
if (mem_cgroup_low(root, memcg)) {
if (!sc->may_thrash)
continue;
mem_cgroup_events(memcg, MEMCG_LOW, 1);
}
/* 获取此memcg在此zone的lru链表
* 如果内核没有开启memcg,那么就是zone->lruvec
*/
lruvec = mem_cgroup_zone_lruvec(zone, memcg);
/* 从memcg中获取swapiness,此值代表了进行swap的频率,此值较低时,那么就更多的进行文件页的回收,此值较高时,则更多进行匿名页的回收 */
swappiness = mem_cgroup_swappiness(memcg);
scanned = sc->nr_scanned;
/* 对此memcg的lru链表进行回收工作
* 每个memcg中都会为每个zone维护一个lru链表
*/
shrink_lruvec(lruvec, swappiness, sc, &lru_pages);
zone_lru_pages += lru_pages;
/* 如果回收内存的目标是最优zone,则触发回收slab */
if (memcg && is_classzone)
shrink_slab(sc->gfp_mask, zone_to_nid(zone),
memcg, sc->nr_scanned - scanned,
lru_pages);
/* 如果是对于整个zone进行回收,那么会遍历所有memcg,对所有memcg中此zone的lru链表进行回收
* 而如果只是针对某个memcg进行回收,如果回收到了足够内存则返回,如果没回收到足够内存,则对此memcg下面的memcg进行回收
*/
if (!global_reclaim(sc) &&
sc->nr_reclaimed >= sc->nr_to_reclaim) {
mem_cgroup_iter_break(root, memcg);
break;
}
/* 下一个memcg,对于整个zone进行回收和对某个memcg进行回收但回收数量不足时会执行到此 */
} while ((memcg = mem_cgroup_iter(root, memcg, &reclaim)));
/* 如果回收内存的目标是最优zone,则触发回收slab */
if (global_reclaim(sc) && is_classzone)
shrink_slab(sc->gfp_mask, zone_to_nid(zone), NULL,
sc->nr_scanned - nr_scanned,
zone_lru_pages);
if (reclaim_state) {
sc->nr_reclaimed += reclaim_state->reclaimed_slab;
reclaim_state->reclaimed_slab = 0;
}
/* 计算此memcg的内存压力,保存到memcg->vmpressure */
vmpressure(sc->gfp_mask, sc->target_mem_cgroup,
sc->nr_scanned - nr_scanned,
sc->nr_reclaimed - nr_reclaimed);
/* 只要回收到page,则认为是可回收的 */
if (sc->nr_reclaimed - nr_reclaimed)
reclaimable = true;
/*
* 继续对此zone进行内存回收有两种情况:
* 1. 没有回收到比目标order值多一倍的数量页框,并且非活动lru链表中的页框数量 > 目标order多一倍的页
* 2. 此zone不满足内存压缩的条件,则继续对此zone进行内存回收
*/
} while (should_continue_reclaim(zone, sc->nr_reclaimed - nr_reclaimed,
sc->nr_scanned - nr_scanned, sc));
return reclaimable;
}
shrink_lruvec
/* 对lru链表描述符lruvec中的lru链表进行内存回收,此lruvec有可能属于一个memcg,也可能是属于一个zone
* lruvec: lru链表描述符,里面有5个lru链表,活动/非活动匿名页lru链表,活动/非活动文件页lru链表,禁止换出页链表
* swappiness: 扫描匿名页的亲和力,其值越低,就扫描越少的匿名页
当为0时,基本不会扫描匿名页lru链表,除非针对整个zone进行内存回收时,此zone的所有文件页都释放了都不能达到高阀值,那就只对匿名页进行扫描
* sc: 扫描控制结构
* 1. 调用get_scan_count()计算每个lru链表需要扫描的页框数量,保存在nr数组中
* 2. 循环判断nr数组中是否还有lru链表没有扫描完成
* 使用shrink_list对lru链表进行回收
* 3. 如果太多脏页正在进行回写,则睡眠100ms
* 回收到足够页框后直接返回:快速内存回收、kswapd内存回收中会这样做,在回收到sc->nr_to_reclaim数量的页框后直接返回上一级
* 回收到足够页框后继续扫描:直接内存回收时第一次调用shrink_zone()时、kswapd针对某个memcg进行内存回收时会这样做,即使回收到sc->nr_to_reclaim数量的页框后,还会继续扫描,直到nr数组为0具体见后面直接内存回收
*/
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链表描述符中的每个lru链表,计算它们本次扫描应该扫描的页框数量
* 计算好的每个lru链表需要扫描的页框数量保存在nr中
* 每个lru链表需要扫描多少与sc->priority有关,sc->priority越小,那么扫描得越多
*/
get_scan_count(lruvec, swappiness, sc, nr, lru_pages);
/* Record the original scan target for proportional adjustments later */
memcpy(targets, nr, sizeof(nr));
/* scan_adjusted: 将nr[]中的数量页数都扫描完才停止
* 1. 针对整个zone进行扫描
* 2. 不是在kswapd内核线程中调用的,
* 3. 优先级为默认优先级
* 快速回收不会这样做(快速回收的优先级不是DEF_PRIORITY)
*/
scan_adjusted = (global_reclaim(sc) && !current_is_kswapd() &&
sc->priority == DEF_PRIORITY);
/* 初始化这个struct blk_plug
* 主要初始化list,mq_list,cb_list这三个链表头
* 然后current->plug = plug
*/
blk_start_plug(&plug);
/* 如果LRU_INACTIVE_ANON,LRU_ACTIVE_FILE,LRU_INACTIVE_FILE这三个其中一个需要扫描的页框数没有扫描完,那扫描就会继续
* 注意这里不会判断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;
for_each_evictable_lru(lru) {
/* nr[lru类型]如果有页框需要扫描 */
if (nr[lru]) {
/* 获取本次需要扫描的页框数量,nr[lru]与SWAP_CLUSTER_MAX的最小值 */
nr_to_scan = min(nr[lru], SWAP_CLUSTER_MAX);
nr[lru] -= nr_to_scan;
/* 对此lru类型的lru链表进行内存回收,本次回收的页框数保存在nr_reclaimed中 */
nr_reclaimed += shrink_list(lru, nr_to_scan,
lruvec, sc);
}
}
/* 没有回收到足够页框,或者需要忽略需要回收的页框数量,尽可能多的回收页框,则继续进行回收 */
if (nr_reclaimed < nr_to_reclaim || scan_adjusted)
continue;
/* 已经回收到了足够数量的页框,调用到此是用于判断是否还要继续扫描,因为已经回收到了足够页框了 */
/* 扫描一遍后,剩余需要扫描的文件页数量和匿名页数量 */
nr_file = nr[LRU_INACTIVE_FILE] + nr[LRU_ACTIVE_FILE];
nr_anon = nr[LRU_INACTIVE_ANON] + nr[LRU_ACTIVE_ANON];
/* 已经扫描完成了,退出循环 */
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;/* 总共回收的页框数量 */
/* 非活动匿名页lru链表中页数量太少 */
if (inactive_anon_is_low(lruvec))
/* 从活动匿名页lru链表中移动一些页去非活动匿名页lru链表,最多32个 */
shrink_active_list(SWAP_CLUSTER_MAX, lruvec,
sc, LRU_ACTIVE_ANON);
/* 如果太多脏页进行回写了,这里就睡眠100ms */
throttle_vm_writeout(sc->gfp_mask);
}
shrink_list
static unsigned long shrink_list(enum lru_list lru, unsigned long nr_to_scan,
struct lruvec *lruvec, struct scan_control *sc)
{
/* 活动lru(包括活动匿名页lru和活动文件页lru) */
if (is_active_lru(lru)) {
if (inactive_list_is_low(lruvec, lru))
shrink_active_list(nr_to_scan, lruvec, sc, lru);
return 0;
}
/* 非活动lru,那么会对此lru类型的lru链表中的页框进行回收 */
return shrink_inactive_list(nr_to_scan, lruvec, sc, lru);
}
shrink_active_list
/*
* 回收active链表中内存
* 1. 使用lru_add_drain将本CPU的lru缓存全部清空,放到对应的lru链表中
* 2. 首先使用isolate_lru_pages()将active链表中满足条件的page隔离到l_hold链表
* 3. 如果节点buffer_heads数量超过限制,则尝试对扫描到的文件页进行buffer_heads的释放
* 4. 遍历l_hold链表,使用page_referenced()及RMAP,确定page的引用情况及页面类型
* a. 页面最近被访问 && 如果是代码段==>则放到l_active链表
* b. 其它页面均放入l_inactive链表
* 3. 将l_hold中剩余页都是page->_count为0的页,作为冷页放回到伙伴系统的每CPU单页框高速缓存中
*/
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;
/* 从lru中获取到的页存放在这,到最后这里面还有剩余的页的话,就把它们释放回伙伴系统 */
LIST_HEAD(l_hold); /* The pages which were snipped off */
/* 移动到活动lru链表头部的页的链表 */
LIST_HEAD(l_active);
/* 将要移动到非活动lru链表的页放在这 */
LIST_HEAD(l_inactive);
struct page *page;
/* lruvec的统计结构 */
struct zone_reclaim_stat *reclaim_stat = &lruvec->reclaim_stat;
unsigned long nr_rotated = 0;
isolate_mode_t isolate_mode = 0;
/* lru是否属于LRU_INACTIVE_FILE或者LRU_ACTIVE_FILE */
int file = is_file_lru(lru);
/* lruvec所属的zone */
struct zone *zone = lruvec_zone(lruvec);
/* 将当前CPU的多个pagevec(lru缓存)中的页都刷入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中,返回隔离的数量
* 1. 当sc->may_unmap为0时,则不会将有进程映射的页隔离出来
* 2. 当sc->may_writepage为0时,则不会将脏页和正在回写的页隔离出来
* 3. 隔离出来的页会page->_count++
* 4. nr_taken保存拿出的页的数量
*/
nr_taken = isolate_lru_pages(nr_to_scan, lruvec, &l_hold,
&nr_scanned, sc, isolate_mode, lru);
if (global_reclaim(sc))
/* 更新本zone的统计数据 */
__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);
/* 更新本zone的统计数据 */
__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); /* 将页从l_hold中拿出来 */
list_del(&page->lru);
/* 如果页是unevictable(不可回收)的,则放回到LRU_UNEVICTABLE这个lru链表中,这个lru链表中的页不能被交换出去 */
if (unlikely(!page_evictable(page))) {
/* 放回到page所应该属于的lru链表中
* 而这里实际上是将页放到zone的LRU_UNEVICTABLE链表中
*/
putback_lru_page(page);
continue;
}
/* buffer_heads的数量超过了结点允许的最大值的情况 */
if (unlikely(buffer_heads_over_limit)) {
/* 文件页的page才有PAGE_FLAGS_PRIVATE标志 */
if (page_has_private(page) && trylock_page(page)) {
if (page_has_private(page))
/* 释放此文件页所拥有的buffer_head链表中的buffer_head,并且page->_count-- */
try_to_release_page(page, 0);
unlock_page(page);
}
}
/*
* 通过反向映射,检查映射了此页的进程页表项有多少个的Accessed被置1了
* 然后清除这些页表项的Accessed标志,此标志被置1说明这些进程最近访问过此页
*/
if (page_referenced(page, 0, sc->target_mem_cgroup,
&vm_flags)) {
/* 如果是大页,则记录一共多少个页,如果是普通页,则是1 */
nr_rotated += hpage_nr_pages(page);
/* 如果此页映射的是代码段,则将其放到l_active链表中
* 可以看出对于代码段的页,还是比较倾向于将它们放到活动文件页lru链表的
* 当代码段没被访问过时,也是有可能换到非活动文件页lru链表的
*/
if ((vm_flags & VM_EXEC) && page_is_file_cache(page)) {
list_add(&page->lru, &l_active);
continue;
}
}
/* 将页放到l_inactive链表中
* 只有最近访问过的代码段的页不会被放入,其他即使被访问过了,也会被放入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);
/* 记录的是最近被加入到活动lru链表的页数量,之后这些页被返回到active链表 */
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
/*
* inactive链表的内存回收
* 1. 将当前CPU的所有lru缓存页刷入lru链表中
* 2. 通过isolate_lru_pages()函数从活动lru链表末尾扫描出符合要求的页,这些页会通过page->lru加入到page_list链表中
* 3. 调用shrink_page_list()对这个page_list链表中的页进行回收处理
* 4. putback_inactive_pages()将page_list链表中剩余的页放回到它们应该放入到链表中
* 5. free_hot_cold_page_list()将page->_count==0的页进行释放
*/
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;
/* 如果隔离的页数量多于非活动的页数量,则是隔离太多页了,个人猜测这里是控制并发
* 当zone的NR_INACTIVE_FILE/ANON < NR_ISOLATED_ANON时,有一种情况是其他CPU也在对此zone进行内存回收,所以NR_ISOLATED_ANON比较高
*/
while (unlikely(too_many_isolated(zone, file, sc))) {
/* 这里会休眠等待100ms,如果是并发进行内存回收,另一个CPU可能也在执行内存回收 */
congestion_wait(BLK_RW_ASYNC, HZ/10);
/* We are about to die and free our memory. Return now. */
/* 当前进程被其他进程kill了,这里接受到了kill信号 */
if (fatal_signal_pending(current))
return SWAP_CLUSTER_MAX;
}
/* 将当前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链表描述符的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);
/* 更新zone中对应lru中页的数量 */
__mod_zone_page_state(zone, NR_LRU_BASE + lru, -nr_taken);
/* 此zone对应隔离的ANON/FILE页框数量 */
__mod_zone_page_state(zone, NR_ISOLATED_ANON + file, nr_taken);
/* 如果是针对整个zone的内存回收,而不是某个memcg的内存回收的情况 */
if (global_reclaim(sc)) {
/* 统计zone中扫描的页框总数 */
__mod_zone_page_state(zone, NR_PAGES_SCANNED, nr_scanned);
if (current_is_kswapd())
/* 如果是在kswapd内核线程中调用到此的,则扫描的页框数量统计到zone的PGSCAN_KSWAPD */
__count_zone_vm_events(PGSCAN_KSWAPD, zone, nr_scanned);
else
/* 否则扫描的数量统计到zone的PGSCAN_DIRECT */
__count_zone_vm_events(PGSCAN_DIRECT, zone, nr_scanned);
}
spin_unlock_irq(&zone->lru_lock);
/* 隔离出来的页数量为0 */
if (nr_taken == 0)
return 0;
/* 上面的代码已经将非活动lru链表中的一些页拿出来放到page_list中了,这里是对page_list中的页进行内存回收
* 此函数的步骤:
* 1.此页是否在进行回写(两种情况会导致回写,之前进行内存回收时导致此页进行了回写;此页为脏页,系统自动将其回写),这种情况同步回收和异步回收有不同的处理
* 2.此次回收时非强制进行回收,那要先判断此页能不能进行回收
* 如果是匿名页,只要最近此页被进程访问过,则将此页移动到活动lru链表头部,否则回收
* 如果是映射可执行文件的文件页,只要最近被进程访问过,就放到活动lru链表,否则回收
* 如果是其他的文件页,如果最近被多个进程访问过,移动到活动lru链表,如果只被1个进程访问过,但是PG_referenced置位了,也放入活动lru链表,其他情况回收
* 3.如果遍历到的page为匿名页,但是又不处于swapcache中,这里会尝试将其加入到swapcache中并把页标记为脏页,这个swapcache作为swap缓冲区,是一个address_space
* 4.对所有映射了此页的进程的页表进行此页的unmap操作
* 5.如果页为脏页,则进行回写,分同步和异步,同步情况是回写完成才返回,异步情况是加入块层的写入队列,标记页的PG_writeback表示正在回写就返回,此页将会被放到非活动lru链表头部
* 6.检查页的PG_writeback标志,如果此标志位0,则说明此页的回写完成(两种情况: 1.同步回收 2.之前异步回收对此页进行的回写已完成),则从此页对应的address_space中的基树移除此页的结点,加入到free_pages链表
* 对于PG_writeback标志位1的,将其重新加入到page_list链表,这个链表之后会将里面的页放回到非活动lru链表末尾,下次进行回收时,如果页回写完成了就会被释放
* 7.对free_pages链表的页释放
*
* page_list中返回时有可能还有页,这些页是要放到非活动lru链表末尾的页,而这些页当中,有些页是正在进行回收的回写,当这些回写完成后,系统再次进行内存回收时,这些页就会被释放
* 而有一些页是不满足回收情况的页
* nr_dirty: page_list中脏页的数量
* nr_unqueued_dirty: page_list中脏页但并没有正在回写的页的数量
* nr_congested: page_list中正在进行回写并且设备正忙的页的数量(这些页可能回写很慢)
* nr_writeback: page_list中正在进行回写但不是在回收的页框数量
* nr_immediate: 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 */
reclaim_stat->recent_scanned[file] += nr_taken;
/* 如果是针对整个zone,而不是某个memcg的情况 */
if (global_reclaim(sc)) {
/* 如果是在kswakpd内核线程中 */
if (current_is_kswapd())
/* 更新到zone的PGSTEAL_KSWAPD */
__count_zone_vm_events(PGSTEAL_KSWAPD, zone,
nr_reclaimed);
else
/* 不是在kswapd内核线程中,更新到PGSTEAL_DIRECT */
__count_zone_vm_events(PGSTEAL_DIRECT, zone,
nr_reclaimed);
}
/*
* 将page_list中剩余的页放回它对应的lru链表中,这里的页有三种情况:
* 1.最近被访问了,放到活动lru链表头部
* 2.此页需要锁在内存中,加入到unevictablelru链表
* 3.此页为非活动页,移动到非活动lru链表头部
* 当页正在进行回写回收,当回写完成后,通过判断页的PG_reclaim可知此页正在回收,会把页移动到非活动lru链表末尾,具体见end_page_writeback()函数
* 加入lru的页page->_count--
* 因为隔离出来时page->_count++,而在lru中是不需要对page->_count++的
*/
putback_inactive_pages(lruvec, &page_list);
/* 更新此zone对应隔离的ANON/FILE页框数量,这里减掉了nr_taken,与此函数之前相对应 */
__mod_zone_page_state(zone, NR_ISOLATED_ANON + file, -nr_taken);
spin_unlock_irq(&zone->lru_lock);
mem_cgroup_uncharge_list(&page_list);
/* 释放page_list中剩余的页到伙伴系统中的每CPU页高速缓存中,以冷页处理
* 这里剩余的就是page->_count == 0的页
*/
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)
/* 标记ZONE的ZONE_WRITEBACK,标记此zone许多页在回写 */
set_bit(ZONE_WRITEBACK, &zone->flags);
/* 本次内存回收是针对整个zone的,这里面主要对zone的flags做一些标记 */
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.
*/
/* 非hibernation_mode,非kswapd的情况下,如果现在设备回写压力较大 */
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;
}