Skip to content

Latest commit

 

History

History
626 lines (567 loc) · 28.6 KB

内存管理系列二十二:内存回收核心流程.md

File metadata and controls

626 lines (567 loc) · 28.6 KB

前沿

往篇回顾

在上一篇中主要分析了内存回收的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;
}