进程调度子系统(3)完全公平调度类CFS

时间:2023-02-05 14:38:20

1. 完全公平调度类

最重要的就是维护一颗红黑树,对普通进程排队

/kernel/sched_fair.c

1802 static const struct sched_class fair_sched_class = {
1803 .next = &idle_sched_class,
1804 .enqueue_task = enqueue_task_fair,
1805 .dequeue_task = dequeue_task_fair,
1806 .yield_task = yield_task_fair,
1807
1808 .check_preempt_curr = check_preempt_wakeup,
1809
1810 .pick_next_task = pick_next_task_fair,
1811 .put_prev_task = put_prev_task_fair,
1812
1813 #ifdef CONFIG_SMP
1814 .select_task_rq = select_task_rq_fair,
1815
1816 .load_balance = load_balance_fair,
1817 .move_one_task = move_one_task_fair,
1818 #endif
1819
1820 .set_curr_task = set_curr_task_fair,
1821 .task_tick = task_tick_fair,
1822 .task_new = task_new_fair,
1823
1824 .prio_changed = prio_changed_fair,
1825 .switched_to = switched_to_fair,
1826
1827 #ifdef CONFIG_FAIR_GROUP_SCHED
1828 .moved_group = moved_group_fair,
1829 #endif
1830 };

2.CFS就绪队列(红黑树)

前面提到,rq中有相应调度类对应的子就绪队列cfs_rq

kernel/sched.c

419 /* CFS-related fields in a runqueue */
420 struct cfs_rq {
421 struct load_weight load; //队列的权重
422 unsigned long nr_running; //队列的可运行进程数
423
424 u64 exec_clock;
425 u64 min_vruntime; //所有进程的最小虚拟运行时间(用于红黑树排序,单调递增(可能比红黑树最左节点要小))
426
427 struct rb_root tasks_timeline; //红黑树的根节点
428 struct rb_node *rb_leftmost; //红黑树的最左节点(vrntime最小,即需要被调度的进程)
429
430 struct list_head tasks;
431 struct list_head *balance_iterator;
432
433 /*
434 * 'curr' points to currently running entity on this cfs_rq.
435 * It is set to NULL otherwise (i.e when none are currently running).
436 */
437 struct sched_entity *curr, *next, *last; //CFS队列的可调度实体
438
439 unsigned int nr_spread_over;
440
441 #ifdef CONFIG_FAIR_GROUP_SCHED
...
454
455 #ifdef CONFIG_SMP
...
478 #endif
479 #endif
480 };

3.CFS的操作

3.1 虚拟时间的计算

为了实现完全公平调度,内核引入了虚拟时钟(virtualclock)的概念,实际上我觉得这个虚拟时钟为什叫虚拟的,是因为这个时钟与具体的时钟晶振没有关系,他只不过是为了公平分配CPU时间而提出的一种时间量度,它与进程的权重有关,这里就知道权重的作用了,权重越高,说明进程的优先级比较高,进而该进程虚拟时钟增长的慢。

我们再回忆一下调度实体struct sched_entity结构体中的变量:

1095         u64                     exec_start;
1096 u64 sum_exec_runtime;
1097 u64 vruntime;
1098 u64 prev_sum_exec_runtime;

sum_exec_runtime是用于记录该进程的CPU消耗时间,这个是真实的CPU消耗时间。在进程本次执行完时会将sum_exec_runtime保存到prev_sum_exec_runtime中。vruntime是本进程生命周期中在CPU上运行的虚拟时钟。那么何时应该更新这些时间呢?这是通过调用update_curr实现的,该函数在多处调用。我们还记得周期调度器函数scheduler_tick会掉用调度器类的task_tick吧,而完全公平调度器类的实例fair_sched_class中task_tick指向的是task_tick_fair,我们看一下task_tick_fair的实现:

kernel/sched_fair.c

