linux内核CFS进度调整战略

在抽象模型中vruntime决定了经过被调解的前后相继顺序,在真实模型中决定被调节的前后相继顺序的参数是由函数entity_key决定的。   
static inline s64 entity_key(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
    return se->vruntime - cfs_rq->min_vruntime;
}
enqueue_task_fair---->enqueue_entity---->__enqueue_entity---->entity_key决定插入就绪队列的地点。

以下大家看下place_entity是怎么总括新进度的vruntime的。

                                                                   |--> rt_mutex_adjust_prio_chain()

[cpp] view plaincopy

         rt_mutex_slowlock() ----> __rt_mutex_slowlock() ---->

首先个情景选为进度创建时CFS相关变量的伊始化。
大家知晓。Linux创制进度使用fork只怕clone或然vfork等系统调用,终于都会到do_fork。

    b)rt_priority,表示实时进度的优先级(普通进程没用该参数),它的值介于[0~99]之间。rt_priority的值越大其优先级越高。
    c)normal_prio,由于static_prio和rt_priority与先行级的关联性不等同,由此用normal_prio统一下“单位”,统一成:normal_prio值越小则进程优先级越高。因此,normal_prio也得以清楚为:统一了单位的“静态”优先级。
    d)prio,在系统中选择prio判别进程优先级,prio是进度的动态优先级,其代表经过的平价优先级。对于实时进程来讲,有效优先级prio就特出它的normal_prio。普通进度能够一时提升优先级,通过退换prio达成,动态优先级的拉长不影响进程的静态优先级。父进度的动态优先级不会遗传给子进度,子进度的动态优先级prio最早化为父进程的静态优先级。

 

vruntime行走速度:
    系统显著:默许权重值(1024)对应的历程的vruntime行走时间与事实上运作时刻runtime是1:1的关系。由于vruntime的行动速度和权重值成反比,那么别的进程的vruntime行走速度都经过以下三个参数计算得到:1、该进程的权重值2、私下认可进程的权重值。
    比方权重为3096的历程的vruntime行走速度为:1024/3096 * (wall clock)。
    “真实石英钟速度”即为runtime(即 wall clock)行走的进度。

除此以外有少数值得大器晚成提,纵然整个系统特别卡,可是对鼠标键盘的响应还是特别快。
自己打字的时候大约不会有何延迟,那也认证。固然CFS未有经过复杂的涉世公式区分
交互作用式进度。然则它的统筹思路使他自然地对交互作用式进程的响应只怕比O(1)调整还要好。

参谋《深切Linux内核架构》p70-76、 p_288-290;

[cpp] view plaincopy

II、睡眠进程被提醒
    将经过的vruntime值设置为cfs_rq->min_vruntime值,然后再开展一下互补:将vruntime减去与sysctl_sched_latencyd相关的一个数值。进度步向睡眠意况时cfs_rq->min_vruntime就抢先或等于该进程的vruntime值,它在上床进程中vruntime值是不退换的,但是cfs_rq->min_vruntime的值却是单调扩张的,进度醒来后补偿的量由sysctl_sched_latency给出,不管进度面对的失之偏颇待遇大依然小,朝气蓬勃律只补充这么多。

  1. /*** 
  2.  * try_to_wake_up - wake up a thread 
  3.  * @p: the to-be-woken-up thread 
  4.  * @state: the mask of task states that can be woken 
  5.  * @sync: do a synchronous wakeup? 
  6.  * 
  7.  * Put it on the run-queue if it's not already there. The "current" 
  8.  * thread is always on the run-queue (except when the actual 
  9.  * re-schedule is in progress), and as such you're allowed to do 
  10.  * the simpler "current->state = TASK_RUNNING" to mark yourself 
  11.  * runnable without the overhead of this. 
  12.  * 
  13.  * returns failure only if the task is already active. 
  14.  */  
  15. static int try_to_wake_up(struct task_struct *p, unsigned int state, int sync)  
  16. {  
  17.     int cpu, orig_cpu, this_cpu, success = 0;  
  18.     unsigned long flags;  
  19.     struct rq *rq;  
  20.     rq = task_rq_lock(p, &flags);  
  21.     if (p->se.on_rq)  
  22.         goto out_running;  
  23.     update_rq_clock(rq);  
  24.     activate_task(rq, p, 1);  
  25.     success = 1;  
  26. out_running:  
  27.     check_preempt_curr(rq, p, sync);  
  28.     p->state = TASK_RUNNING;  
  29. out:  
  30.     current->se.last_wakeup = current->se.sum_exec_runtime;  
  31.     task_rq_unlock(rq, &flags);  
  32.     return success;  
  33. }  

