kswapd进程工作原理(三)——回收LRU链表

注:本文分析基于linux-4.18.0-193.14.2.el8_2内核版本,即CentOS 8.2

在上篇文章——kswapd进程工作原理(二)——回收内存上半部中,我们分析了kswapd进程的具体回收过程,今天我们再继续往下,看看kswapd是如何回收LRU链表。

1 shrink_list

主要流程:

  • 如果操作的是活动LRU链表,需要满足非活动LRU链表中页面过少,才会回收活动链表
  • 如果操作的是活动LRU链表,直接开始回收
static unsigned long shrink_list(enum lru_list lru, unsigned long nr_to_scan,
				 struct lruvec *lruvec, struct scan_control *sc)
{
	//如果操作的是活动LRU链表
	if (is_active_lru(lru)) {
		//先判断非活动LRU链表是不是太少了,是的话先压缩下活动链表
		//也就是说活动链表优先级低,只要非活动链表数量够多,活动链表可以不回收
		if (inactive_list_is_low(lruvec, is_file_lru(lru), sc, true))
			shrink_active_list(nr_to_scan, lruvec, sc, lru);
		return 0;
	}
	//如果操作的是活动LRU链表,直接开始回收
	return shrink_inactive_list(nr_to_scan, lruvec, sc, lru);
}

2 shrink_active_list

主要流程:

  • 将pagevec中的LRU缓存全部刷到对应的LRU链表
  • 从LRU链表中分离出一批页面到l_hold链表中,避免锁竞争
  • 处理文件页的buffer_heads
  • 最近被访问过的可执行文件页移动到活动LRU链表,其他页面清除PG_active标志,放到非活动LRU链表
  • 把引用计数为0的页面释放回到每CPU单页高速缓存per_cpu_pages中
static void shrink_active_list(unsigned long nr_to_scan,
			       struct lruvec *lruvec,
			       struct scan_control *sc,
			       enum lru_list lru)
{
	...
	//将pagevec中的LRU缓存全部刷到对应的LRU链表
	lru_add_drain();
    // kswapd流程sc->may_unmap为1
    // 直接内存回收sc->may_unmap为1
    // 快速内存回收的情况,sc->may_unmap与zone_reclaim_mode有关
	if (!sc->may_unmap)
		isolate_mode |= ISOLATE_UNMAPPED;

	spin_lock_irq(&pgdat->lru_lock);
	//从LRU链表中分离出一批页面到l_hold链表中,避免锁竞争
	nr_taken = isolate_lru_pages(nr_to_scan, lruvec, &l_hold,
				     &nr_scanned, sc, isolate_mode, lru);
	...
	//针对刚刚分离出来的l_hold链表进行回收,这时就不用加锁了
	while (!list_empty(&l_hold)) {
		cond_resched();
		page = lru_to_page(&l_hold);
		list_del(&page->lru);
		//如果不可回收,页面放回到LRU链表(会经过pagevec LRU缓存)
		if (unlikely(!page_evictable(page))) {
			putback_lru_page(page);
			continue;
		}
		//buffer_head过多
		if (unlikely(buffer_heads_over_limit)) {
			if (page_has_private(page) && trylock_page(page)) {
				if (page_has_private(page))
					//释放文件页对应的buffer_head链表中的buffer_head
					try_to_release_page(page, 0);
				unlock_page(page);
			}
		}
		//页面近期有被引用
		if (page_referenced(page, 0, sc->target_mem_cgroup, &vm_flags)) {
			nr_rotated += hpage_nr_pages(page);
			//如果是可执行文件页面(代码段),放到l_active链表,此次不回收
			if ((vm_flags & VM_EXEC) && page_is_file_cache(page)) {
				list_add(&page->lru, &l_active);
				continue;
			}
		}
		//清除页面active标志
		ClearPageActive(page);	/* we are de-activating */
		SetPageWorkingset(page); //设置PageWorkingset标志
		//页面加入l_inactive链表
		list_add(&page->lru, &l_inactive);
	}