1694 /*
1695 * scheduler tick hitting a task of our scheduling class:
1696 */
1697 static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
1698 {
1699 struct cfs_rq *cfs_rq;
1700 struct sched_entity *se = &curr->se;
1701
1702 for_each_sched_entity(se) {//遍历调度实体的每个成员,但进程的实体即为进程
1703 cfs_rq = cfs_rq_of(se);
1704 entity_tick(cfs_rq, se, queued);//更新实体(进程)的时间
1705 }
1706 }

主要看entity_tick函数,实现如下:

          //1.update 更新进程运行 统计量
//2.检查是否有进程可以抢占当前进程
865 static void
866 entity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, int queued)
867 {
868 /*
869 * Update run-time statistics of the 'current'.
870 */
871 update_curr(cfs_rq); //更新当前进程的运行时间统计量
872
873 #ifdef CONFIG_SCHED_HRTICK
...
888 #endif
889
890 if (cfs_rq->nr_running > 1 || !sched_feat(WAKEUP_PREEMPT)) //可运行的进程多于1个 或 允许唤醒抢占
891 check_preempt_tick(cfs_rq, curr); //检查就绪队列中是否有进程可以抢占当前进程(当前进程运行时间超过了它可以得到的cpu份额的时候(超期运行)选择下一个进程抢占)
892 }

接下来看一下update_curr和cheeck_preempt_tick的实现:

所有与进程虚拟时钟有关的计算都在update_curr中执行,该函数在系统中不同的地方被调用

    //更新进程的运行时间统计量

//1.确定就绪队列的当前运行进程
//2.获取主调度器就绪队列的实际时钟值
//3.内核计算的当前和上次更新负荷统计量的时间差
//4.更新当前进程在cpu上执行花费的物理时间和虚拟时间

336 static void update_curr(struct cfs_rq *cfs_rq)
337 {
338 struct sched_entity *curr = cfs_rq->curr;//1.当前调度实体
339 u64 now = rq_of(cfs_rq)->clock; //2.当前cpu的clock
340 unsigned long delta_exec;
341 //如果当前CFS就绪队列上没有进程运行,无事可做;否则,否则内核计算当前和上一次更新负荷统计量时的时间差
342 if (unlikely(!curr))
343 return;
344
345 /*
346 * Get the amount of time the current task was running
347 * since the last time we changed load (this cannot
348 * overflow on 32 bits):
349 */
//3.当前和上次更新负荷统计量的时间差
//即计算当前进程的执行时间
350 delta_exec = (unsigned long)(now - curr->exec_start);
351

//4.更新当前进程在cpu上执行花费的时间(对运行时间进行加权计算)
352 __update_curr(cfs_rq, curr, delta_exec);
353 curr->exec_start = now; //now作为当前更新负荷统计量的时间
354
355 if (entity_is_task(curr)) {/*下面为关于组调度的,暂时不分析了*/
356 struct task_struct *curtask = task_of(curr);
357
358 cpuacct_charge(curtask, delta_exec);
359 }
360 }

