关于Kswapd的理解(一)

关于Kswapd的理解(一)

之前跟踪了MemAvailable的计算方式,其中watermark的计算花了很多精力,在学习的过程中看到一些资料中说watermark是给swapd使用的,于是乎,研究一下看看咯

本部分主要记录两个方面:

  1. 对于kswapd这个东西,从初学者(我)的角度出发会考虑哪些内容?
  2. 跟踪kswapd 初始化结构部分;

1. KSwapd机制

image-20220329194956950

上图是还没有跟踪kswapd code的时候,对自己提出的一些疑问和自身的理解,主要是如下内容:

  1. kswapd 机制用来做什么:内存不足时回收内存;
  2. kswapd 机制的载体是什么:启动一个内核线程用于回收mem,平时进入sleep状态;
  3. kswapd 线程满足什么条件会被唤醒:当前来看分配mem时低于watermark[high] 的时候会被唤醒;
  4. kswapd 机制会回收哪些内存:anon & file ,即我们俗称的cache;
  5. kswapd 机制如何回收:从根本上讲,主要是discard、swap到emmc、zram等几种方式;
  6. kswapd 机制回收内存的具体策略是:还在学习中,后续持续补充;

2. code 跟踪

接下来具体来跟踪code来理解kswapd

2.1 kswapd 初始化

初始化部分的调用逻辑非常简单:

image-20220329202338495

这部分平平无奇:主要逻辑就是kthread_run 起一个线程运行kswapd这个功能函数;

//task 初始化部分,这种task很明确的,在init的时候run起来,在需要的时候唤醒;
static int __init kswapd_init(void)
{
	int nid;

	swap_setup();
	for_each_node_state(nid, N_MEMORY) //每个node run一个task
		kswapd_run(nid);
	hotcpu_notifier(cpu_callback, 0);
	return 0;
}

int kswapd_run(int nid)
{
	pg_data_t *pgdat = NODE_DATA(nid);
	int ret = 0;
	if (pgdat->kswapd)
		return 0;
	pgdat->kswapd = kthread_run(kswapd, pgdat, "kswapd%d", nid);//起一个线程执行kswapd
	if (IS_ERR(pgdat->kswapd)) {//失败后处理,不重要
		BUG_ON(system_state == SYSTEM_BOOTING);
		pr_err("Failed to start kswapd on node %d\n", nid);
		ret = PTR_ERR(pgdat->kswapd);
		pgdat->kswapd = NULL;
	}
	return ret;
}

2.2 swapd 功能函数

这里是本小节中最核心的部分了:

image-20220329203405809

跟常规理解的一样:

  1. 主要逻辑是一个for循环,检测到满足条件(zone_balanced)就让出CPU进入睡眠等待;
  2. 被唤醒后根据条件不同,主要会进行balance_pgdat操作进行内存回收;

具体来看code(注释):