进度的优先等级调整了其权重值,task_struct中与先行级相关数据成员:
    a)static_prio,指普通进度的静态优先级(实时进程没用该参数),值越小则优先级越高。静态优先级是进度运行时分配的事先级。它能够用nice()或然sched_setscheduler()系统调用更正,不然在运营时期一直保持一直。

 

平凡进度分为四十多少个级次,每一种阶段对应叁个权重值,权重值用一个整数来标示。权重值定义在数组prio_to_weight[40]中;普通进度的权重值最大为88761,最小为15。暗中认可情状下,普通进度的权重值为1024(由NICE_0_LOAD钦命)。weight是由进度的静态优先级static_prio决定的,静态优先级越高(static_prio值越小)weight值越大。普通进度的暗中认可nice值为0,即暗中同意静态优先级为120,它的weight值为prio_to_weight[20],即1024。因此NICE_0_LOAD的值就 为1024。

 
此间是总括进程的始发vruntime。

诚实模型计算:
    a)进程在就绪队列中用键值key来排序,它并未有保留在其余变量中,而是在须要时由函数entity_key()计算得出。它相当
        key = task->vruntime - cfs_rq->min_vruntime
    b)各类进度有分裂的显要(优先等级),越首要的进度权重值weight(task.se.load.weight)越大。
    c)每种进度vruntime行走的进度和weight值成反比。权重值为1024(NICE_0_LOAD)的进程vruntime行走速度和runtime相仿。
    d)每一种进程每一遍得到CPU使用权最多推行与该进度对应的ideal_runtime长期。该时长和weight值成正比,它并未有用变量来保存,而是须要使用sched_slice()函数总括得出。
    e)ideal_runtime计算的法则是period,它也从不用变量来保存,而是由__sched_period()总结得出。

  1. static struct task_struct *pick_next_task_fair(struct rq *rq)  
  2. {  
  3.     struct task_struct *p;  
  4.     struct cfs_rq *cfs_rq = &rq->cfs;  
  5.     struct sched_entity *se;  
  6.     if (unlikely(!cfs_rq->nr_running))  
  7.         return NULL;  
  8.     do {  
  9.         //那多个函数是重大,选拔下叁个要运维的天职  
  10.         se = pick_next_entity(cfs_rq);  
  11.         set_next_entity(cfs_rq, se);  
  12.         cfs_rq = group_cfs_rq(se);  
  13.     } while (cfs_rq);  
  14.     p = task_of(se);  
  15.     hrtick_start_fair(rq, p);  
  16.     return p;  
  17. }  

 

 

由于在少数情况下必要暂时提升进度的优先级,由此不但需求静态优先级和平常优先级,还需求动态优先级prio;

到这里,place_entity就说罢了。