300 /*
301 * 1.更新进程的物理运行时间
302 * 2.更新进程的虚拟运行时间
* 3.更新CFS队列的min_vruntime(注意正在运行进程 和 红黑树中最左的进程)
303 */
304 static inline void
305 __update_curr(struct cfs_rq *cfs_rq, struct sched_entity *curr,
306 unsigned long delta_exec)
307 {
308 unsigned long delta_exec_weighted;
309 u64 vruntime;
310
311 schedstat_set(curr->exec_max, max((u64)delta_exec, curr->exec_max));
312 //1.更新进程的总物理运行时间(clock计数)
//当前进程总的运行时间 += 上次进程总的运行时间(sum_exec_runtime) + 进程本次运行的时间
313 curr->sum_exec_runtime += delta_exec;
//更新cfs_rq的exec_clock += delta_ecec
314 schedstat_add(cfs_rq, exec_clock, delta_exec);
//2.更新虚拟运行时间
//用优先级和delta_exec来计算weighted以用于更细vruntime
315 delta_exec_weighted = delta_exec;
316 if (unlikely(curr->load.weight != NICE_0_LOAD)) {//nice = 0 的进程,物理运行时间和虚拟运行时间相当
317 //非nice = 0 的进程,更具优先级权重,重新计算虚拟运行时间(自上次运行以来)
delta_exec_weighted = calc_delta_fair(delta_exec_weighted,
318 &curr->load);
319 }
//最后得到的进程总的虚拟运行时间
320 curr->vruntime += delta_exec_weighted;
//vruntime可以准确地测量给定进程的运行时间而且可知道谁应该是下一个被运行的进程

321 //3.更新CFS的min_vruntime(这个值是就绪队列红黑树排序的基准时间,主要用于唤醒进程的vruntime的确定)
322 /*
323 * 跟踪红黑树中最左边的节点,维护Min_vruntime的单调地曾性
324 *
325 */
326 if (first_fair(cfs_rq)) {//如果有最左节点,即有进程在树上等待调度
//获取等待进程的vruntime(这是当前就绪队列中所有节点最小的vruntime值)
327 vruntime = min_vruntime(curr->vruntime,
328 __pick_next_entity(cfs_rq)->vruntime);
329 } else //如果树是空的而没有最左节点,则使用当前进程的虚拟运行时间
330 vruntime = curr->vruntime;
331 //设置Min_vruntime是 max(当前树中最小的节点vruntime,当前正在运行的进程的vruntime),这样保证min_vruntime单调增
//因为红黑树中最左的节点的vruntime是当前树中排队进程中的最小值,而当前正在cpu上运行进程的vrunrime是上次树中的最小值(执行一段时间后可能会变得)
332 cfs_rq->min_vruntime =
333 max_vruntime(cfs_rq->min_vruntime, vruntime);
334 }


   /* 进程虚拟运行时间的放缩
*本质上执行的就是:
*   delta *= NICE_0_LOAD / se.load 更具nice = 0 的权重和当前进程权重对虚拟运行时间放缩
*/
397 static inline unsigned long
398 calc_delta_fair(unsigned long delta, struct sched_entity *se)
399 {
400 if (unlikely(se->load.weight != NICE_0_LOAD))//以NICE_0_LOAD为基准
//依据进程(实体)的权重 修正虚拟执行时间
401 delta = calc_delta_mine(delta, NICE_0_LOAD, &se->load);//计算本次执行时间(放缩)
402
403 return delta;
404 }
calc_delta_mine返回值是delta_exec*(NICE_0_LOAD/curr->load.weight),可见该进程的权重越大,delta_exec*(NICE_0_LOAD/curr->load.weight)就越小,这样vruntime增长的就越小。

越小有什么用的,那么我们需要看一下红黑树是如何组织的。红黑树的键值是通过entity_key获得的,该函数的实现如下:

275 static inline s64 entity_key(struct cfs_rq *cfs_rq, struct sched_entity *se)
276 {
277 return se->vruntime - cfs_rq->min_vruntime;
278 }

cfs_rq->min_vruntime是为了跟踪红黑树中最左面节点的时钟,但它必须是单调递增的,以防止时钟倒流。之所以用个减法来实现,主要考虑到vruntime是进程获得执行的虚拟时间的长度,也就是说

 ( 1)进程睡眠的时候,这个虚拟时间的长度是不会增长的,而min_vruntime是整个就绪队列的最小虚拟时间基准,是一直会增长的。当一个进程睡眠一段时间后,通过上面的算法计算出来的key就是变小,从而醒来后更靠近红黑树的左边,更容易得到调度。

 (2) 进程运行的时候,vruntime会一直增加,他在红黑树中的位置就会向右移动