	spin_lock_irq(&pgdat->lru_lock);

	reclaim_stat->recent_rotated[file] += nr_rotated;
	//把l_active链表和l_inactive的页面放回到对应的LRU链表,并把引用计数为0的页面放到l_hold链表中
	nr_activate = move_active_pages_to_lru(lruvec, &l_active, &l_hold, lru);
	nr_deactivate = move_active_pages_to_lru(lruvec, &l_inactive, &l_hold, lru - LRU_ACTIVE);
	__mod_node_page_state(pgdat, NR_ISOLATED_ANON + file, -nr_taken);
	spin_unlock_irq(&pgdat->lru_lock);

	mem_cgroup_uncharge_list(&l_hold);
	//把引用计数为0的页面释放回到每CPU单页高速缓存per_cpu_pages中
	free_unref_page_list(&l_hold); 
	trace_mm_vmscan_lru_shrink_active(pgdat->node_id, nr_taken, nr_activate,
			nr_deactivate, nr_rotated, sc->priority, file);
}

3 shrink_inactive_list

主要流程:

  • 将pagevec中的LRU缓存全部刷到对应的LRU链表
  • 从LRU链表中分离出一批页面到page_list链表中,避免锁竞争
  • 遍历page_list上隔离出来的页面,尝试回收
  • 把page_list处理完剩下的页面放回到对应的LRU链表
  • 判断是否要唤醒BDI脏页回写进程
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)
{
	...
	//同样将pagevec中的LRU缓存全部刷到对应的LRU链表
	lru_add_drain();

	if (!sc->may_unmap)
		isolate_mode |= ISOLATE_UNMAPPED;

	spin_lock_irq(&pgdat->lru_lock);
	//隔离部分页面到page_list链表
	nr_taken = isolate_lru_pages(nr_to_scan, lruvec, &page_list,
				     &nr_scanned, sc, isolate_mode, lru);
	...
	if (nr_taken == 0)
		return 0;
	//开始回收page_list上的页面
	nr_reclaimed = shrink_page_list(&page_list, pgdat, sc, 0,
				&stat, false);
	...
	//把page_list剩下的页面放回到对应的LRU链表,里面有活动页面也有非活动页面
	putback_inactive_pages(lruvec, &page_list);
	...
	//把引用计数为0的页面释放回到每CPU单页高速缓存per_cpu_pages中
	free_unref_page_list(&page_list);
	//隔离出来的页面都是脏页但是没有在回写,说明当前BDI回写进程没有启动
	//唤醒进程,开始回写系统脏页
	if (stat.nr_unqueued_dirty == nr_taken)
		wakeup_flusher_threads(WB_REASON_VMSCAN);
	...
	return nr_reclaimed;
}

4 shrink_page_list

主要流程:

  • 页面不可回收,放active LRU链表
  • 页面有进程映射但不允许umap操作,继续放inactive链表
  • 页面正在回写,异步操作就放active LRU链表;同步操作就等待回写完成,然后放临时链表尾部,待会再处理
  • 匿名页和可执行文件页最近有被进程访问过,放active LRU链表
  • 页面最近被多个进程访问过或者被单个进程访问但PG_referenced置位,也放active LRU链表,其余情况尝试回收
  • 匿名页没在swapcache中,将页面放到swapcache
  • 页面有被其他进程映射,尝试解除映射并回收
  • 对脏文件页执行pageout
  • 释放文件页的buffer_head
  • 匿名页且没有swap分区,继续待在inactive链表
  • 把引用计数为0的页面释放回到每CPU单页高速缓存per_cpu_pages中
  • 剩下的页面放回到隔离出来的链表中