static int kswapd(void *p)
{
	unsigned int alloc_order, reclaim_order, classzone_idx;
	pg_data_t *pgdat = (pg_data_t*)p; //当前node指针
	struct task_struct *tsk = current;//获取到current_task

	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;

	tsk->flags |= PF_MEMALLOC | PF_SWAPWRITE | PF_KSWAPD;//给task设置标志位;
	set_freezable();

	pgdat->kswapd_order = alloc_order = reclaim_order = 0;//申请的order,即2^order这么多pages
	pgdat->kswapd_classzone_idx = classzone_idx = 0;//处理的zone的标号
	for ( ; ; ) {//进入循环
		bool ret;

kswapd_try_sleep:
		kswapd_try_to_sleep(pgdat, alloc_order, reclaim_order, classzone_idx);
		//核心处理1,实质的就是判断各个zone是否为balanced,是否balanced即判断zone内可申请的mem数量是否在watermark[high] 之上;

		alloc_order = reclaim_order = pgdat->kswapd_order; //赋值操作
		classzone_idx = pgdat->kswapd_classzone_idx; 
		pgdat->kswapd_order = 0;
		pgdat->kswapd_classzone_idx = 0;

		ret = try_to_freeze();//判断下当前是否是休眠操作
		if (kthread_should_stop())//是否有人调用thread_stop,正常情况下就是在module_exit时候调用;
			break;

		if (ret)//如果是suspend状态的话,就啥也不干,继续循环
			continue;
		trace_mm_vmscan_kswapd_wake(pgdat->node_id, classzone_idx, alloc_order);//trace 埋点,对于理解流程不需要关注;
		reclaim_order = balance_pgdat(pgdat, alloc_order, classzone_idx);//核心处理2,进行实质回收操作
		if (reclaim_order < alloc_order)//回收数量不够,则再来一次;
			goto kswapd_try_sleep;

		alloc_order = reclaim_order = pgdat->kswapd_order;
		classzone_idx = pgdat->kswapd_classzone_idx;
	}

	tsk->flags &= ~(PF_MEMALLOC | PF_SWAPWRITE | PF_KSWAPD);
	current->reclaim_state = NULL;
	lockdep_clear_current_reclaim_state();

	return 0;
}

这个主循环实际上干了两件事:

  1. 判断当前是否可以sleep,如果可以就让出了CPU(kswapd_try_to_sleep);
  2. 被唤醒后调用balance_pgdat 进行mem 回收操作;
2.2.1 kswapd_try_to_sleep

本接口即判断各个zone是否都balanced,如果都满足,就进入sleep让出CPU:

image-20220329205632765

直接上code:

static void kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_order,
				unsigned int classzone_idx)
{
	long remaining = 0;
	DEFINE_WAIT(wait);//当前task

	if (freezing(current) || kthread_should_stop())//如果需要退出,则直接返回
		return;

	prepare_to_wait(&pgdat->kswapd_wait, &wait, TASK_INTERRUPTIBLE);// wait加入kswap_wait queue中,即等待被唤醒,注意此时没有让出CPU


	if (prepare_kswapd_sleep(pgdat, reclaim_order, classzone_idx)) {//核心处理1:判断是否各个zone都是balanced

		reset_isolation_suitable(pgdat);
		wakeup_kcompactd(pgdat, alloc_order, classzone_idx);//唤醒compact线程处理,这个是压缩内存的一个机制,这里不做详细讨论;

		remaining = schedule_timeout(HZ/10); //sleep 100ms

		if (remaining) {//remaining > 0说明被唤醒而非100ms结束
			pgdat->kswapd_classzone_idx = max(pgdat->kswapd_classzone_idx, classzone_idx);
			pgdat->kswapd_order = max(pgdat->kswapd_order, reclaim_order);
		}

		finish_wait(&pgdat->kswapd_wait, &wait);//将wait从kswapd_wait queue中移除,并将当前状态配置为running
		prepare_to_wait(&pgdat->kswapd_wait, &wait, TASK_INTERRUPTIBLE);//将wait加入kswapd wait queue,这里看起来是确保queue中只有一个等待事件;
	}

	if (!remaining &&//上述没有被唤醒
	    prepare_kswapd_sleep(pgdat, reclaim_order, classzone_idx)) {//各个zone都是balanced
		trace_mm_vmscan_kswapd_sleep(pgdat->node_id);

		set_pgdat_percpu_threshold(pgdat, calculate_normal_threshold);

		if (!kthread_should_stop())//没有需要退出thread,则真正的进入睡眠,主动调用schedule调度下一个事件;
			schedule();

		set_pgdat_percpu_threshold(pgdat, calculate_pressure_threshold);
	} else {
		if (remaining)//之前是被唤醒的,则记录vm
			count_vm_event(KSWAPD_LOW_WMARK_HIT_QUICKLY);
		else
			count_vm_event(KSWAPD_HIGH_WMARK_HIT_QUICKLY);
	}
	finish_wait(&pgdat->kswapd_wait, &wait);//将wait从kswapd_wait queue中移除,并将当前状态配置为running,额 睡醒了
}