3.2 调度延迟
Linux是支持调度延迟的,这样可以保证可运行的进程都应该至少运行一次。它与运行队列上的进程数目有关,进程数越多,该时间延迟越长,所有的进程根据权重平分该时间,权重越大,分的的时间越长。那我们看一下时间延迟周期是如何增长的:

264 static u64 sched_slice(struct cfs_rq *cfs_rq, struct sched_entity *se)
265 {
266 u64 slice = __sched_period(cfs_rq->nr_running);
267
268 slice *= se->load.weight;
269 do_div(slice, cfs_rq->load.weight);
270
271 return slice;
272 }

 //确定延迟周期的长度(物理时间)

414 static u64 __sched_period(unsigned long nr_running)
415 {
416 u64 period = sysctl_sched_latency;//默认是20ms
417 unsigned long nr_latency = sched_nr_latency;//默认延迟周期可运行的进程数 = 5
418 //当前可运行的多余默认
419 if (unlikely(nr_running > nr_latency)) {
420 period = sysctl_sched_min_granularity;//本调度波此中单进程获得的最小执行时间4ms = 20/5
421 period *= nr_running;
422 }
423
424 return period;
425 }

   从这个函数我们可以看出这个时间延迟周期就是一调度运行的时间,sched_nr_latency是sysctl_sched_latency这么长时间里应该处理的进程数目,这两值是可配置的(sysctl_sched_min_granularity*nr_running)。可见nr_running越大,即当前队列的进程数越多,进程的调度延迟越长。那么一个进程如何获得它的执行时间呢?
slice = calc_delta_mine(slice, se->load.weight, load);
实际做的就是slice *= se->load.weight /cfs->load.weight  。
当然这个是按实际时间给出的,有时候也要知道等价的虚拟时间
//实际就是 vslice *= NICE_0_LOAD/rq_weight

279 static u64 __sched_vslice(unsigned long rq_weight, unsigned long nr_running)
280 {
281 u64 vslice = __sched_period(nr_running);//计算调度延迟
282
283 vslice *= NICE_0_LOAD;
284 do_div(vslice, rq_weight);
285
286 return vslice;
287 }
288
289 static u64 sched_vslice(struct cfs_rq *cfs_rq)
290 {
291 return __sched_vslice(cfs_rq->load.weight, cfs_rq->nr_running);//当前进程的权重 和 的当前的可运行进程数
292 }
293
294 static u64 sched_vslice_add(struct cfs_rq *cfs_rq, struct sched_entity *se)
295 {
296 return __sched_vslice(cfs_rq->load.weight + se->load.weight,
297 cfs_rq->nr_running + 1);//cfs的权重 和 的当前的可运行进程数
298 }
可以看出sched_vslice_add的返回值就是(sysctl_sched_latency*(NICE_0_LOAD/(cfs_rq->load.weight +se->load.weight))),可以看到这里的计算也是用NICE_0_LOAD作为是一个权重的分界点。优先级在它之前的权重更重一些,这样延迟就会更小。
3.3 队列操作
3.3.1 入队

     //可运行进程数增加时调用
//CFS队列,要入队的进程,唤醒标记
959 static void enqueue_task_fair(struct rq *rq, struct task_struct *p, int wakeup)
960 {
961 struct cfs_rq *cfs_rq;
962 struct sched_entity *se = &p->se;//要入队的进程对应的可调度实体
963 //对于主调度器,会对一个组中的所有进程进行操作
964 for_each_sched_entity(se) {//遍历每个进程(当进程实体只有一个)
965 if (se->on_rq) //若该进程已经入队则返回
966 break;
967 cfs_rq = cfs_rq_of(se); //CFS就绪队列
968 enqueue_entity(cfs_rq, se, wakeup);//否则当前进程未在就绪队列,入队
969 wakeup = 1;//表示是被唤醒的进程(新加入系统的进程wakeup为0,此后都为1)
970 }
971
972 hrtick_update(rq);
973 }


