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