1. 进程调度概述

1.1 进程与线程概念

进程是操作系统进行资源分配和调度的基本单位,是程序在某个数据集合上的一次执行活动。每个进程拥有独立的地址空间、文件描述符表、信号处理表等资源。线程是进程内的执行流,是CPU调度的最小单位,同一进程内的线程共享地址空间和大部分资源。

对比维度 进程 线程
资源拥有 独立地址空间、堆栈、文件描述符 共享进程地址空间,仅独立栈和寄存器
创建开销 大(需复制页表、文件表等) 小(仅分配TCB和栈)
上下文切换 慢(涉及地址空间切换、TLB刷新) 快(同一进程内无需切换地址空间)
通信方式 IPC(管道、消息队列、共享内存) 直接读写共享内存
调度粒度 进程级调度 线程级调度
内核视角 task_struct 实例 task_struct 实例(Linux线程是轻量级进程)
Linux 实现特点: Linux 内核并不严格区分进程和线程,两者均使用 task_struct 表示。线程通过 clone() 系统调用创建,共享资源由 clone_flags 控制。这种设计使得调度器对进程和线程采用统一的调度策略。
进程与线程内存布局对比 进程 A 代码段 数据段 堆 栈 文件描述符表 信号处理表 页表 独立地址空间 进程 B 代码段 数据段 堆 栈 文件描述符表 信号处理表 页表 独立地址空间 进程 C(多线程) 共享代码段 共享数据段 共享堆 共享文件表 线程1 栈 线程2 栈 线程3 栈 共享地址空间

1.2 调度器的作用

进程调度器是内核的核心子系统之一,负责在多个可运行进程之间分配CPU时间,决定下一个获得CPU的进程以及运行多长时间。调度器的设计直接影响系统的吞吐量、响应时间、公平性和功耗。

  • CPU 资源分配: 在多个进程间公平分配CPU时间片,避免进程饥饿。
  • 上下文切换: 保存当前进程的寄存器、PC、栈指针等状态,恢复目标进程的状态。
  • 负载均衡: 在多核系统中,将进程均匀分配到各个CPU核心,最大化并行效率。
  • 响应性保障: 对交互式进程(如编辑器、终端)优先调度,降低延迟。
  • 功耗管理: 在移动设备中,结合CPU调频策略,在性能与功耗间取得平衡。
调度器核心目标: 在公平性、吞吐量、响应时间、实时性和功耗五个维度间寻找最优权衡。不同场景(服务器、桌面、嵌入式、实时系统)对上述指标的权重完全不同。

1.3 调度策略分类

根据调度目标和应用场景,调度策略可分为以下三大类:

分类 典型算法 核心目标 适用场景
通用调度 CFS(完全公平调度) 公平分配CPU时间,O(log n)复杂度 桌面、服务器、通用Linux
实时调度 FIFO、RR(轮转)、EDF 满足任务截止时间,可抢占 工业控制、音视频、嵌入式
批处理调度 FCFS(先来先服务)、SJF(短作业优先) 最大化吞吐量,减少周转时间 科学计算、大数据处理

Linux 内核同时支持通用调度和实时调度策略,通过 sched_setscheduler() 系统调用为每个进程指定调度策略:

// Linux 支持的调度策略宏定义(include/uapi/linux/sched.h)
#define SCHED_NORMAL    0   // CFS 通用调度
#define SCHED_FIFO      1   // 实时 FIFO
#define SCHED_RR        2   // 实时轮转
#define SCHED_BATCH     3   // 批处理调度
#define SCHED_IDLE      5   // 空闲调度
#define SCHED_DEADLINE  6   // 截止时间调度(EDF)
Linux 调度策略分类树 Linux 调度策略 通用调度 实时调度 空闲/批处理 SCHED_NORMAL SCHED_BATCH SCHED_IDLE SCHED_FIFO SCHED_RR SCHED_DEADLINE SCHED_BATCH SCHED_IDLE SCHED_NORMAL 实时调度策略优先级(0-99)始终高于通用调度(优先级100-139)