//1.更新负荷统计量
//2.进程加入就绪队列

524 static void
525 enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int wakeup)
526 {
527 /*
528 * Update run-time statistics of the 'current'.
529 */
//1.更新负荷统计量
530 update_curr(cfs_rq);
531

//wakeup = 1 ,刚被唤醒的进程(虚拟时间无效)
532 if (wakeup) {
533 place_entity(cfs_rq, se, 0);
534 enqueue_sleeper(cfs_rq, se);
535 }
536

//wakeup = 0 ,最近运行过的进程(虚拟时间还有效)


//设置当前实体的等待被调度的开始时间se->wait_start = cfs_rq->clock

537 update_stats_enqueue(cfs_rq, se);
//根据配置信息 和 vruntime 可能会对cfs_rq->nr_spread_over++
538 check_spread(cfs_rq, se);
539 if (se != cfs_rq->curr) //该进程(实体)不是当前正在运行的进程
540 __enqueue_entity(cfs_rq, se);//该进程入队,重排红黑树
541 account_entity_enqueue(cfs_rq, se);
542 }



//进程加入就绪队列的红黑树操作
//1.找到位置
//2.加入
//3.调整树平衡

146 static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
147 {
148 struct rb_node **link = &cfs_rq->tasks_timeline.rb_node; //红黑树的根节点
149 struct rb_node *parent = NULL;
150 struct sched_entity *entry;
151 s64 key = entity_key(cfs_rq, se); //vruntime转换的键值
152 int leftmost = 1;
153
154 /*
155 * Find the right place in the rbtree:
156 */
//1.找到加入的位置
157 while (*link) {//遍历红黑树,左子树或右子树为NULL的时候退出循环
158 parent = *link;
//container_of机制,得到红黑树节点代表的可调度实体结构
159 entry = rb_entry(parent, struct sched_entity, run_node);
160 /*
161 * We dont care about collisions. Nodes with
162 * the same key stay together.
163 */
//红黑树按照vruntime转换的键值来组织
164 if (key < entity_key(cfs_rq, entry)) {
//如果当前入队的实体的键值(vruntime)比父节点的小,则在左子树查找插入位置
165 link = &parent->rb_left;
166 } else {
//在右子树找插入位置
167 link = &parent->rb_right;
168 leftmost = 0;
169 }
170 }
171
172 /*
173 * Maintain a cache of leftmost tree entries (it is frequently
174 * used):
175 */
//leftmost = 1,表示有该实体对应的节点是最左节点;lefmost = 0,表示该实体不是最左节点
176 if (leftmost)
177 cfs_rq->rb_leftmost = &se->run_node;
//2.加入节点
178 //link的位置就是该节点要插入的位置
179 rb_link_node(&se->run_node, parent, link);//link = NULL
//3.调整红黑树
//加入节点后红黑树的调整(平衡二叉树)
180 rb_insert_color(&se->run_node, &cfs_rq->tasks_timeline);
181 }



//对于被唤醒的进程要调整其先前的虚拟运行时间
676 static void677 place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial)
678 {
679 u64 vruntime = cfs_rq->min_vruntime;//当前就绪队列(红黑树的最小虚拟时间)
680 //initial = 1,表示新进入系统的新进程
687 if (initial && sched_feat(START_DEBIT))
688 vruntime += sched_vslice(cfs_rq, se); //新创建进程的初始虚拟运行时间(依据权重和调度延迟计算)
689 //initial = 0,表示是被唤醒的进程
690 if (!initial) {
691 /* sleeps upto a single latency don't count. */
692 if (sched_feat(NEW_FAIR_SLEEPERS)) {
//被唤醒的长睡眠时间进程获得的补偿
693 unsigned long thresh = sysctl_sched_latency;//当前延迟周期(之内的所有活动进程至少运行一次)
//普通睡眠进程
701 if (sched_feat(NORMALIZED_SLEEPER) &&
702 (!entity_is_task(se) ||
703 task_of(se)->policy != SCHED_IDLE))
//补偿时间转换为vruntime
704 thresh = calc_delta_fair(thresh, se);
705 //被唤醒的进程获得的虚拟运行时间
706 vruntime -= thresh;//保证该进程在当前延迟周期结束后才会被调度运行
707 }
708
709 /* ensure we never gain time by being placed backwards. */
//确保短睡眠进程(vruntime > min_runtime)不会获得额外的运行时间
710 vruntime = max_vruntime(se->vruntime, vruntime);
711 }
712
713 se->vruntime = vruntime;
714 }

