一次cfs组调度不公平引起的负载不均衡分析及cfs组调度深入探索(三)

本文围绕Linux CFS Group展开,阐述复现程序触发问题的逻辑。先介绍创建任务组、设置任务到任务组、设置睡眠任务cpuset的流程,指出设置cpuset时睡眠任务负载不能正确衰减的问题,影响任务组se权重计算。最后说明patch修复逻辑,使任务组load_avg正确衰减。

第一篇中复现程序触发问题逻辑以及 patch-1 的修复逻辑

1 创建任务组

首先按照脚本逻辑创建任务组,接着将睡眠任务设置到对应任务组,最后设置睡眠任务 cpuset时有以下流程逻辑:

首先是:创建任务组,当前创建的是cfs 任务组时将会有如下调用:
alloc_fair_sched_group -------------------------------(1)
	-> init_tg_cfs_entry -----------------------------(2)
    -> init_entity_runnable_average ------------------(3)

(1)alloc_fair_sched_group

int alloc_fair_sched_group(struct task_group *tg, struct task_group *parent)
{
   
   
	struct sched_entity *se;
	struct cfs_rq *cfs_rq;
	int i;

	tg->cfs_rq = kcalloc(nr_cpu_ids, sizeof(cfs_rq), GFP_KERNEL); /* 1 */
	if (!tg->cfs_rq)
		goto err;
	tg->se = kcalloc(nr_cpu_ids, sizeof(se), GFP_KERNEL);		/* 2 */
	if (!tg->se)
		goto err;

	tg->shares = NICE_0_LOAD;									/* 3 */

	init_cfs_bandwidth(tg_cfs_bandwidth(tg));

	for_each_possible_cpu(i) {
   
   									/* 4 */
		cfs_rq = kzalloc_node(sizeof(struct cfs_rq),
				      GFP_KERNEL, cpu_to_node(i));
		if (!cfs_rq)
			goto err;

		se = kzalloc_node(sizeof(struct sched_entity),
				  GFP_KERNEL, cpu_to_node(i));
		if (!se)
			goto err_free_rq;

		init_cfs_rq(cfs_rq);
		init_tg_cfs_entry(tg, cfs_rq, se, i, parent->se[i]);
		init_entity_runnable_average(se);
	}

	return 1;

err_free_rq:
	kfree(cfs_rq);
err:
	return 0;
}

1)2)为一个新的任务的指针数组分配空间,后续每一个 cpu 的 cfs_rq/se 地址都存在这个里面。
3)一个新的任务组对应的 shares 权重默认为 1024。
4)对每一个cpu分配 cfs_rq 和 se,并初始化它们。
(2)init_tg_cfs_entry

void init_tg_cfs_entry(struct task_group *tg, struct cfs_rq *cfs_rq,
			struct sched_entity *se, int cpu,
			struct sched_entity *parent)
{
   
   
	struct rq *rq = cpu_rq(cpu);

	cfs_rq->tg = tg;	// 设置 cfs_rq 对应的任务组
	cfs_rq->rq = rq;	// 对应的 rq
	init_cfs_rq_runtime(cfs_rq);

	tg->cfs_rq[cpu] = cfs_rq;	// 设置任务组的 cfs_rq[cpu]
	tg->se[cpu] = se;			// 设置任务组的 se[cpu]

	/* se could be NULL for root_task_group */
	if (!se)
		return;

	if (!parent) {
   
   
		se->cfs_rq = &rq->cfs;	// 对于根 root_task_group 普通任务的 cfs_rq 就是就绪队列的
		se->depth = 0;			// cfs,并且深度为零。
	} else {
   
   
		se->cfs_rq = parent->my_q;	// 这里可以看到任务组的se->cfs_rq指向自己所属的任务组cfs_rq[cpu]
		se->depth = parent->depth + 1;	// 任务组 se[cpu] 的深度
	}

	se->my_q = cfs_rq;	// 参与挂入红黑树对应的 cfs_rq。
	/* guarantee group entities always have weight */
	update_load_set(&se->load, NICE_0_LOAD); // 设置 se 权重默认为 NICE_0_LOAD,对应任务组将会在后续 update_cfs_group 时根据任务组 shares 和 load_avg 更新权重。
	se->parent = parent;
}

(3)init_entity_runnable_average 为 se 设置新的任务负载,具体逻辑可以之前对 init_entity_runnable_average 函数的描述及分析。
至此当创建一个新的任务组时对应的 cfs_rq/se 的初始化完成。接着开始将任务附加到任务组中。

2 设置一个任务到任务组中

在脚本中有如下示例:
任务组所属层次:

cgroup
    -> zy_test
    	-> sub-1
    		-> test_sub

我们将任务通过写 cgoup.procs 文件设置到 test_sub 任务组中:触发如下逻辑:

cgroup_attach_task
	-> cgroup_migrate_prepare_dst
	-> cgroup_migrate
		-> cgroup_migrate_execute
			-> cpu_cgroup_attach
				-> sched_move_task	-------------------------1-> task_change_group_fair
						-> task_move_group_fair -------------2-> cgroup_migrate_finish

(1)当设置 cgroup.procs 时将会调用 sched_move_task:

void sched_move_task(struct task_struct *tsk)
{
   
   
	int queued, running, queue_flags =
		DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK;
	struct rq_flags rf;
	struct rq *rq;

	rq = task_rq_lock(tsk, &rf);
	update_rq_clock(rq);

	running = task_current(rq, tsk);  ------------------1)
	queued = task_on_rq_queued(tsk);  ------------------2if (queued)
		dequeue_task(rq, tsk, queue_flags);
	if (running)
		put_prev_task(rq, tsk);

	sched_change_group(tsk, TASK_MOVE_GROUP); ----------3----------------------------------------------------4if (queued)
		enqueue_task(rq, tsk, queue_flags);
	if (running)
		set_curr_task(rq, tsk);

	task_rq_unlock
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值