这里充分的演示了如何进入睡眠的整个操作:

  1. prepare_to_wait(&pgdat->kswapd_wait, &wait, TASK_INTERRUPTIBLE)
  2. if (!kthread_should_stop()) schedule()
  3. finish_wait(&pgdat->kswapd_wait, &wait)
2.2.2 prepare_kswapd_sleep

本接口功能即判断是否各个zone都属于balanced,如果属于则可以sleep,意味着做好了入睡的准备

static bool prepare_kswapd_sleep(pg_data_t *pgdat, int order, int classzone_idx)
{
	int i;

	if (waitqueue_active(&pgdat->pfmemalloc_wait))//判断performance中有无等待task,有的话唤醒
		wake_up_all(&pgdat->pfmemalloc_wait);
	for (i = 0; i <= classzone_idx; i++) {//遍历每个zone
		struct zone *zone = pgdat->node_zones + i;
		if (!managed_zone(zone))
			continue;
		if (!zone_balanced(zone, order, classzone_idx))//是否balanced
			return false;
	}
	return true;
}
//第一层遍历每一个zone,判断是否balanced
static bool zone_balanced(struct zone *zone, int order, int classzone_idx)
{
	unsigned long mark = high_wmark_pages(zone);//注意这个mark是watermark[high]

	if (!zone_watermark_ok_safe(zone, order, mark, classzone_idx))//判断是否ok,这里就是判断是否高于watermark[high]
		return false;

	clear_bit(PGDAT_CONGESTED, &zone->zone_pgdat->flags);
	clear_bit(PGDAT_DIRTY, &zone->zone_pgdat->flags);

	return true;
}
//第二层获取到了mark的值,又套了一层
bool zone_watermark_ok_safe(struct zone *z, unsigned int order,
			unsigned long mark, int classzone_idx)
{
	long free_pages = zone_page_state(z, NR_FREE_PAGES);

	if (z->percpu_drift_mark && free_pages < z->percpu_drift_mark)
		free_pages = zone_page_state_snapshot(z, NR_FREE_PAGES);//获取freepage数量

	return __zone_watermark_ok(z, order, mark, classzone_idx, 0, free_pages);//又套了一层,终于进来了
}
//第三层获取到freepage的值,又套了一层

bool __zone_watermark_ok(struct zone *z, unsigned int order, unsigned long mark,
			 int classzone_idx, unsigned int alloc_flags,
			 long free_pages)
{
	long min = mark;
	int o;
	const bool alloc_harder = (alloc_flags & ALLOC_HARDER);//我们是否需要更harder的申请内存呢?

	free_pages -= (1 << order) - 1;//去除申请order的pages

	if (alloc_flags & ALLOC_HIGH)//对于highmem,不需要保留那么多,去除一半;
		min -= min / 2;

	if (likely(!alloc_harder))//我们还没到那么艰难,则在free中去除rserved数量
		free_pages -= z->nr_reserved_highatomic;
	else //假如我们已经很艰难了,那么保留的值再减少1/4吧,这里跟计算high的过程挺像;
		min -= min / 4;

#ifdef CONFIG_CMA//CMA部分先不care
	if (!(alloc_flags & ALLOC_CMA))
		free_pages -= zone_page_state(z, NR_FREE_CMA_PAGES);
#endif

	if (free_pages <= min + z->lowmem_reserve[classzone_idx])//free比起min + reserve要小,即不balance
		return false;

	if (!order)//order为0,即申请一个page,而且上边一个判断过去了,说明可以申请到,即balanced
		return true;

	for (o = order; o < MAX_ORDER; o++) {
		struct free_area *area = &z->free_area[o];
		int mt;

		if (!area->nr_free)
			continue;

		if (alloc_harder)
			return true;

		for (mt = 0; mt < MIGRATE_PCPTYPES; mt++) {
			if (!list_empty(&area->free_list[mt]))
				return true;
		}

#ifdef CONFIG_CMA
		if ((alloc_flags & ALLOC_CMA) &&
		    !list_empty(&area->free_list[MIGRATE_CMA])) {
			return true;
		}
#endif
	}
	return false;
}
//第四层终于看到了实际的判断逻辑,其实就是watermark + reserve这些,另外需要考虑迁移策略;