enqueue_task_fair调用place_entity传递的initial参数为0,所以会执行if(!initial)后的语句。
当initial =0 时,表示唤醒进程的虚拟运行时间补偿过程
我们设想一个任务睡眠了,vruntime就不会增加了,当它醒来后不知道过了多长时间。如果进程睡眠已经累积了较大的不公平值,即se.vruntime远小于min_vruntime(可能比延迟周期内的所有进程都要小得多),如果只是简单的将其插入到就绪队列中。它将拼命追赶min_vruntime,因为它总是在红黑树的最左面。如果这样,它将会占用大量的CPU时间,导致红黑树右边的进程被饿死,这显然是不合理的。所以这要对唤醒任务的vruntime进行一些调整,我们可以看到,这里是用min_vruntime减去一个thresh,
这个thresh的计算过程就是将sysctl_sched_latency换算成进程的vruntime,而这个sysctl_sched_latency就是默认的调度周期(20ms)。之所以要减去一个值是为了对睡眠进程做一个补偿,能让它醒来时可以快速的到CPU。同时还要注意那句注释 ensure we never gain time by being placed backwards,本来这里是给因为长时间睡眠而vruntime远远小于min_vruntime的进程补偿的,但是有些进程只睡眠很短时间,这样在它醒来后vruntime还是大于min_vruntime,不能让进程通过睡眠获得额外的运行时间,所以最后选择计算出的补偿时间与进程原本vruntime中的较大者。
但是有一个问题,为什么计算thresh要用整个调度周期换算成vruntime?
感觉应该用(调度周期 * 进程权重 / 所有进程总权重)再换算成vruntime才合理阿,用整个调度周期是不是补偿太多了?
当initial = 1 时,表示计算新进程的初始虚拟运行时间
这里并不是计算进程应该运行的时间,而是先把进程的已经运行时间设为一个较大的值,但是该进程明明还没有运行过啊,为什么要这样做呢?
假设新进程都能获得最小的vruntime(min_vruntime),那么新进程会第一个被调度运行,这样程序员就能通过不断的fork新进程来让自己的程序一直占据CPU,这显然是不合理的,这跟以前使用时间片的内核中父子进程要平分父进程的时间片是一个道理。
再解释下min_vruntime,这是每个cfs队列一个的变量,它一般小于等于所有就绪态进程的最小vruntime,也有例外,比如对睡眠进程进行时间补偿会导致vruntime小于min_vruntime。

至于sched_vslice计算,大体上说就是:
sched_vslice = (调度周期 * 进程权重 / 所有进程总权重) * NICE_0_LOAD / 进程权重
也就是算出进程应分配的实际cpu时间,再把它转化为vruntime。把这个vruntime加在进程上之后,就相当于认为新进程在这一轮调度中已经运行过了。


3.3.2 选择下一个可运行进程
     //1.选择下一个进程
//2.标记为可运行

