Linux-进程的管理与调度22(基于6.1内核)

Linux-进程的管理与调度22(基于6.1内核)---Linux进程调度CFS简介

一、 CFS概述

CFS(Completely Fair Scheduler)是 Linux 内核中的默认调度器,旨在提供公平的 CPU 时间分配。它的主要目标是确保系统中所有进程根据其需求公平地共享 CPU 资源,并通过一个高效的调度算法确保系统的响应性和吞吐量。

CFS 的设计目标:

  1. 公平性(Fairness):CFS 设计的核心目标是保证每个进程(或者说任务)公平地获取 CPU 时间,避免某些进程长时间占用 CPU 资源,而其他进程被“饿死”。
  2. 低延迟(Low Latency):调度器需要快速响应新的任务和请求,确保进程之间的切换不会引入过高的延迟。
  3. 高吞吐量(High Throughput):同时,调度器也应当尽量保证高效执行,减少不必要的调度开销,以提升整体吞吐量。

CFS 的主要特性:

CFS 的工作方式与传统的时间片轮转(Round Robin)调度器不同,它并不是简单地给每个进程分配固定时间片,而是通过一个更为复杂的算法来计算进程的“虚拟运行时间”,并依据此时间来决定哪个进程应该执行。

1. 虚拟运行时间(Virtual Runtime)

  • CFS 通过维护每个进程的虚拟运行时间(vruntime)来进行调度。虚拟运行时间是一个不断增长的值,反映了进程运行的多少。每当进程运行时,其虚拟运行时间会根据进程的优先级(权重)增加。虚拟运行时间较小的进程被认为需要更多的 CPU 时间,因此它们会被优先调度。
  • 进程的虚拟运行时间是一个加权值,进程的优先级越高(权重越大),其虚拟运行时间的增加速度就越慢。这样,优先级高的进程会相对较长时间地占用 CPU,直到其虚拟运行时间增加到一定程度。

2. 红黑树(Red-Black Tree)

  • CFS 使用一个 红黑树 来存储所有可运行的进程。红黑树是一种自平衡的二叉搜索树,能够提供对进程的快速插入、删除和查找操作。
  • 树中的每个节点代表一个进程,节点的排序依据是进程的虚拟运行时间。虚拟运行时间较小的进程被放在树的左侧,因此这些进程会被优先选择执行。

3. 调度过程

  • 每当调度器决定调度下一个进程时,CFS 会从红黑树中选择虚拟运行时间最小的进程进行执行。这个进程就会被分配到 CPU 上。
  • 如果当前进程的虚拟运行时间最小,它会继续运行,直到它的虚拟时间达到一定的阈值或者被新的任务抢占。

4. 动态调整(动态权重)

  • 进程的权重(即优先级)是动态调整的。CFS 不依赖固定的优先级,而是通过 nice 值和负载来动态计算进程的权重。
  • nicenice 值决定了进程的优先级,数值越大,优先级越低,进程的虚拟运行时间增加得越快,反之亦然。

5. 多核处理器优化

  • 在多核系统中,CFS 会尽量将进程分配到不同的 CPU 核心上,以提高系统的并行性和性能。CFS 会尝试保持进程在同一个 CPU 核心上运行,以减少进程的上下文切换和缓存失效。

CFS 与传统调度器的对比:

CFS 相比于传统的基于时间片轮转的调度器有以下几个显著优势:

  • 公平性:CFS 基于虚拟运行时间来进行调度,避免了传统时间片调度器可能导致的某些进程被长期饿死的情况。
  • 无固定时间片:CFS 不需要为每个进程分配固定的时间片,而是根据进程的运行情况动态调整 CPU 时间的分配。
  • 高效:使用红黑树结构和虚拟运行时间算法,CFS 能够高效地管理进程调度,避免了不必要的调度开销。

CFS 工作流程简述:

  1. 新进程加入:当一个新进程加入系统时,它会被插入到红黑树中,初始时其虚拟运行时间为 0。
  2. 进程执行:当一个进程被调度执行时,CFS 会按照虚拟运行时间的顺序选择下一个进程。在执行期间,该进程的虚拟运行时间会不断增加。
  3. 进程阻塞:如果进程发生阻塞(例如等待 I/O 操作),CFS 会将它移出红黑树,直到进程恢复运行后重新计算虚拟运行时间并放入调度队列。
  4. 进程唤醒:当一个等待的进程被唤醒时,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指定的进程调度)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值