CFS组调度

     /******以下结论和代码分析都是基于最新Linux master分支(Linux5.0)******/

在支持多用户登陆的系统,假如用户A启动了99个进程,用户B只启用了1个进程 (优先级都为NICE 0).  那么按照CFS调度规则,

用户A将获得99%的CPU时间,而用户B只能获取1%的CPU时间,这显然是不合理的.

因此,组调度被引入CFS,用户A和用户B属于不同的调度组,在优先级一样的情况下,两个用户组各占用50%的CPU时间. 而用户A中的99个进程,再根据各自权重,分享这50%的CPU时间.(代码基于最新的Linux 5.0)

 

1. 组调度的创建

用支持组调度,需要打开宏CONFIG_FAIR_GROUP_SCHED和CONFIG_CGROUP.

用struct task_group表示一个组调度

struct task_group {
    struct cgroup_subsys_state css;/* CGROUP相关结构体*/

#ifdef CONFIG_FAIR_GROUP_SCHED
    /* schedulable entities of this group on each CPU */
    struct sched_entity    **se;/*task group的se数组 */
    /* runqueue "owned" by this group on each CPU */
    struct cfs_rq        **cfs_rq;/* task gourp 的cfs数组*/
    unsigned long        shares;/*task group的权重 */

    /*
     * load_avg can be heavily contended at clock tick time, so put
     * it in its own cacheline separated from the fields above which
     * will also be accessed at each tick.
     */
    atomic_long_t        load_avg ;/*task group的 平均负载 */
#endif

};

一个task group 在每个cpu上分配一个SE和CFS.  

组调度的创建函数:
cpu_cgroup_css_alloc->sched_create_group->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;
    /*为每个CPU分配一个CFS_RQ指针 */
    tg->cfs_rq = kcalloc(nr_cpu_ids, sizeof(cfs_rq), GFP_KERNEL);
    if (!tg->cfs_rq)
        goto err;
    /*每个cpu分配一个SE指针 */
    tg->se = kcalloc(nr_cpu_ids, sizeof(se), GFP_KERNEL);
    if (!tg->se)
        goto err;
    /*组调度的权重默认为NICE_0_LOAD */
    tg->shares = NICE_0_LOAD;

    /*为每个CPU分配一个CFS和SE */
    for_each_possible_cpu(i) {
        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;

        /*初始化cfs和se */
        init_cfs_rq(cfs_rq);
        /*建立se的层级关系 */
        init_tg_cfs_entry(tg, cfs_rq, se, i, parent->se[i]);
        init_entity_runnable_average(se);
    }

    return 0;
}

重点看下init_tg_cfg_entry函数
这里的cfs和se 用gcfs和gse来表示组CFS和组se

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);

	/*建立task group与gcfs和gse的关系 */
	cfs_rq->tg = tg;
	cfs_rq->rq = rq;
	init_cfs_rq_runtime(cfs_rq);
	tg->cfs_rq[cpu] = cfs_rq;
	tg->se[cpu] = se;

	if (!parent) {
		se->cfs_rq = &rq->cfs;
		se->depth = 0;/*顶层组调度深度为0 */
	} else {
		se->cfs_rq = parent->my_q;/*gse的cfs_rq指向parent的my_q */
		se->depth = parent->depth + 1;
	}

	se->my_q = cfs_rq;/*gse的CFS rq */
	/* gse的权重默认为NICE_0_LOAD */
	update_load_set(&se->load, NICE_0_LOAD);
	se->parent = parent;
}

1.2 组调度的逻辑图

假设系统有两个CPU, 一个用户组。

CFS组调度_第1张图片

cfs_rq中的nr_running和 h_nr_running 在开启组调度时,也有不同的值

nr_running: cfs_rq红黑数中的se的数量

h_nr_running: cfs_rq红黑树的se数量,再加上子CFS中的红黑树se数量。

如上图nr_running = 4, h_nr_running = 9

 

2. 组进程的调度

2.1 入队列