static unsigned long shrink_page_list(struct list_head *page_list,
				      struct pglist_data *pgdat,
				      struct scan_control *sc,
				      enum ttu_flags ttu_flags,
				      struct reclaim_stat *stat,
				      bool force_reclaim)
{
	...
	//遍历处理隔离出来的页面
	while (!list_empty(page_list)) {
		...
		//页面不可回收,不处理,放active LRU链表
		if (unlikely(!page_evictable(page)))
			goto activate_locked;
		//不允许umap操作,但是页面有进程映射,不处理,继续放inactive链表
		if (!sc->may_unmap && page_mapped(page))
			goto keep_locked;
		...
		//判断是否能进入文件系统,比如进行回写啥的操作
		may_enter_fs = (sc->gfp_mask & __GFP_FS) ||
			(PageSwapCache(page) && (sc->gfp_mask & __GFP_IO));

		//判断页面是否为脏页或者正在回写
		page_check_dirty_writeback(page, &dirty, &writeback);
		if (dirty || writeback)
			nr_dirty++;
		//脏页,但是没有在回写,数量多了说明BDI回写进程没启动
		if (dirty && !writeback)
			nr_unqueued_dirty++;
		...
		//页面正在回写
		if (PageWriteback(page)) {
			//kswapd内核进程,且页面可以进行回收,同时该zone上有许多页面在回写
			//则本次回收就不对该页面操作
			if (current_is_kswapd() && PageReclaim(page) &&
			    test_bit(PGDAT_WRITEBACK, &pgdat->flags)) {
				nr_immediate++;
				goto activate_locked;

			//如果是全局内存回收或者页面没有被标记为回收或者进程不允许进行文件系统操作
			} else if (sane_reclaim(sc) || !PageReclaim(page) || !may_enter_fs) {
				//设置回收标志
				SetPageReclaim(page);
				nr_writeback++;
				goto activate_locked;

			//针对某个memcg的内存回收
			} else {
				unlock_page(page);
				//等待页面回写完成
				wait_on_page_writeback(page);
				//加入到链表尾部,待会再次尝试回收
				list_add_tail(&page->lru, page_list);
				continue;
			}
		}
		//非强制回收,kswapd路径force_reclaim为false
		//判断页面是否需要移动到活动LRU链表
		if (!force_reclaim)
			//对于匿名页,最近有被进程访问过,则移动到活动LRU链表
			//映射可执行文件的文件页,最近被进程访问过,同样放到活动LRU链表
			//最近被进程访问且PG_referenced置位了,或者最近被多个进程访问过,也放入活动LRU链表
			//其余情况都尝试回收
			references = page_check_references(page, sc);

		switch (references) {
		//放到活动lru链表
		case PAGEREF_ACTIVATE:
			goto activate_locked;
		//继续保存在非活动lru链表
		case PAGEREF_KEEP:
			nr_ref_keep++;
			goto keep_locked;
		//尝试回收页面
		case PAGEREF_RECLAIM:
		case PAGEREF_RECLAIM_CLEAN:
			; /* try to reclaim the page below */
		}
		//匿名页且有swap分区
		if (PageAnon(page) && PageSwapBacked(page)) {
			//页面没在swapcache中
			if (!PageSwapCache(page)) {
				//进程不允许IO操作
				if (!(sc->gfp_mask & __GFP_IO))
					goto keep_locked;
				...
				//把匿名页添加到swapcache中,并设置为脏页
				if (!add_to_swap(page)) {
					...
				}

				may_enter_fs = 1;

				/* Adding to swap updated mapping */
				mapping = page_mapping(page);
			}
		} else if (unlikely(PageTransHuge(page))) {
			/* Split file THP */
			if (split_huge_page_to_list(page, page_list))
				goto keep_locked;
		}

		//页面有被其他进程映射,尝试解除映射并回收
		if (page_mapped(page)) {
			enum ttu_flags flags = ttu_flags | TTU_BATCH_FLUSH;

			if (unlikely(PageTransHuge(page)))
				flags |= TTU_SPLIT_HUGE_PMD;
			//尝试unmap,失败的话将页面返回活动LRU链表
			if (!try_to_unmap(page, flags)) {
				nr_unmap_fail++;
				goto activate_locked;
			}
		}
		//如果是脏页
		if (PageDirty(page)) {
			//非kswapd进程无法对文件页进行回写,并且需要该node脏页达到一定数量
			//否则不回收,只将该页面设置为可回收放到活动LRU链表
			if (page_is_file_cache(page) &&
			    (!current_is_kswapd() || !PageReclaim(page) ||
			     !test_bit(PGDAT_DIRTY, &pgdat->flags))) {

				inc_node_page_state(page, NR_VMSCAN_IMMEDIATE);
				SetPageReclaim(page);

				goto activate_locked;
			}
			//文件页最近没被进程访问过,但PG_referenced被置位
			if (references == PAGEREF_RECLAIM_CLEAN)
				goto keep_locked;
			//不允许操作文件系统
			if (!may_enter_fs)
				goto keep_locked;
			//不允许回写,以上这三种情况页面都将继续放在非活动LRU链表
			if (!sc->may_writepage)
				goto keep_locked;

			//刷新TLB
			try_to_unmap_flush_dirty();
			//对文件页执行pageout操作
			switch (pageout(page, mapping, sc)) {
			case PAGE_KEEP:
				goto keep_locked;
			case PAGE_ACTIVATE:
				goto activate_locked;
			case PAGE_SUCCESS:
				if (PageWriteback(page))
					goto keep;
				if (PageDirty(page))
					goto keep;
				if (!trylock_page(page))
					goto keep;
				if (PageDirty(page) || PageWriteback(page))
					goto keep_locked;
				mapping = page_mapping(page);
			case PAGE_CLEAN:
				; /* try to free the page below */
			}
		}

		//走到这说明页面已经完成pageout,再检查此文件页是否有buffer_head
		if (page_has_private(page)) {
			//尝试释放buffer_head
			if (!try_to_release_page(page, sc->gfp_mask))
				goto activate_locked;
			if (!mapping && page_count(page) == 1) {
				unlock_page(page);
				if (put_page_testzero(page))
					goto free_it;
				else {
					nr_reclaimed++;
					continue;
				}
			}
		}
		//匿名页且没有swap分区,继续待在inactive链表
		if (PageAnon(page) && !PageSwapBacked(page)) {
			/* follow __remove_mapping for reference */
			if (!page_ref_freeze(page, 1))
				goto keep_locked;
			if (PageDirty(page)) {
				page_ref_unfreeze(page, 1);
				goto keep_locked;
			}

			count_vm_event(PGLAZYFREED);
			count_memcg_page_event(page, PGLAZYFREED);
		} else if (!mapping || !__remove_mapping(mapping, page, true))
			goto keep_locked;

		unlock_page(page);
free_it:
		nr_reclaimed++;

		if (unlikely(PageTransHuge(page))) {
			mem_cgroup_uncharge(page);
			(*get_compound_page_dtor(page))(page);
		} else
			//可以释放的页面放入free_pages链表
			list_add(&page->lru, &free_pages);
		continue;

activate_locked:
		//如果swap空间要满了,尝试释放swap
		if (PageSwapCache(page) && (mem_cgroup_swap_full(page) ||
						PageMlocked(page)))
			try_to_free_swap(page);
		VM_BUG_ON_PAGE(PageActive(page), page);
		//页面没有被锁住,设置PG_Active标志
		if (!PageMlocked(page)) {
			SetPageActive(page);
			pgactivate++;
			count_memcg_page_event(page, PGACTIVATE);
		}
keep_locked:
		unlock_page(page);
keep:	
		//继续保存在非活动lru链表的页面添加到ret_pages链表
		list_add(&page->lru, &ret_pages);
		VM_BUG_ON_PAGE(PageLRU(page) || PageUnevictable(page), page);
	}

	mem_cgroup_uncharge_list(&free_pages);
	//刚才可能有umap操作,刷新TLB
	try_to_unmap_flush();
	//把引用计数为0的页面释放回到每CPU单页高速缓存per_cpu_pages中
	free_unref_page_list(&free_pages);
	//ret_pages链表上的页面放回到隔离出来的链表中,后面会放回到对应的LRU链表
	list_splice(&ret_pages, page_list);
	...
	return nr_reclaimed;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值