Linux-进程的管理与调度22(基于6.1内核)---Linux进程调度CFS简介
一、 CFS概述
CFS(Completely Fair Scheduler)是 Linux 内核中的默认调度器,旨在提供公平的 CPU 时间分配。它的主要目标是确保系统中所有进程根据其需求公平地共享 CPU 资源,并通过一个高效的调度算法确保系统的响应性和吞吐量。
CFS 的设计目标:
- 公平性(Fairness):CFS 设计的核心目标是保证每个进程(或者说任务)公平地获取 CPU 时间,避免某些进程长时间占用 CPU 资源,而其他进程被“饿死”。
- 低延迟(Low Latency):调度器需要快速响应新的任务和请求,确保进程之间的切换不会引入过高的延迟。
- 高吞吐量(High Throughput):同时,调度器也应当尽量保证高效执行,减少不必要的调度开销,以提升整体吞吐量。
CFS 的主要特性:
CFS 的工作方式与传统的时间片轮转(Round Robin)调度器不同,它并不是简单地给每个进程分配固定时间片,而是通过一个更为复杂的算法来计算进程的“虚拟运行时间”,并依据此时间来决定哪个进程应该执行。
1. 虚拟运行时间(Virtual Runtime):
- CFS 通过维护每个进程的虚拟运行时间(
vruntime)来进行调度。虚拟运行时间是一个不断增长的值,反映了进程运行的多少。每当进程运行时,其虚拟运行时间会根据进程的优先级(权重)增加。虚拟运行时间较小的进程被认为需要更多的 CPU 时间,因此它们会被优先调度。 - 进程的虚拟运行时间是一个加权值,进程的优先级越高(权重越大),其虚拟运行时间的增加速度就越慢。这样,优先级高的进程会相对较长时间地占用 CPU,直到其虚拟运行时间增加到一定程度。
2. 红黑树(Red-Black Tree):
- CFS 使用一个 红黑树 来存储所有可运行的进程。红黑树是一种自平衡的二叉搜索树,能够提供对进程的快速插入、删除和查找操作。
- 树中的每个节点代表一个进程,节点的排序依据是进程的虚拟运行时间。虚拟运行时间较小的进程被放在树的左侧,因此这些进程会被优先选择执行。
3. 调度过程:
- 每当调度器决定调度下一个进程时,CFS 会从红黑树中选择虚拟运行时间最小的进程进行执行。这个进程就会被分配到 CPU 上。
- 如果当前进程的虚拟运行时间最小,它会继续运行,直到它的虚拟时间达到一定的阈值或者被新的任务抢占。
4. 动态调整(动态权重):
- 进程的权重(即优先级)是动态调整的。CFS 不依赖固定的优先级,而是通过
nice值和负载来动态计算进程的权重。 nice值:nice值决定了进程的优先级,数值越大,优先级越低,进程的虚拟运行时间增加得越快,反之亦然。
5. 多核处理器优化:
- 在多核系统中,CFS 会尽量将进程分配到不同的 CPU 核心上,以提高系统的并行性和性能。CFS 会尝试保持进程在同一个 CPU 核心上运行,以减少进程的上下文切换和缓存失效。
CFS 与传统调度器的对比:
CFS 相比于传统的基于时间片轮转的调度器有以下几个显著优势:
- 公平性:CFS 基于虚拟运行时间来进行调度,避免了传统时间片调度器可能导致的某些进程被长期饿死的情况。
- 无固定时间片:CFS 不需要为每个进程分配固定的时间片,而是根据进程的运行情况动态调整 CPU 时间的分配。
- 高效:使用红黑树结构和虚拟运行时间算法,CFS 能够高效地管理进程调度,避免了不必要的调度开销。
CFS 工作流程简述:
- 新进程加入:当一个新进程加入系统时,它会被插入到红黑树中,初始时其虚拟运行时间为 0。
- 进程执行:当一个进程被调度执行时,CFS 会按照虚拟运行时间的顺序选择下一个进程。在执行期间,该进程的虚拟运行时间会不断增加。
- 进程阻塞:如果进程发生阻塞(例如等待 I/O 操作),CFS 会将它移出红黑树,直到进程恢复运行后重新计算虚拟运行时间并放入调度队列。
- 进程唤醒:当一个等待的进程被唤醒时,CFS 会重新评估该进程的虚拟运行时间,并决定是否需要调度该进程执行。
二、 CFS完全公平调度器
2.1、 CFS调度器类fair_sched_class
CFS完全公平调度器的调度器类叫fair_sched_class, 它是我们熟知的是struct sched_class调度器类类型, 将我们的CFS调度器与一些特定的函数关联起来。kernel/sched/fair.c
/*
* All the scheduling class methods:
*/
const struct sched_class fair_sched_class = {
.next = &idle_sched_class, /* 下个优先级的调度类, 所有的调度类通过next链接在一个链表中*/
.enqueue_task = enqueue_task_fair,
.dequeue_task = dequeue_task_fair,
.yield_task = yield_task_fair,
.yield_to_task = yield_to_task_fair,
.check_preempt_curr = check_preempt_wakeup,
.pick_next_task = pick_next_task_fair,
.put_prev_task = put_prev_task_fair,
#ifdef CONFIG_SMP
.select_task_rq = select_task_rq_fair,
.migrate_task_rq = migrate_task_rq_fair,
.rq_online = rq_online_fair,
.rq_offline = rq_offline_fair,
.task_waking = task_waking_fair,
.task_dead = task_dead_fair,
.set_cpus_allowed = set_cpus_allowed_common,
#endif
.set_curr_task = set_curr_task_fair,
.task_tick = task_tick_fair,
.task_fork = task_fork_fair,
.prio_changed = prio_changed_fair,
.switched_from = switched_from_fair,
.switched_to = switched_to_fair,
.get_rr_interval = get_rr_interval_fair,
.update_curr = update_curr_fair,
#ifdef CONFIG_FAIR_GROUP_SCHED
.task_move_group = task_move_group_fair,
#endif
};
| 成员 | 描述 |
|---|---|
| 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 调度模块使用它进行组调度,而用于实时任务的调度模块则不会使用这个函数 |
2.2、 CFS的就绪队列
就绪队列是全局调度器许多操作的起点, 但是进程并不是由就绪队列直接管理的, 调度管理是各个调度器的职责, 因此在各个就绪队列中嵌入了特定调度类的子就绪队列(cfs的顶级调度就队列 struct cfs_rq, 实时调度类的就绪队列struct rt_rq和deadline调度类的就绪队列struct dl_rq。
/* CFS-related fields in a runqueue */
/* CFS调度的运行队列,每个CPU的rq会包含一个cfs_rq,而每个组调度的sched_entity也会有自己的一个cfs_rq队列 */
struct cfs_rq {
/* CFS运行队列中所有进程的总负载 */
struct load_weight load;
/*
* nr_running: cfs_rq中调度实体数量
* h_nr_running: 只对进程组有效,其下所有进程组中cfs_rq的nr_running之和
*/
unsigned int nr_running, h_nr_running;
u64 exec_clock;
/*
* 当前CFS队列上最小运行时间,单调递增
* 两种情况下更新该值:
* 1、更新当前运行任务的累计运行时间时
* 2、当任务从队列删除去,如任务睡眠或退出,这时候会查看剩下的任务的vruntime是否大于min_vruntime,如果是则更新该值。
*/
u64 min_vruntime;
#ifndef CONFIG_64BIT
u64 min_vruntime_copy;
#endif
/* 该红黑树的root */
struct rb_root tasks_timeline;
/* 下一个调度结点(红黑树最左边结点,最左边结点就是下个调度实体) */
struct rb_node *rb_leftmost;
/*
* 'curr' points to currently running entity on this cfs_rq.
* It is set to NULL otherwise (i.e when none are currently running).
* curr: 当前正在运行的sched_entity(对于组虽然它不会在cpu上运行,但是当它的下层有一个task在cpu上运行,那么它所在的cfs_rq就把它当做是该cfs_rq上当前正在运行的sched_entity)
* next: 表示有些进程急需运行,即使不遵从CFS调度也必须运行它,调度时会检查是否next需要调度,有就调度next
*
* skip: 略过进程(不会选择skip指定的进程调度)
*/
struct sched_entity *curr, *next, *last, *skip;
#ifdef CONFIG_SCHED_DEBUG
unsigned int nr_spread_over;
#endif
#ifdef CONFIG_SMP
/*
* CFS load tracking
*/
struct sched_avg avg;
u64 runnable_load_sum;
unsigned long runnable_load_avg;
#ifdef CONFIG_FAIR_GROUP_SCHED
unsigned long tg_load_avg_contrib;
#endif
atomic_long_t removed_load_avg, removed_util_avg;
#ifndef CONFIG_64BIT
u64 load_last_update_time_copy;
#endif
#ifdef CONFIG_FAIR_GROUP_SCHED
/*
* h_load = weight * f(tg)
*
* Where f(tg) is the recursive weight fraction assigned to
* this group.
*/
unsigned long h_load;
u64 last_h_load_update;
struct sched_entity *h_load_next;
#endif /* CONFIG_FAIR_GROUP_SCHED */
#endif /* CONFIG_SMP */
#ifdef CONFIG_FAIR_GROUP_SCHED
/* 所属于的CPU rq */
struct rq *rq; /* cpu runqueue to which this cfs_rq is attached */
/*
* leaf cfs_rqs are those that hold tasks (lowest schedulable entity in
* a hierarchy). Non-leaf lrqs hold other higher schedulable entities
* (like users, containers etc.)
*
* leaf_cfs_rq_list ties together list of leaf cfs_rq's in a cpu. This
* list is used during load balance.
*/
int on_list;
struct list_head leaf_cfs_rq_list;
/* 拥有该CFS运行队列的进程组 */
struct task_group *tg; /* group that "owns" this runqueue */
#ifdef CONFIG_CFS_BANDWIDTH
int runtime_enabled;
u64 runtime_expires;
s64 runtime_remaining;
u64 throttled_clock, throttled_clock_task;
u64 throttled_clock_task_time;
int throttled, throttle_count;
struct list_head throttled_list;
#endif /* CONFIG_CFS_BANDWIDTH */
#endif /* CONFIG_FAIR_GROUP_SCHED */
};
| 成员 | 描述 |
|---|---|
| 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指定的进程调度) |
1831

被折叠的 条评论
为什么被折叠?