1.4 Linux 调度器演进历史

Linux 调度器经历了多次重大重构,每一次演进都针对特定场景的瓶颈进行了优化:

内核版本 调度器名称 核心算法 主要特点 局限性
0.01 - 2.4 O(1) 调度器前身 循环链表 + 优先级队列 简单实现,支持SMP 大负载下性能差,缺乏公平性
2.6.0 - 2.6.22 O(1) 调度器 双优先级位图 + 活跃/过期数组 O(1) 时间复杂度,SMP负载均衡 交互式进程识别不准,调参复杂
2.6.23 - 至今 CFS(完全公平调度器) 红黑树 + 虚拟运行时间 公平性极佳,无需时间片,O(log n) 实时性依赖RT组调度
3.14 - 至今 SCHED_DEADLINE EDF(最早截止时间优先) 硬实时能力,确定性调度 实现复杂,仅限实时任务
5.x - 至今 CFS + EEVDF EEVDF(最早虚拟截止时间优先) 更精确的延迟控制,改进交互性 仍在演进中
Linux 调度器演进时间线 1991 v0.01 2003 v2.6.0 2007 v2.6.23 2014 v3.14 2022+ v5.x+ 循环链表调度 简单优先级队列 O(1) 调度器 双数组位图 CFS 调度器 红黑树 + vruntime SCHED_DEADLINE EDF 算法 EEVDF 延迟优化 演进趋势:从简单循环链表 → O(1) 位图 → 红黑树 CFS → EDF 实时扩展 → EEVDF 延迟优化 每次演进都解决了前一代调度器在特定场景下的核心瓶颈
关键转折点: 2.6.23 版本引入的 CFS 调度器是 Linux 调度史上最重要的变革。它摒弃了传统的时间片和优先级队列,改用红黑树组织进程,以虚拟运行时间(vruntime)作为调度依据,从根本上解决了 O(1) 调度器交互式进程识别不准、调参复杂的问题。CFS 的设计哲学是“让每个进程都获得公平的 CPU 时间份额”,而非“给每个进程分配固定时间片”。

1.5 调度器核心数据结构概览

Linux 调度器的核心数据结构围绕 task_struct 和 sched_class 展开:

// 进程描述符中的调度相关字段(精简)
struct task_struct {
    /* 调度策略与优先级 */
    unsigned int            policy;         // SCHED_NORMAL/FIFO/RR/BATCH/IDLE/DEADLINE
    int                     prio;           // 动态优先级
    int                     static_prio;    // 静态优先级
    int                     normal_prio;    // 常规优先级

    /* CFS 调度实体 */
    struct sched_entity     se;             // CFS 调度实体,包含 vruntime

    /* 实时调度实体 */
    struct sched_rt_entity  rt;             // 实时调度实体

    /* 截止时间调度实体 */
    struct sched_dl_entity  dl;             // DEADLINE 调度实体

    /* 调度类指针 */
    const struct sched_class *sched_class;  // 指向当前使用的调度类
};

// 调度类接口(多态设计)
struct sched_class {
    void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
    void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
    void (*yield_task)   (struct rq *rq);
    void (*check_preempt_curr)(struct rq *rq, struct task_struct *p, int flags);
    struct task_struct *(*pick_next_task)(struct rq *rq);
    void (*put_prev_task)(struct rq *rq, struct task_struct *p);
    void (*set_curr_task)(struct rq *rq);
    void (*task_tick)    (struct rq *rq, struct task_struct *p, int queued);
};
设计模式: Linux 调度器采用策略模式(Strategy Pattern),通过 sched_class 结构体指针实现多态。每个调度策略(CFS、RT、DL)都实现一套 sched_class 接口,调度器核心代码只需调用 p->sched_class->pick_next_task(rq) 即可选择下一个运行进程,无需关心具体调度算法实现。这种设计使得添加新的调度策略变得非常容易。