引言

进程调度是操作系统最核心的组件之一,它决定了哪个进程在何时获得 CPU 时间片。Linux 内核调度器经过二十余年的演进,从早期的 O(n) 调度器、O(1) 调度器,到 2007 年引入的 CFS(完全公平调度器),再到 2023 年 Linux 6.6 版本中被 EEVDF(Earliest Eligible Virtual Deadline First)取代,每一次变革都在解决实际生产环境中的调度延迟与公平性问题。

本文将从调度器理论基础出发,深入剖析 CFS 的实现原理、实时调度类的工作机制、EEVDF 的核心创新,并提供从内核参数调优到应用程序绑核的全链路实战指南。

一、Linux 调度器架构总览

1.1 调度策略与优先级模型

Linux 内核定义了五种调度策略,按优先级从高到低排列如下:

调度策略类型说明
SCHED_DEADLINE实时 - 截止时间调度基于 EDF(最早截止时间优先),Linux 3.14
SCHED_FIFO实时 - 先进先出高优先级可抢占低优先级,无时间片限制
SCHED_RR实时 - 轮转类似 FIFO 但同优先级按时间片轮转
SCHED_OTHER / SCHED_NORMAL普通 - CFS默认策略,CFS 完全公平调度
SCHED_IDLE普通 - 空闲仅当无其他进程时才运行

内核中调度类的优先级通过链表串联:stop_sched_class → dl_sched_class → rt_sched_class → fair_sched_class → idle_sched_class。每个 CPU 的 runqueue 按此顺序遍历,确保高优先级调度类先获得调度机会。

1.2 调度器的核心数据结构

在 6.x 内核中,每个 CPU 的运行队列 struct rq 包含多个子队列:

struct rq {
    struct cfs_rq    cfs;       // CFS 运行队列
    struct rt_rq     rt;        // 实时运行队列
    struct dl_rq     dl;        // DEADLINE 运行队列
    struct task_struct *curr;   // 当前运行进程
    struct task_struct *idle;   // idle 进程
    unsigned int     nr_running; // 可运行进程数
    // ...
};

二、CFS 完全公平调度器深度解析

2.1 vruntime 与红黑树机制

CFS 的核心思想是维护每个进程的虚拟运行时间(vruntime),选择 vruntime 最小的进程运行。所有可运行进程按照 vruntime 值存储在一棵红黑树(rbtree)中,最左端节点即为最应该被调度的进程。

// 内核中 vruntime 的计算公式
void update_curr(struct cfs_rq *cfs_rq) {
    struct sched_entity *curr = cfs_rq->curr;
    u64 now = rq_clock_task(rq_of(cfs_rq));
    u64 delta_exec;
    
    delta_exec = now - curr->exec_start; // 实际执行时间
    curr->exec_start = now;
    curr->sum_exec_runtime  = delta_exec;
    
    // 根据进程权重将实际时间转换为虚拟时间
    curr->vruntime  = calc_delta_fair(delta_exec, curr);
    update_min_vruntime(cfs_rq);
}

关键设计:nice 值每降低 1 级(优先级提高),获得 1.25 倍的 CPU 时间权重。这是因为内核使用 1.25^n 的权重比例表(sched_prio_to_weight[]),nice 0 的权重为 1024,nice -1 的权重为 1277,比值恰好约为 1.25。

2.2 调度粒度与延迟控制

CFS 通过三个 sysctl 参数控制调度行为:

# 调度最小粒度(每次切换的最小时间片)
kernel.sched_latency_ns = 24000000       # 24ms(默认)
kernel.sched_min_granularity_ns = 3000000  # 3ms
kernel.sched_wakeup_granularity_ns = 4000000 # 4ms

# 调度周期 = max(latency_ns, min_granularity * nr_running)
// 当进程数过多时,周期自动扩展,避免频繁切换

wakeup_granularity 是新进程唤醒抢占的重要参数:只有当新进程的 vruntime 比当前进程小超过该阈值时才发生抢占,防止过于频繁的上下文切换。

2.3 CFS 组调度(Group Scheduling)

CFS 通过 CONFIG_FAIR_GROUP_SCHED 支持组调度,允许将 CPU 时间按比例分配给进程组。组调度的核心数据结构是嵌套的 sched_entity:每个进程是一个 sched_entity,cgroup 本身也是一个 sched_entity,形成多层级的调度树。

cgroup v1 中通过 cpu.shares 控制组的 CPU 权重比例,cpu.cfs_quota_us 限制组的 CPU 使用上限:

# 创建一个 cgroup,分配 50% CPU 时间
mkdir /sys/fs/cgroup/cpu/mygroup
echo 512 > /sys/fs/cgroup/cpu/mygroup/cpu.shares    # 512/1024 = 50%
echo 50000 > /sys/fs/cgroup/cpu/mygroup/cpu.cfs_quota_us  # 每 100ms 周期内最多用 50ms
echo 100000 > /sys/fs/cgroup/cpu/mygroup/cpu.cfs_period_us

三、实时调度类详解

3.1 SCHED_FIFO 与 SCHED_RR

实时进程的优先级数值为 0-99(数值越大优先级越高),永远优先于普通进程。使用 chrt 命令可以设置进程的实时调度策略:

# 将进程 1234 设为 SCHED_FIFO,优先级 80
sudo chrt -f 80 -p 1234

# 启动新进程为 SCHED_RR,优先级 50
sudo chrt -r 50 ./my_rt_program

# 查看进程调度策略
chrt -p 1234

SCHED_FIFO 的特点:一旦获得 CPU,除非主动阻塞(如等待 I/O、睡眠)、更高优先级实时进程抢占、或主动调用 sched_yield(),否则永远运行下去。这也是为什么编写不当时可能导致系统锁死。

3.2 SCHED_DEADLINE — 基于 EDF 的硬实时调度

Linux 3.14 引入的 SCHED_DEADLINE 是最精确的实时调度策略,每个进程声明三个参数:

  • runtime (Q):每次激活最多运行的时间
  • deadline (D):每次激活必须完成的截止时间
  • period (P):激活周期的长度(Q ≤ D ≤ P)
// 使用 SCHED_DEADLINE 的代码示例
#include                        
                    
点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿
网站二维码

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部
/* 跳过导航链接 (无障碍) */ .skip-link { position: absolute; top: -100px; left: 15px; z-index: 99999; padding: 8px 16px; background: #007bff; color: #fff; font-size: 14px; border-radius: 0 0 4px 4px; text-decoration: none; transition: top 0.2s; } .skip-link:focus { top: 0; outline: 3px solid #0056b3; }