小张学linux内核:五.cfs调度类和rt调度类

今天我们来学习调度类cfs和rt调度类。

cfs调度类

简述
cfs是绝对公平调度算法,理想情况下,优先级相同的两个task,运行时间应该各占cpu的50%,同理3个则cpu利用率为1/3。但是cfs中弱化了优先级的概念而是使用权重weight来决定任务的运行时间。例如:3个任务A,B,C权重分别1,2,3;则总权重,一个调度周期为6单位时间,理想状态下,A应占用1单位,B为2,C为3。
cfs中使用虚拟时间vruntime来决定运行的task,nice值-20到20 --> weight -->vruntime; cfs中使用rbtree来管理调度实体se。每次选取vruntime最小的task进行执行。在rbtree中vruntime最小的se在rbtree的最左侧。cfs是通过限制当前task的运行时间来实现公平的,task的vruntime单调递增,它在rbtree中向右移动,让出cpu使用权给vruntime更小的task。

nice 权重和vruntime的转换。

cfs即普通进程的nice值在-20到19之间。即优先级在100到139之间.0-99是实时进程,采用rt调度类。
nice值和权重weight之间的转化,是通过表格sched_prio_to_weight转换的。
kernel/sched/core.c中

const int sched_prio_to_weight[40] = {
 /* -20 */     88761,     71755,     56483,     46273,     36291,
 /* -15 */     29154,     23254,     18705,     14949,     11916,
 /* -10 */      9548,      7620,      6100,      4904,      3906,
 /*  -5 */      3121,      2501,      1991,      1586,      1277,
 /*   0 */      1024,       820,       655,       526,       423,
 /*   5 */       335,       272,       215,       172,       137,
 /*  10 */       110,        87,        70,        56,        45,
 /*  15 */        36,        29,        23,        18,        15,
};

查表法很快得出nice值对应的weight值。nice值为0的weight为1024。同时nice值为0,所对应的vruntime 虚拟时间是和物理时间是同一个时钟的,不用通过weight进行加权计算,nice值每上升一级,则load值下降20%,则总vruntime则下降10%。
vruntime是由该task当前时间和上次更新时间的插值delta 在加上weight权重加权计算而来的。
周期性调度器 scheduler_tick()中会调用 具体调度类的task_tick函数。
task_tick_failr()
->entity_tick()
->->update_curr() 更新当前时间的vruntime
kernel/sched/fair.c中

static void update_curr(struct cfs_rq *cfs_rq)
{
	struct sched_entity *curr = cfs_rq->curr;
	u64 now = rq_clock_task(rq_of(cfs_rq));        /*获取当前时间*/
	u64 delta_exec;       

	if (unlikely(!curr))
		return;

	delta_exec = now - curr->exec_start;         /*和上次更新的差值*/
	if (unlikely((s64)delta_exec <= 0))
		return;

	curr->exec_start = now;				/*更新 更新vruntime的时间*/

	schedstat_set(curr->statistics.exec_max,
		      max(delta_exec, curr->statistics.exec_max));

	curr->sum_exec_runtime += delta_exec;
	schedstat_add(cfs_rq->exec_clock, delta_exec);

	curr->vruntime += calc_delta_fair(delta_exec, curr);      /*更新当前task的vruntime,档期那vruntime+一个值*/
	update_min_vruntime(cfs_rq);

	if (entity_is_task(curr)) {
		struct task_struct *curtask = task_of(curr);

		trace_sched_stat_runtime(curtask, delta_exec, curr->vruntime);
		cgroup_account_cputime(curtask, delta_exec);
		account_group_exec_runtime(curtask, delta_exec);
	}

	account_cfs_rq_runtime(cfs_rq, delta_exec);
}
	curr->vruntime += calc_delta_fair(delta_exec, curr);     
	 /*更新当前task的vruntime,档期那vruntime+一个值*/

来看calc_delta_fair()函数
当nice为0时 vruntime 就加上delta值,即为两次更新物理时间的差值,但当nice值不为0时,则要加权一下。

static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se)
{
	if (unlikely(se->load.weight != NICE_0_LOAD))
		delta = __calc_delta(delta, NICE_0_LOAD, &se->load);
	return delta;
}

/*
 * delta_exec * weight / lw.weight
 *   OR
 * (delta_exec * (weight * lw->inv_weight)) >> WMULT_SHIFT
 *
 * Either weight := NICE_0_LOAD and lw \e sched_prio_to_wmult[], in which case
 * we're guaranteed shift stays positive because inv_weight is guaranteed to
 * fit 32 bits, and NICE_0_LOAD gives another 10 bits; therefore shift >= 22.
 *
 * Or, weight =< lw.weight (because lw.weight is the runqueue weight), thus
 * weight/lw.weight <= 1, and therefore our shift will also be positive.
 */
static u64 __calc_delta(u64 delta_exec, unsigned long weight, struct load_weight *lw)
{
	u64 fact = scale_load_down(weight);
	int shift = WMULT_SHIFT;

	__update_inv_weight(lw);

	if (unlikely(fact >> 32)) {
		while (fact >> 32) {
			fact >>= 1;
			shift--;
		}
	}

	/* hint to use a 32x32->64 mul */
	fact = (u64)(u32)fact * lw->inv_weight;

	while (fact >> 32) {
		fact >>= 1;
		shift--;
	}

	return mul_u64_u32_shr(delta_exec, fact, shift);
}

delta vt = delta vt * NICE_0_LOAD/load;
由于除法的算法效率很低,所以奖除法转换成乘法和移位操作;转换成公式:
delat vt = (ldelta vt 1024 * 2^32 /load2^32 ) >> 32;
将2^32/load * 2^32依旧做成查表法 inv_load。

1.如何选择下一个task?

pick_next_fair:
选择rbtree上vruntime最小的se来进行调度。

2. 何时更新vruntime?更新在rb tree中的位置

vruntime 是在entity_tick中update_curr中更新。

3.延迟调度

task 至少运行一次的时间间隔。

4. 组调度中vruntime是何设置

  1. 组调度会有自己的cfs_rq,组内的各se的vruntime如何计算?

  2. 调度组对应的se的vruntime是如何计算的?

每个task_group都有一个shares,share并非我们说的进程优先级,而是调度权重,这个是cfs调度管理的概念,但在cfs中最终体现到调度优先排序上。shares值默认都是相同的,所有没有设置权重的值。
entity_tick()
->update_cfs_group()

static void update_cfs_group(struct sched_entity *se)
{
	struct cfs_rq *gcfs_rq = group_cfs_rq(se);
	long shares, runnable;

	if (!gcfs_rq)
		return;

	if (throttled_hierarchy(gcfs_rq))
		return;

#ifndef CONFIG_SMP
	runnable = shares = READ_ONCE(gcfs_rq->tg->shares);

	if (likely(se->load.weight == shares))
		return;
#else
	shares   = calc_group_shares(gcfs_rq);
	runnable = calc_group_runnable(gcfs_rq, shares);
#endif

	reweight_entity(cfs_rq_of(se), se, shares, runnable);
}

调度组的shares即为se的load weight。

总结

休眠唤醒的进程vruntime相对来说比较小,可以尽快被调度。

fork创建的进程vruntime是copy当前进程的,当前进程的,所以fork后父进程被调度出去后排在子进程之后了

rt调度类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值