本部分用一句话描述就是,判断各个zone的内存是否够用;

2.3 kswapd回收

image-20220329212822363

这里是kswapd被唤醒后的处理,被唤醒的话,那就要进行kswapd实际回收操作了:

static int balance_pgdat(pg_data_t *pgdat, int order, int classzone_idx)
{
	int i;
	unsigned long nr_soft_reclaimed;
	unsigned long nr_soft_scanned;
	struct zone *zone;
	struct scan_control sc = {
		.gfp_mask = GFP_KERNEL,
		.order = order,
		.priority = DEF_PRIORITY,
		.may_writepage = !laptop_mode,
		.may_unmap = 1,
		.may_swap = 1,
	};
	count_vm_event(PAGEOUTRUN);

	do {
		bool raise_priority = true; //默认将raise_priority配置为true

		sc.nr_reclaimed = 0;
		sc.reclaim_idx = classzone_idx;

		if (buffer_heads_over_limit) {
            //这里是判断buffer_head的limit,如果是buffer_head内存较多的话优先从highmem进行,这里标记了进行回收的zone
			for (i = MAX_NR_ZONES - 1; i >= 0; i--) {//按照high -- normal  -- dma的顺序进行
				zone = pgdat->node_zones + i;
				if (!managed_zone(zone))//如果没有managed mem则直接进行下一个zone
					continue;

				sc.reclaim_idx = i;
				break;
			}
		}

		for (i = classzone_idx; i >= 0; i--) {//
			zone = pgdat->node_zones + i;
			if (!managed_zone(zone))
				continue;

			if (zone_balanced(zone, sc.order, classzone_idx))//如果该zone满足分配order后仍属于balanced的情况,则说明分配OK,直接跳出;
				goto out;
		}

		age_active_anon(pgdat, &sc);//走到这里说明将各个zone都判断过之后,回收内存仍不够用,所以对anon 进行老化处理;

	
		if (sc.priority < DEF_PRIORITY - 2 || !pgdat_reclaimable(pgdat))
			sc.may_writepage = 1;
		//优先级高于10还没有搞到足够内存的时候,需要打开writepage
		//pgdat_reclaimable 这里是做个判断,当前scan的pages数量是否足够多,如果够多还没有out出去的话,加码

		/* Call soft limit reclaim before calling shrink_node. */
		sc.nr_scanned = 0;
		nr_soft_scanned = 0;
		nr_soft_reclaimed = mem_cgroup_soft_limit_reclaim(pgdat, sc.order, sc.gfp_mask, &nr_soft_scanned);//先进行一次 mem_cgroup的回收
		sc.nr_reclaimed += nr_soft_reclaimed;

		if (kswapd_shrink_node(pgdat, &sc))//关键部分,进行shrink_node回收
			raise_priority = false;


		if (waitqueue_active(&pgdat->pfmemalloc_wait) && pfmemalloc_watermark_ok(pgdat))//唤醒performance malloc申请
			wake_up_all(&pgdat->pfmemalloc_wait);

		if (try_to_freeze() || kthread_should_stop())//suspend或者退出的话,这里直接跳出去;
			break;

		if (raise_priority || !sc.nr_reclaimed)//没回收够,则priority--
			sc.priority--;
	} while (sc.priority >= 1);

out:

	return sc.order;//返回回收的order数量
}

暂时只能这样了,搞这个的时候突然来了个紧急的活儿

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值