3597 static struct task_struct *pick_next_task_fair(struct rq *rq)
3598 {
3599 struct task_struct *p;
3600 struct cfs_rq *cfs_rq = &rq->cfs;
3601 struct sched_entity *se;
3602
3603 if (!cfs_rq->nr_running) //没有进程可运行
3604 return NULL;
3605
3606 do {
3607 se = pick_next_entity(cfs_rq); //选择下一个实体
3608 set_next_entity(cfs_rq, se); //设置下一个进程为运行进程
3609 cfs_rq = group_cfs_rq(se);
3610 } while (cfs_rq);
3611
3612 p = task_of(se);
3613 if (hrtick_enabled(rq))
3614 hrtick_start_fair(rq, p);
3615
3616 return p;
3617 }
//1.
/*优先考虑last,其次secod,最后才是left(若leftmost为skip则选second)*/

1897 static struct sched_entity *pick_next_entity(struct cfs_rq *cfs_rq)
1898 {
1899 struct sched_entity *se = __pick_first_entity(cfs_rq);//从缓存leftmost中取最左边的进程
1900 struct sched_entity *left = se;
1901
1902 /*
1903 * Avoid running the skip buddy, if running something else can
1904 * be done without getting too unfair.
1905 */
//如果leftmost需要被跳过执行,那从红黑树中取出第二个shced__entity实例
//wakeup 是唤醒抢占的判断
1906 if (cfs_rq->skip == se) {
1907 struct sched_entity *second = __pick_next_entity(se);
1908 if (second && wakeup_preempt_entity(second, left) < 1)
1909 se = second;//达不到抢占粒度,下一个运行的是second进程
1910 }
1911 //这里是优先照顾next和last进程,只有当__pick_next_entity选出来的进程
//的vruntime比next和last都小超过调度粒度时才轮到它运行,否则就是next或者last

1912 /*
1913 * Prefer last buddy, try to return the CPU to a preempted task.
1914 */
1915 if (cfs_rq->last && wakeup_preempt_entity(cfs_rq->last, left) < 1)
1916 se = cfs_rq->last;
1917
1918 /*
1919 * Someone really wants this to run. If it's not unfair, run it.
1920 */
1921 if (cfs_rq->next && wakeup_preempt_entity(cfs_rq->next, left) < 1)
1922 se = cfs_rq->next;
1923
1924 clear_buddies(cfs_rq, se);
1925
1926 return se;
1927 }

/*是否可以抢占的判断 返回1 可以抢占*/

3473 wakeup_preempt_entity(struct sched_entity *curr, struct sched_entity *se)
3474 {
3475 s64 gran, vdiff = curr->vruntime - se->vruntime;
3476
3477 if (vdiff <= 0)
3478 return -1;
3479 //调度粒度 gan实际lms转换的虚拟时间
3480 gran = wakeup_gran(curr, se);
3481 if (vdiff > gran)
3482 return 1;
3483
3484 return 0;
3485 }


// 2.

1858 set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
1859 {
1860 /* 'current' is not kept within the tree. */
//这里什么情况下条件会为假?我以为刚唤醒的进程可能不在rq上
//但是回到上面去看了下,唤醒的进程也通过activate_task将on_rq置1了
//新创建的进程on_rq也被置1,这里什么情况会为假,想不出来
1861 if (se->on_rq) {
1862 /*
1863 * Any task has to be dequeued before it get to execute on
1864 * a CPU. So account for the time it spent waiting on the
1865 * runqueue.
1866 */
1867 update_stats_wait_end(cfs_rq, se);
1868 __dequeue_entity(cfs_rq, se); //当前执行进程不保存在树中
1869 }
1870
1871 update_stats_curr_start(cfs_rq, se);
//curr保存的那个前运行进程
1872 cfs_rq->curr = se; //在put_prev_entity中清空的curr在这里被更新
1873 //...
//将进程运行总时间保存到prev_..中,这样进程本次调度的运行时间可以用下面公式计算:
//进程本次运行已占用CPU时间 = sum_exec_runtime - prev_sum_exec_runtime
//这里sum_exec_runtime会在每次时钟tick中更新
1884 se->prev_sum_exec_runtime = se->sum_exec_runtime;
1885 }