static struct task_struct *
pick_next_task_fair(struct rq *rq, struct task_struct *prev)
{
	struct cfs_rq *cfs_rq = &rq->cfs; //从最顶层的CFS开始遍历
	struct sched_entity *se;
	struct task_struct *p;
#ifdef CONFIG_FAIR_GROUP_SCHED/*组调度 */
	do {
		struct sched_entity *curr = cfs_rq->curr;
		/*从CFS上选择虚拟时间最小的SE */
		se = pick_next_entity(cfs_rq, curr);
		/*如果SE时Group SE,继续在group的cfs上选择进程 */
		cfs_rq = group_cfs_rq(se);
	} while (cfs_rq);/*直到选者到虚拟时间最小的进程 */
	/* 获取se对应的task struct */
	p = task_of(se);

	if (prev != p) {
		
		struct sched_entity *pse = &prev->se;
		/*这里处理pse和se不在同一个cfs的情况 */
		while (!(cfs_rq = is_same_group(se, pse))) {
			int se_depth = se->depth;
			int pse_depth = pse->depth;
			
			if (se_depth <= pse_depth) {
				put_prev_entity(cfs_rq_of(pse), pse);
				pse = parent_entity(pse);
			}
			if (se_depth >= pse_depth) {
				set_next_entity(cfs_rq_of(se), se);
				se = parent_entity(se);
			}
		}
		/*最后pse和se指向同一个cfs_rq */
		put_prev_entity(cfs_rq, pse);
		set_next_entity(cfs_rq, se);
	}
	/*开启高精度定时器 */
	if (hrtick_enabled(rq))
		hrtick_start_fair(rq, p);

	return p;
}

通过pick函数后,每一级的cfs_rq->curr都指向当前红黑树中的虚拟运行时间最小的SE
比如系统有一个用户组A, se属于group A, 而pse属于系统的cfs.
那么系统的cfs_rq->curr= group se, 而gse->curr = se;
2.2 出队列

组调度的dequeue比较简单,从最底层依次遍历se.

static void dequeue_task_fair(struct rq *rq, struct task_struct *p, int flags)
{
	struct cfs_rq *cfs_rq;
	struct sched_entity *se = &p->se;
	
	/*遍历se */
	for_each_sched_entity(se) {
		cfs_rq = cfs_rq_of(se);
		/*把se移除cfs队列 */
		dequeue_entity(cfs_rq, se, flags);
		
		cfs_rq->h_nr_running--;
		
		/* 如果cfs的weight不为0,证明此cfs队列上面还有其他se,此时group se不能被移除CFS队列 */
		if (cfs_rq->load.weight) {
			/* Avoid re-evaluating load for this entity: */
			se = parent_entity(se);
			break;
		}
		
	}
}

3.用户组的权重与负载计算

CFS调度器根据权重来分配CPU时间,当开启组调度后,需要根据组的权重来分配CPU时间

task group的shares来表示组的权重,初始化为NICE_0_LOAD, 也可以通过接口设置用户组的权重。

3.1 用户组进程时间片计算

                                           se->load.weight             gse->load.weight
    time = sched_period * ------------------------- *       ------------------------
                                           gcfs_rq->load.weight      cfs_rq->load.weight

先计算gcfs_rq的总时间片sched_period,然后计算se在组内的权重比例,再计算组在cfs_rq中的比重

函数sched_slice用于计算se时间片

static u64 sched_slice(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
    u64 slice = __sched_period(cfs_rq->nr_running + !se->on_rq);

/*向上依次遍历se */

    for_each_sched_entity(se) {
        struct load_weight *load;
        struct load_weight lw;

        cfs_rq = cfs_rq_of(se);
        load = &cfs_rq->load;/* 获取cfs的总load*/

        if (unlikely(!se->on_rq)) {
            lw = cfs_rq->load;

            update_load_add(&lw, se->load.weight);
            load = &lw;
        }

/*计算se在cfs中的比例 */
        slice = __calc_delta(slice, se->load.weight, load);
    }
    return slice;
}

3.2 group se权重计算

计算group se的权重,可以用来跟踪task group的平均负载

gse->load.weight =   task_group->shares * (gcfs->load.weight / sum_gcfs->load.weight)

由于cfs_rq->load.weight变化很快,还有与其他CPU进行互斥,会影响系统性能, 在PELT算法中,load_avg的最大值是

load->weight .所以这里以load_avg来近似代替load.weight .

gse->load.weight = task_group->shares * (gcfs->load_avg/ task_group->load_avg)

为了处理group从空闲到运行进程时出现突变,已经group load->weight为0的情况,最终gse 优化计算公式

CFS组调度_第2张图片

函数update_cfs_group中会计算gse的权重.这里不再分析

3.3 task group的负载

为了不影响系统性能,task group的负载不是实时更新的,而是逐步累计到task group ->load_avg

update_load_avg->update_tg_load_avg

static inline void update_tg_load_avg(struct cfs_rq *cfs_rq, int force)
{
	long delta = cfs_rq->avg.load_avg - cfs_rq->tg_load_avg_contrib;

	/*只有当delta大于一定值时,cfs_rq的负载,才会累加到task group */
	if (force || abs(delta) > cfs_rq->tg_load_avg_contrib / 64) {
		atomic_long_add(delta, &cfs_rq->tg->load_avg);
		cfs_rq->tg_load_avg_contrib = cfs_rq->avg.load_avg;
	}
}

 

你可能感兴趣的:(进程管理,CFS,组调度,Linux5.0)