Scheduler CFS
前景回顾
进程调度
内存中保存了对每个进程的唯一描述, 并通过若干结构与其他进程连接起来.
调度器面对的情形就是这样, 其任务是在程序之间共享 CPU 时间, 创造并行执行的错觉, 该任务分为两个不同的部分, 其中一个涉及调度策略, 另外一个涉及上下文切换.
进程的分类
linux 把进程区分为实时进程和非实时进程, 其中非实时进程进一步划分为交互式进程和批处理进程
根据进程的不同分类 Linux 采用不同的调度策略.
对于实时进程,采用 FIFO, Round Robin 或者 Earliest Deadline First (EDF)最早截止期限优先调度算法|的调度策略.
linux 调度器的演变
| 字段 | 版本 |
|---|---|
| O(n)的始调度算法 | linux-0.11~2.4 |
| O(1)调度器 | linux-2.5 |
| CFS 调度器 | linux-2.6~至今 |
Linux 的调度器组成
2 个调度器
可以用两种方法来激活调度
- 一种是直接的, 比如进程打算睡眠或出于其他原因放弃 CPU
- 另一种是通过周期性的机制, 以固定的频率运行, 不时的检测是否有必要
因此当前 linux 的调度程序由两个调度器组成:主调度器,周期性调度器(两者又统称为通用调度器(generic scheduler)或核心调度器(core scheduler))
并且每个调度器包括两个内容:调度框架(其实质就是两个函数框架)及调度器类
6 种调度策略
linux 内核目前实现了 6 中调度策略(即调度算法), 用于对不同类型的进程进行调度, 或者支持某些特殊的功能
- SCHED_NORMAL 和 SCHED_BATCH 调度普通的非实时进程
- SCHED_FIFO 和 SCHED_RR 和 SCHED_DEADLINE 则采用不同的调度策略调度实时进程
- SCHED_IDLE 则在系统空闲时调用 idle 进程.
5 个调度器类
而依据其调度策略的不同实现了 5 个调度器类, 一个调度器类可以用一种种或者多种调度策略调度某一类进程, 也可以用于特殊情况或者调度特殊功能的进程.
其所属进程的优先级顺序为
|
|
3 个调度实体
调度器不限于调度进程, 还可以调度更大的实体, 比如实现组调度.
这种一般性要求调度器不直接操作进程, 而是处理可调度实体, 因此需要一个通用的数据结构描述这个调度实体,即 seched_entity 结构, 其实际上就代表了一个调度对象,可以为一个进程,也可以为一个进程组.
linux 中针对当前可调度的实时和非实时进程, 定义了类型为 seched_entity 的 3 个调度实体
- sched_dl_entity 采用 EDF 算法调度的实时调度实体
- sched_rt_entity 采用 Roound-Robin 或者 FIFO 算法调度的实时调度实体 rt_sched_class
- sched_entity 采用 CFS 算法调度的普通非实时进程的调度实体
cfs 完全公平调度器
CFS 调度器类 fair_sched_class
CFS 完全公平调度器的调度器类叫 fair_sched_class, 其定义在kernel/sched/fair.c, line 8521, 它是我们熟知的是 struct sched_class 调度器类类型, 将我们的 CFS 调度器与一些特定的函数关联起来
|
|
| 成员 | 描述 |
|---|---|
| enqueue_task | 向就绪队列中添加一个进程, 某个任务进入可运行状态时,该函数将得到调用。它将调度实体(进程)放入红黑树中,并对 nr_running 变量加 1 |
| dequeue_task | 将一个进程从就就绪队列中删除, 当某个任务退出可运行状态时调用该函数,它将从红黑树中去掉对应的调度实体,并从 nr_running 变量中减 1 |
| yield_task | 在进程想要资源放弃对处理器的控制权的时, 可使用在 sched_yield 系统调用, 会调用内核 API yield_task 完成此工作. compat_yield sysctl 关闭的情况下,该函数实际上执行先出队后入队;在这种情况下,它将调度实体放在红黑树的最右端 |
| check_preempt_curr | 该函数将检查当前运行的任务是否被抢占。在实际抢占正在运行的任务之前,CFS 调度程序模块将执行公平性测试。这将驱动唤醒式(wakeup)抢占 |
| pick_next_task | 该函数选择接下来要运行的最合适的进程 |
| put_prev_task | 用另一个进程代替当前运行的进程 |
| set_curr_task | 当任务修改其调度类或修改其任务组时,将调用这个函数 |
| task_tick | 在每次激活周期调度器时, 由周期性调度器调用, 该函数通常调用自 time tick 函数;它可能引起进程切换。这将驱动运行时(running)抢占 |
| task_new | 内核调度程序为调度模块提供了管理新任务启动的机会, 用于建立 fork 系统调用和调度器之间的关联, 每次新进程建立后, 则用 new_task 通知调度器, CFS 调度模块使用它进行组调度,而用于实时任务的调度模块则不会使用这个函数 |
cfs 的就绪队列
就绪队列是全局调度器许多操作的起点, 但是进程并不是由就绪队列直接管理的, 调度管理是各个调度器的职责, 因此在各个就绪队列中嵌入了特定调度类的子就绪队列(cfs 的顶级调度就队列 struct cfs_rq, 实时调度类的就绪队列struct rt_rq和 deadline 调度类的就绪队列struct dl_rq
|
|
| 成员 | 描述 |
|---|---|
| nr_running | 队列上可运行进程的数目 |
| load | 就绪队列上可运行进程的累计负荷权重 |
| min_vruntime | 跟踪记录队列上所有进程的最小虚拟运行时间. 这个值是实现与就绪队列相关的虚拟时钟的基础 |
| tasks_timeline | 用于在按时间排序的红黑树中管理所有进程 |
| rb_leftmost | 总是设置为指向红黑树最左边的节点, 即需要被调度的进程. 该值其实可以可以通过病例红黑树获得, 但是将这个值存储下来可以减少搜索红黑树花费的平均时间 |
| curr | 当前正在运行的 sched_entity(对于组虽然它不会在 cpu 上运行,但是当它的下层有一个 task 在 cpu 上运行,那么它所在的 cfs_rq 就把它当做是该 cfs_rq 上当前正在运行的 sched_entity |
| next | 表示有些进程急需运行,即使不遵从 CFS 调度也必须运行它,调度时会检查是否 next 需要调度,有就调度 next |
| skip | 略过进程(不会选择 skip 指定的进程调度) |
参考
https://blog.csdn.net/gatieme/article/details/52067665
https://blog.csdn.net/gatieme/article/details/52067748
-
No backlinks found.