CFS调度器vruntime和min_vruntime

min_vruntime是CFS红黑树中最小的虚拟时间(运行最少)。其在进程更新虚拟时间(vruntime)时一起被更新

1. 当一个进程进入运行对列或者出队列时,会调用update_curr

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;
	
	curr->exec_start = now;

	/*更新进程累计运行时间 */
	curr->sum_exec_runtime += delta_exec;
	/*根据进程权重计算虚拟运行时间 */
	curr->vruntime += calc_delta_fair(delta_exec, curr);
	/*更新cfs运行队列最小虚拟运行时间 */
        update_min_vruntime(cfs_rq);

}

CFS运行队列min_vruntime更新

static void update_min_vruntime(struct cfs_rq *cfs_rq)
{
	struct sched_entity *curr = cfs_rq->curr;
	struct rb_node *leftmost = rb_first_cached(&cfs_rq->tasks_timeline);

	u64 vruntime = cfs_rq->min_vruntime;

	if (curr) { //#1
		if (curr->on_rq)
			vruntime = curr->vruntime;
		else
			curr = NULL;
	}

	if (leftmost) { /* #2 */
		struct sched_entity *se;
		se = rb_entry(leftmost, struct sched_entity, run_node);

		if (!curr)
			vruntime = se->vruntime;
		else
			vruntime = min_vruntime(vruntime, se->vruntime);
	}
	/*#3*/
		
	/* ensure we never gain time by being placed backwards. */
	cfs_rq->min_vruntime = max_vruntime(cfs_rq->min_vruntime, vruntime);

}

这里vruntime有二种选择

   当前进程curr和最leftmost 最小的虚拟时间,

最后在#3选择最大的值,是因为要保证min_vruntime是单调递增.

2. 在一个进程被fork出来时,vruntime时怎么确定的了?

do_fork->sched_fork->task_fork_fair

static void task_fork_fair(struct task_struct *p)
{
	struct cfs_rq *cfs_rq;
	struct sched_entity *se = &p->se, *curr;
	struct rq *rq = this_rq();
	struct rq_flags rf;

	rq_lock(rq, &rf);
	update_rq_clock(rq);

	cfs_rq = task_cfs_rq(current);
	curr = cfs_rq->curr;
	if (curr) {
		update_curr(cfs_rq);
		se->vruntime = curr->vruntime;/*#1 */
	}
        /* #2*/
	place_entity(cfs_rq, se, 1);

	se->vruntime -= cfs_rq->min_vruntime;/*#3 */
	rq_unlock(rq, &rf);
}

在#1处,把父进程的vruntime赋值给子进程

在#2处: 对新进程的vruntime进行惩罚,也就是增加vruntime的值

在#3处,却要减去CFS的min_vruntime值,这是因为此时新进程还没有跟CPU联系起来,实际在运行时,可能会挂到其他CPU的CFS队列,如果两个cpu的min_vruntime相差比较大,会导致新进程优先级很高/很低,为了解决这中问题,在入新CPU时,会再加上min_vruntime.其实也就是计算了相对排名。新进程的vruntime可以等价为

  vruntime = 父进程vruntime + (cpux_min_vruntime - cpuy_min_vruntime).

3. 睡眠进程唤醒时vruntime的计算

进程被唤醒时,会调用enqueue_task_fair->enqueue_entity->place_entity(cfs_rq, se, 0);

static void
place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial)
{
	u64 vruntime = cfs_rq->min_vruntime;

	if (initial && sched_feat(START_DEBIT))/*#1 */
		vruntime += sched_vslice(cfs_rq, se);

	if (!initial) {/*#2 */
	unsigned long thresh = sysctl_sched_latency;//6ms
	if (sched_feat(GENTLE_FAIR_SLEEPERS))
			thresh >>= 1;

	  vruntime -= thresh;
	}

	/* #3 */
	se->vruntime = max_vruntime(se->vruntime, vruntime);
}

 

#1:针对刚创建的进程,唤醒进程时这里initial=0

#2: 给被唤醒进程一定的奖励,也就是减少vruntime的值(毕竟sleep了段时间,加个鸡腿)

#3 :被唤醒进程vruntime 为cfs和se最大的vruntime,这么做有两个好处:

         1. 假如进程sleep了很长时间,那么意味着se->vruntime很久没更新了,肯定是个比较小的值。直接使用,会打破CFS 调度公平。所有se->vruntime = CFS->min_vruntime

        2. 假如一个进程在sleep时,运行了很长时间vruntime比较大,且休眠了很短时间,vruntime> cfs->min_vrtumtime.这样可以防止进程的vruntime出现后退的现象(vruntime只能单调递增)

 

4.新进程的唤醒

do_fork->wake_up_new_task/* 选者cpu逻辑,未完待续*/

wake_up_new_task函数会为新进程选者合适的cpu,并加入对应的runqueue

5. CFS 运行队列时间片

 一个CFS队列,所有处于runable状态的进程,有个总的运行时间。也叫做调度延时。

调度延迟就是让每个可运行状态的进程都运行一次的时间间隔,Linux下的计算函数

sched_nr_latency:值为8,如果CFS中的进程只有8个,那么调度延迟为sysctl_sched_latency(6ms)

如果大于,则调度延迟=nr_running * sysctl_sched_min_granularity(0.75ms),保证每个进程至少运行0.75ms

static u64 __sched_period(unsigned long nr_running)
{
    if (unlikely(nr_running > sched_nr_latency))
        return nr_running * sysctl_sched_min_granularity;
    else
        return sysctl_sched_latency;
}
 4.1 进程时间片计算

 进程时间片 = cfs_total_slice* (w/cfs_load),可以看出进程优先级越高,时间片越多。

static u64 sched_slice(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
	/* 计算CFS总的时间片*/
       u64 slice = __sched_period(cfs_rq->nr_running + !se->on_rq);

	/*根据进程权重,计算进程时间片 */
        slice = __calc_delta(slice, se->load.weight, load);
	
	return slice;
}

4.2 进程虚拟时间片计算

   vslice = (NICE_0_LOAD*slice)/se->load.weight=(NICE_0_LOAD/se->load.weight)*(se->load.weight*slice)/cfs->load

       = (NICE_0_LOAD)*(cfs_slice_total)/cfs->load

 也就是说,在一个CFS的运行队列中所有进程的虚拟时间片是相等的,这就是CFS完全公平调度的体现:

最终保证所有进程的虚拟运行时间相等。每次调度都选者虚拟运行时间小的进程.(进程优先级越高,真实运行时间越长)

static u64 sched_vslice(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
	return calc_delta_fair(sched_slice(cfs_rq, se), se);
}

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值