欢迎光临散文网 会员登陆 & 注册

带你玩转linux内核源码分析之CFS调度

2022-06-01 18:06 作者:补给站Linux内核  | 我要投稿

一、CFS 完全公平调度

  1. 允许每个进程运行一段时间、循环轮转,选择运行最少的进程作为下一个运行进程。

  2. 依靠所有可运行总数基础上计算出一个进程应该运行多久。

  3. nice优先级用来计算权重

内核源码 kernel/sched/fair.c

二、调度实现

1、时间记账

  • 每个进程只能在公平分配给它的处理器时间运行

  • 时间在调度实体struct sched_entity中维护


  • 虚拟实时 vruntime,该运行时间计算是经过了所有可运行进程总数的标准化。以ns为单位。用vruntime记录了一个程序到底运行了多长时间以及还应该运行多久。

  • elta_exec:当前进程的执行时间,参数有又递给calc_delta_fair。

  • calc_delta_fair 再根据当前可运行进程总数对运行时间进行加权计算,将最终权重值与当前进程的vruntimes相加。curr->vruntime += calc_delta_fair(delta_exec, curr);

  • update_curr是由系统定时器周期性调用的,无论进程在可运行还是阻塞状态,vruntime可以准确地计算出给定进程的运行时间,而且制定谁应该是下一个被运行进程

【文章福利】小编推荐自己的Linux内核技术交流群:【891587639】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100名进群领取,额外赠送一份价值699的内核资料包(含视频教程、电子书、实战项目及代码)   


2、调度器入口

  • schedule() 选择哪个进程可以运行,何时将其投入运行。

  • 调用pick_next_task,会以优先级排序,从高到低,依次检查每个调度类,并从最高优先级调度类中,选择最高优先级的进程

schedule->__schedule->pick_next_task->pick_next_task_fair schedule->__schedule->pick_next_task->context_switch

  • 上下文切换本身通过调用两个特定于处理器的函数完成

  1. switch_mm 更换通过task_struct->mm描述的内存管理上下文

  2. switch_to切换处理器寄存器内容和内核栈,通常使用汇编编写

  • switch_to(prev, next, prev);//之后的代码只有在当前进程的下一次被选择运行时才会执行

  •    barrier();//前后语句执行顺序不会因为任何可能的优化而改变

  •    return finish_task_switch(prev);//完成清理工作,才能够正确释放锁

3、进程选择


选择vruntime最小也就是红黑树中最左侧叶子节点 实现函数pick_next_entity->__pick_next_entity

4、睡眠和唤醒

  • 睡眠时进程会标记为休眠状态,从可执行红黑树中移除,放入等待队列,然后调用schedule选择执行下一个进程。

  • 唤醒:进程被设置为可执行状态,从等待队列中移到可执行红黑树中

check_preempt_wakeup->wakeup_preempt_entity

三、CFS调度中vruntime

  1. 新创建的vruntime是多少?

  2. 阻塞的进程在调度时,runtime是多少?

  3. 周期调度,vruntime时间如何计算?

  4. 如果进程比较多,进程所获得时间片时间趋于0,系统有何策略,调度优化的参数有哪些?

1.新进程vruntime的值

do_fork --> copy_process --> sched_fork --> task_fork_fair

减掉min_vruntime的数值,是因为你这个时候的新进程不跟CPU关联起来,实际在运行的时,可能会挂到其它CPU的CFS队列,如果两个进程CPU处理器的min_vruntime差距太大,会导致新进程优先级很低或者很高,主要是为了解决这个问题,在入新CPU处理顺路时,会再加上min_vruntime。新进程vruntime=父进程vruntime + (cpux_min_vruntime - cpuy_min_vruntime)。


  • 设置好时间后 接着执行 判断是否抢占wake_up_new_task->activate_task->check_preempt_wakeup->wakeup_preempt_entity

  • wakeup_preempt_entity在上面有介绍

2.阻塞的进程在调度时,runtime是多少?

activate_task–>enqueue_task_fair–>enqueue_entity

  • 由于内核已经承诺,在当前的延迟周期内所有使用活动进程都至少运行一次,队列的min_vruntime用作基准虚拟时间,通过减去sysctl_sched_latency的一半,则可以确保新唤醒的进程只有在当前延迟周期结束后才能运行。

  • 如果睡眠进程已经积累了比较大的不公平。则内核必须考虑。如果se->vruntime比计算的差值更大,则将其作为进程的vruntime,这会导致该进程在红黑树中比较靠左的位置。


3.周期调度,vruntime时间如何计算

  • 对于单 CPU 而言, 默认设置是小于 8 个就绪任务时就按照 6ms, 大于 8 个就绪任务时, 每个任务给 0.75 ms.

  • 确保没有哪个进程能够比延迟周期中确定的份额(给定的时间)运行时间更长

ideal_runtime:该份额对应的时间(给定的时间) delta_exec:进程在CPU上已经运行的时间

1)已经运行的时间与份额比较

2)已经运行的时间与下一个调度实体时间比较

4.调度器优化变量

sysctl_sched_child_runs_first:child在fork之后调度的策略,如果为0 则先调用parent sched_min_granularity_ns:最低级别抢占粒度。给每个进程分配CPU时间,如果进程无限多,则时间趋向于0。如果小于sched_min_granularity_ns则以sched_min_granularity_ns为准 sysctl_sched_min_granularity:如果执行时间delta_exec < sysctl_sched_min_granularity则不进行调度。unsigned int sysctl_sched_min_granularity = 750000ULL


带你玩转linux内核源码分析之CFS调度的评论 (共 条)

分享到微博请遵守国家法律