至于Priority Inversion能够参见《Operating System Concepts》9_ed p217-218                                                                                                                       

  1. static void update_curr(struct cfs_rq *cfs_rq)  
  2. {  
  3.     struct sched_entity *curr = cfs_rq->curr;  
  4.     u64 now = rq_of(cfs_rq)->clock; //这个clock刚刚在scheduler_tick中更新过  
  5.     unsigned long delta_exec;  
  6.     /* 
  7.      * Get the amount of time the current task was running 
  8.      * since the last time we changed load (this cannot 
  9.      * overflow on 32 bits): 
  10.      */  
  11.     //exec_start记录的是上二遍调用update_curr的日子,大家用当下时刻减去exec_start  
  12.     //就拿到了从上次测算vruntime到今后历程又进行的年月,用那些时间换算成vruntime  
  13.     //然后加到vruntime上,那总体是在__update_curr中停止的  
  14.     delta_exec = (unsigned long)(now - curr->exec_start);  
  15.     __update_curr(cfs_rq, curr, delta_exec);  
  16.     curr->exec_start = now;   
  17.     if (entity_is_task(curr)) {  
  18.         struct task_struct *curtask = task_of(curr);  
  19.         cpuacct_charge(curtask, delta_exec);  
  20.         account_group_exec_runtime(curtask, delta_exec);  
  21.     }  
  22. }  
  23. /* 
  24.  * Update the current task's runtime statistics. Skip current tasks that 
  25.  * are not in our scheduling class. 
  26.  */  
  27. static inline void  
  28. __update_curr(struct cfs_rq *cfs_rq, struct sched_entity *curr,  
  29.           unsigned long delta_exec)  
  30. {  
  31.     unsigned long delta_exec_weighted;  
  32.     //前边说的sum_exec_runtime便是在那地计算的,它也就是进度从创建開始占用CPU的总时间  
  33.     curr->sum_exec_runtime += delta_exec;   
  34.     //以下变量的weighted表示这几个值是从试行时间考虑权重因素换算来的vruntime。再写一回那一个公式  
  35.     //vruntime(delta_exec_weighted) = 实际进行时间(delta_exe) * 1024 / 进度权重  
  36.     delta_exec_weighted = calc_delta_fair(delta_exec, curr);  
  37.     //将进程刚刚实践的时刻换算成vruntime后立马加到进程的vruntime上。  
  38.     curr->vruntime += delta_exec_weighted;  
  39.     //由于有经过的vruntime变了,由此cfs_rq的min_vruntime可能也要转移,更新它。  
  40.     //这一个函数不难,就不跟进去了,正是先取tmp = min(curr->vruntime,leftmost->vruntime)  
  41.     //然后cfs_rq->min_vruntime = max(tmp, cfs_rq->min_vruntime)  
  42.     update_min_vruntime(cfs_rq);  
  43. }   

          __rt_mutex_adjust_prio调整了脚下持有锁的经过的动态优先级(世袭自等待队列中全数进程的万丈优先级卡塔 尔(英语:State of Qatar),rt_mutex_adjust_prio_chain()假如被调动的动态优先级的历程也在伺机有些能源,那么也要链式地调动相应进程的动态优先级。

接下去的景观正是石英钟中断,机械钟中断在time_init_hook中起头化,中断函数为timer_interrupt
信守比如以下路子
   timer_interrupt
-->do_timer_interrupt_hook
-->这里有叁个回调函数,在本身机器上測试调用的是tick_handle_oneshot_broadcast
-->从tick_handle_oneshot_broadcast前边意气风发部分进度怎么走的没搞领会,不经常光用kgdb追踪一下
-->反正最终是到了tick_handle_periodic
-->tick_periodic
-->update_process_times
-->scheduler_tick 那此中跟CFS相关的重大便是改善了cfs_rq的时钟
-->通过回调函数调到task_tick_fair,没作什么事,直接进去了entity_tick
-->entity_tick这几个函数看下

注:

最重要看下pick_next_entity和set_next_entity

酌量下当创设新历程大概经过唤醒时,vruntime在真实模型中的管理形式:
I、新建进程
    进程的ideal_time长度和weight成正比,vruntime行走速度与weight值成反比。因而当各种进度在period时间内,都实施了和煦相应的ideal_time长期,那么他们的vruntime的增量相等。而nice为0的历程的vruntime行走速度等于runtime行走速度,所以每一个进程都运转它本身相应的ideal_runtime时间,其余进度的vruntime增量都等于nice值为0的长河的ideal_runtime。即使初叶景况下,它们的兼具进度的vruntime值都等于0,那么当一个进程运行完runtime的年华为ideal_time,那么它的vruntime将为最大,只要任何进程的运维总时间从没直达分别对应的ideal_runtime值,那么它始终排在进度队列的末梢。

[cpp] view plaincopy

    进度试行试行时期周期性调节器周期性地运转,其承当更新一些连锁数据,并不肩负进度之间的切换:
    timer_tick()---->update_process_times---->schedule_tick()
    schedule_tick---->task_tick_fair---->entity_tick()---->update_curr()
    update_curr()函数实现相关数据的换代。
        update_curr()---->delta_exec = (unsigned long)(now - curr->exec_start)
                              |-->__update_curr()
                              |-->curr_exec_start = now;
    update_curr()函数只担任总计delta_exec以至更新exec_start。别的工作由__update_curr()函数实现:
        1、更新当前历程的实在运作时刻(抽象模型中的runtime卡塔尔。
        2、更新当前历程的诬捏时间vruntime。
        3、更新cfs_rq->min_vruntime。
           在现阶段历程和下三个就要被调治的长河中选择vruntime不大的值。然后用该值和cfs_rq->min_vruntime比较,如果比min_vruntime大,则更新cfs_rq为的min_vruntime为所求出的值。

OK。更新完CFS状态之后回来entity_tick中,这时候供给检測是不是满意抢占条件。这里也是CFS的根本之中的叁个

         linux内核的优先级继承左券(pip卡塔 尔(阿拉伯语:قطر‎

[cpp] view plaincopy

    对于新历程,task_fork_fair()->place_entity(cfs_rq, se, 1),其intial参数为1。新历程的vruntime值被安装为min_vruntime+sched_vslice(cfs_rq, se), sched_vslice()函数可总计出nice值为0的经过的ideal_runtime。其作用是将新插足的进度的符号为“它在period长日子内少年老成度运营它对应的ideal_time长时间”,那么新到场进程在评论上(全体进度都实行它对应的ideal_runtime时间,未有爆发睡眠、进度终止等特别规处境卡塔 尔(阿拉伯语:قطر‎独有静观其变period之后技艺被调整。
    sched_vslice(cfs_rq, se)---->calc_delta_fair(sched_slice(cfs_rq, se), se), sched_slice()总计新建进程的ideal_runtime,calc_delta_fair()将ideal_runtime转换成vruntime。

 

         进程优先级转败为胜难题的消除  

[cpp] view plaincopy

       注意:关于a),注意本文的末尾增添的注释。

  1. static void  
  2. place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial)  
  3. {  
  4.     u64 vruntime = cfs_rq->min_vruntime;  
  5.     /* 
  6.      * The 'current' period is already promised to the current tasks, 
  7.      * however the extra weight of the new task will slow them down a 
  8.      * little, place the new task so that it fits in the slot that 
  9.      * stays open at the end. 
  10.      */  
  11.     if (initial && sched_feat(START_DEBIT))  
  12.         vruntime += sched_vslice(cfs_rq, se);  
  13.     if (!initial) {  
  14.         //先不看这里,  
  15.     }  
  16.     se->vruntime = vruntime;  
  17. }  

                 task_blocks_on_rt_mutex() ---->  __rt_mutex_adjust_prio()

 

        为了在Linux中动用Priority Inheritance Protocol合同来减轻先行级反转难题,Linux中引进实时互斥量rt_mutex,在task_struc结构体中也引进了pi_waiters链表,须求留意的流程为:

再解释下min_vruntime,那是每叁个cfs队列二个的变量,它日常小于等于风度翩翩体就绪态进度
的矮小vruntime。也可以有例外。比如对睡眠进度张开时间补偿会导致vruntime小于min_vruntime。

小编们看看wakeup_preempt_entity(se, pse)。到底怎么估摸前者是或不是能够抢占前边多少个

 
再dmesg查看结果,我測试过两遍。二次设置nice分别为0和6,那么权重之比为
1024 / 272 = 3.7647。结果进行时间之比为 146390068851 / 38892147894 = 3.7640
可见见到结果极其临近,
还应该有贰回设置nice分别为-20和19,权重之比为88761 / 15 = 5917.4000,
结果实践时间之比为187800781308 / 32603290 = 5760.1788。也特别相近。
能够看见,下面的权重与推行时间成正比的结论是建设构造的。
实际,当自个儿实施三个nice为-20的前后相继后,整个体系很卡,差那么一点儿成了幻灯片,
也印证nice值的两样带给的差距很明显。

  1. static void put_prev_entity(struct cfs_rq *cfs_rq, struct sched_entity *prev)  
  2. {  
  3.     /* 
  4.      * If still on the runqueue then deactivate_task() 
  5.      * was not called and update_curr() has to be done: 
  6.      */  
  7.     //记得这里的on_rq吗?在schedule函数中借使进程情形不是TASK_RUNNING,  
  8.     //那么会调用deactivate_task将prev移出实践队列,on_rq清零。由此这里也是单纯有当  
  9.     //prev进度仍然在推行态的时候才要求更新vruntime等新闻。  
  10.     //如若prev进度由于被侵吞大概是因为岁月到了而被调解出去则on_rq仍然为1  
  11.     if (prev->on_rq)  
  12.         update_curr(cfs_rq);  
  13.     check_spread(cfs_rq, prev);  
  14.     //这里也相似,仅唯有当prev进度仍在施行情形的时候才供给更新vruntime音讯  
  15.     //实际上正在cpu上推行的进程是不在红黑树中的,仅唯有在等候CPU的经过才在红黑树  
  16.     //由此这里将调整出的进度又一次扩展红黑树。

    on_rq并不代表在红黑树中,而是意味着在推行意况

      

  17.     if (prev->on_rq) {  

  18.         update_stats_wait_start(cfs_rq, prev);  
  19.         /* Put 'current' back into the tree. */  
  20.         //那一个函数也不跟进去了,便是把经过以(vruntime-min_vruntime)为key增到红黑树中  
  21.         __enqueue_entity(cfs_rq, prev);  
  22.     }  
  23.     //未有当前路程了。那一个当前经过就要pick_next_task中更新  
  24.     cfs_rq->curr = NULL;  
  25. }  

[cpp] view plaincopy

[cpp] view plaincopy

 

函数方面凝视中国和亚洲常图便是其一意思,我们看下:
横坐标表示vruntime。s1 s2 s3分级代表新历程,c表示近些日子经过,g表示调解粒度。
s3分明能抢占c。而s1不容许抢占c。
s2即便vruntime比c小。不过在调节粒度之内,是不是能抢占要看事态,像以后如此的光景就不能够抢占。

 

关于dequeue_task,dequeue_entity和__dequeue_entity三者差距
前双方差不太多。不一致的那部分本人也没看鲜明。。。首即使它们都会将on_rq清零。
本身以为是当进度要离开TASK_RUNNING状态时调用。这多个函数能够将经过取下试行队列。

 

  1. static struct sched_entity *pick_next_entity(struct cfs_rq *cfs_rq)  
  2. {  
  3.     //__pick_next_entity正是平素选取红黑树缓存的最左结点,也正是vruntime最小的结点  
  4.     struct sched_entity *se = __pick_next_entity(cfs_rq);  
  5.     //以下的wakeup_preempt_entity已经讲过。忘记的同室能够到地点看下  
  6.     //这里正是前边所说的先行照看next和last进程,仅唯有当__pick_next_entity选出来的进度  
  7.     //的vruntime比next和last都小超二人台解粒度时才轮到它施行,不然即使next或许last  
  8.     if (cfs_rq->next && wakeup_preempt_entity(cfs_rq->next, se) < 1)  
  9.         return cfs_rq->next;  
  10.     if (cfs_rq->last && wakeup_preempt_entity(cfs_rq->last, se) < 1)  
  11.         return cfs_rq->last;  
  12.     return se;  
  13. }  
  14. static void  
  15. set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)  
  16. {  
  17.     /* 'current' is not kept within the tree. */  
  18.     //这里怎么状态下标准会为假?作者以为刚唤醒的长河可能不在rq上  
  19.     //但是回到地点去看了下,唤醒的历程也由此activate_task将on_rq置1了  
  20.     //新制造的进度on_rq也被置1,这里怎么状态会为假,想不出来  
  21.     //这里本人也測试了生龙活虎晃。在准绳为真假的路径上各设置了三个流速计  
  22.     //当条件为真经过了周边三十万次的时候条件为假只有一回。  
  23.     //所以咱们能够认为大致都会平昔进去if语句块实行  
  24.     if (se->on_rq) {  
  25.         /*此处凝视是否写错了?dequeued写成了enqueued? 
  26.          * Any task has to be enqueued before it get to execute on 
  27.          * a CPU. So account for the time it spent waiting on the 
  28.          * runqueue. 
  29.          */  
  30.         update_stats_wait_end(cfs_rq, se);  
  31.         //就是把结点从红黑树上取下来。

    前边说过,占用CPU的进度不在红黑树上

      

  32.         __dequeue_entity(cfs_rq, se);  

  33.     }  
  34.     update_stats_curr_start(cfs_rq, se);  
  35.     cfs_rq->curr = se;  //OK。在put_prev_entity中清空的curr在那处被更新  
  36.     //将进程实践总时间保存到prev_..中。那样经过这次调节的实践时间能够用以下公式总结:  
  37.     //进度本次实践已攻陷CPU时间 =  sum_exec_runtime - prev_sum_exec_runtime  
  38.     //这里sum_exec_runtime会在历次石英钟tick中更新  
  39.     se->prev_sum_exec_runtime = se->sum_exec_runtime;  
  40. }  

 

  1. /* 
  2.  * Preempt the current task with a newly woken task if needed: 
  3.  */笔者略去了部分不太重大的代码  
  4. static void check_preempt_wakeup(struct rq *rq, struct task_struct *p, int sync)  
  5. {  
  6.     struct task_struct *curr = rq->curr;  
  7.     struct sched_entity *se = &curr->se, *pse = &p->se; //se是如今经过。pse是新历程  
  8.     /* 
  9.      * Only set the backward buddy when the current task is still on the 
  10.      * rq. This can happen when a wakeup gets interleaved with schedule on 
  11.      * the ->pre_schedule() or idle_balance() point, either of which can 
  12.      * drop the rq lock. 
  13.      * 
  14.      * Also, during early boot the idle thread is in the fair class, for 
  15.      * obvious reasons its a bad idea to schedule back to the idle thread. 
  16.      */  
  17.     if (sched_feat(LAST_BUDDY) && likely(se->on_rq && curr != rq->idle))  
  18.         set_last_buddy(se);  
  19.     set_next_buddy(pse);  
  20.     while (se) {  
  21.         if (wakeup_preempt_entity(se, pse) == 1) {  
  22.             resched_task(curr);  
  23.             break;  
  24.         }  
  25.         se = parent_entity(se);  
  26.         pse = parent_entity(pse);  
  27.     }  
  28. }  

[cpp] view plaincopy

当今大家把公式第22中学的实际实践时间用公式1来替换。能够收获这样叁个结实:
vruntime = (调治周期 * 进程权重 / 全部进度总权重) * 1024 / 过程权重=调解周期 * 1024 / 全部经过总权重
看看哪些形容未有?没有错,就算经过的权重分裂,不过它们的vruntime增速应该是相似的(这里所说的增速同样,是从宏观上来看的。从上风流洒脱篇小说能够看出来。而在上黄金时代篇小说中说vruntime的增量差异,是从公式深入分析获得的,算是局地解析,在公式第22中学,如若实际奉行时间都是同等。特别明确权重小的拉长的多。权根本的增进的小,我个人感到便是虚构机械钟的存在。调换了沉凝。才有了这么些CFS,事实上照旧依据权重来决定贰个经过在贰个调用周期内推行了多久,但是虚构石英钟决定了怎么调节这些进度,那就是观念卡塔 尔(阿拉伯语:قطر‎,与权重无关。
好,既然全体进度的vruntime增速宏观上看应该是如出风流倜傥辙时候推进的。
那正是说就可以预知用那个vruntime来采撷实践的历程。何人的vruntime值异常的小就表达它早已占用cpu的年月相当的短,
面前遭遇了“有失公平”对待,由此下一个奉行进度正是它。

 
率先对此last和next七个字段授予证实。
后生可畏经那三个字段不为NULL,那么last指向最近被调治出去的历程,next指向被调节上cpu的历程。
比方A正在实施,被B抢占。那么last指向A。next指向B。

 

  1. /* 
  2.  * schedule() is the main scheduler function. 
  3.  */  
  4. asmlinkage void __sched schedule(void)  
  5. {  
  6.     struct task_struct *prev, *next;  
  7.     unsigned long *switch_count;  
  8.     struct rq *rq;  
  9.     int cpu;  
  10. need_resched:  
  11.     preempt_disable(); //在这里其间被并吞只怕现身故障。先禁绝它!  
  12.     cpu = smp_processor_id();  
  13.     rq = cpu_rq(cpu);  
  14.     rcu_qsctr_inc(cpu);  
  15.     prev = rq->curr;  
  16.     switch_count = &prev->nivcsw;  
  17.     release_kernel_lock(prev);  
  18. need_resched_nonpreemptible:  
  19.     spin_lock_irq(&rq->lock);  
  20.     update_rq_clock(rq);  
  21.     clear_tsk_need_resched(prev); //解除要求调节的位  
  22.     //state==0是TASK_RUNNING,不等于0便是绸缪睡觉,寻常情况下应当将它移出实践队列  
  23.     //可是还要检查下是或不是有随机信号过来。借使有能量信号何况经过处于可暂停睡眠就提醒它  
  24.     //注意对于要求睡觉的经过。这里调用deactive_task将其移出队列並且on_rq也被清零  
  25.     //这个deactivate_task函数就不看了,特别easy  
  26.     if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {  
  27.         if (unlikely(signal_pending_state(prev->state, prev)))  
  28.             prev->state = TASK_RUNNING;  
  29.         else  
  30.             deactivate_task(rq, prev, 1);  
  31.         switch_count = &prev->nvcsw;  
  32.     }  
  33.     if (unlikely(!rq->nr_running))  
  34.         idle_balance(cpu, rq);  
  35.     //那三个函数都以重大,大家以下解析  
  36.     prev->sched_class->put_prev_task(rq, prev);  
  37.     next = pick_next_task(rq, prev);  
  38.     if (likely(prev != next)) {  
  39.         sched_info_switch(prev, next);  
  40.         rq->nr_switches++;  
  41.         rq->curr = next;  
  42.         ++*switch_count;  
  43.         //完成进度切换,不讲了,跟CFS不妨  
  44.         context_switch(rq, prev, next); /* unlocks the rq */  
  45.         /* 
  46.          * the context switch might have flipped the stack from under 
  47.          * us, hence refresh the local variables. 
  48.          */  
  49.         cpu = smp_processor_id();  
  50.         rq = cpu_rq(cpu);  
  51.     } else  
  52.         spin_unlock_irq(&rq->lock);  
  53.     if (unlikely(reacquire_kernel_lock(current) < 0))  
  54.         goto need_resched_nonpreemptible;  
  55.     preempt_enable_no_resched();  
  56.     //这里新历程也说不许有TIF_NEED_RESCHED标记,假若新历程也必得调节则再调治叁遍  
  57.     if (unlikely(test_thread_flag(TIF_NEED_RESCHED)))  
  58.         goto need_resched;  
  59. }  

<
那三个指针仅仅使用一遍。正是在上头那么些函数退出后,重返客户空间时会触发schedule,
在此选用下二个调迈进程时会优先选项next,次优先选项last。接纳完后。就能够清空那三个指针。
那样设计的缘由是,在上头的函数中检測结果是能够抢占并不意味已经抢占,而只是是设置了调节标记,
在最后触发schedule时抢占进程B并不一定是到头来被调整的进程(为啥?由于我们测度是不是能抢占
的依靠是抢占进度B比实践过程A的vruntime小,但红黑树中或者有比抢占进度B的vruntime更加小的进度C,
这么在调节时就能够中选vruntime最小的C,并不是并吞进度B)。可是大家当然希望优先调节B,
是因为大家正是为了试行B才设置了调治标记,所以这里用一个next指针指向B,以便给他个后门走,
假使B实在不争气,vruntime太大。就仍然继续实践被侵吞进度A比較合理,因而last指向被侵占进度。
那是一个比next小一些的后门,假如next近便的小路失利,就让被侵夺进度A也走壹次后门,
若果被侵夺进程A也不争气。vruntime也太大,仅仅好从红黑树中挑多个vruntime最小的了。
任凭它们活动是还是不是成功,豆蔻梢头旦选出下三个历程,就立刻清空那八个指针,不可能老开着那一个后门吧。
供给在意的是,schedule中清空那八个指针仅仅在2.6.29及之后的木本才有。以前的水源未有那句话。

首先看put_prev_task,它等于put_prev_task_fair,后面一个基本上便是一贯调用put_prev_entity

entity_tick函数正是翻新意况音讯,然后检測是还是不是满意抢占条件。
眼下大家直接忽视update_curr,这里必看一下了

三、唤醒进度
咱俩再看看唤醒进度时的CFS动作。看下函数try_to_wake_up。十分长的函数,仅仅留几行代码

  1. static void  
  2. entity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, int queued)  
  3. {  
  4.     /* 
  5.      * Update run-time statistics of the 'current'. 
  6.      */  
  7.     update_curr(cfs_rq);  
  8.     //....非亲非故代码  
  9.     if (cfs_rq->nr_running > 1 || !sched_feat(WAKEUP_PREEMPT))  
  10.         check_preempt_tick(cfs_rq, curr);  
  11. }  
  1. static void  
  2. place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial)  
  3. {  
  4.     u64 vruntime = cfs_rq->min_vruntime;  
  5.     /* 
  6.      * The 'current' period is already promised to the current tasks, 
  7.      * however the extra weight of the new task will slow them down a 
  8.      * little, place the new task so that it fits in the slot that 
  9.      * stays open at the end. 
  10.      */  
  11.     if (initial && sched_feat(START_DEBIT))  
  12.         vruntime += sched_vslice(cfs_rq, se);  
  13.     if (!initial) {  
  14.         /* sleeps upto a single latency don't count. */  
  15.         if (sched_feat(NEW_FAIR_SLEEPERS)) {  
  16.             unsigned long thresh = sysctl_sched_latency;  
  17.             /* 
  18.              * convert the sleeper threshold into virtual time 
  19.              */  
  20.             if (sched_feat(NORMALIZED_SLEEPER))  
  21.                 thresh = calc_delta_fair(thresh, se);  
  22.             vruntime -= thresh;  
  23.         }  
  24.         /* ensure we never gain time by being placed backwards. */  
  25.         vruntime = max_vruntime(se->vruntime, vruntime);  
  26.     }  
  27.     se->vruntime = vruntime;  
  28. }  

 
此地还须求再看三回place_entity,前面固然看过二遍,然而第两个參数不一样样。
当參数3为0的时候走的是还应该有一条路径,大家看下

一、概述

它以cfs队列的min_vruntime为基准,再加上进度在壹遍调节周期中所增进的vruntime。
此间并非计算进度应该施行的时日。而是先把经过的早就实践时间设为七个非常大的值。
唯独该进程鲜明还还未进行过啊,为何要这么做呢?
假如新进程都能拿到最小的vruntime(min_vruntime),那么新进度会率先个被调节试行。
诸如此比技士就能够由此持续的fork新历程来让投机的次序一贯据有CPU。那显著是不创造的,
那跟曾经采取时间片的基石中老爹和儿子进度要平均父进度的时间片是一个道理。

若是未有安装CLONE_STOPPED,则会进去wake_up_new_task函数,大家看看这么些函数的非常重要部分

再补充一下权重的源于,权重跟进程nice值之间有各种相应的关联,能够通过全局数组prio_to_weight来转换,
nice值越大,权重越低

[cpp] view plaincopy

[cpp] view plaincopy

 
update_rq_clock就是改正cfs_rq的挂钟表,保持与系统时间同步。
重点是activate_task,它将经过增添红黑树并且对vruntime做一些调解。
然后用check_preempt_curr检查是否构成侵占条件。如若能够抢占则设置TIF_NEED_RESCHED标识。

至于sched_vslice总计细节临时不审美,大要上说即是把概述中付出的三个公式结合起来举例以下:
sched_vslice = (调解周期 * 进度权重 / 全体历程总权重) * NICE_0_LOAD / 进度权重
约等于算出进程应分配的其实cpu时间,再把它转变为vruntime。
把那么些vruntime加在进度上自此,就一定于以为新历程在这里风姿罗曼蒂克轮调整中已经进行过了。

本文由2020欧洲杯官方投注-2020欧洲杯官方投注网址发布于win7,转载请注明出处:linux内核CFS进度调整战略

相关阅读