1. 进程调度概述
1.1 进程与线程概念
进程是操作系统进行资源分配和调度的基本单位,是程序在某个数据集合上的一次执行活动。每个进程拥有独立的地址空间、文件描述符表、信号处理表等资源。线程是进程内的执行流,是CPU调度的最小单位,同一进程内的线程共享地址空间和大部分资源。
| 对比维度 | 进程 | 线程 |
|---|---|---|
| 资源拥有 | 独立地址空间、堆栈、文件描述符 | 共享进程地址空间,仅独立栈和寄存器 |
| 创建开销 | 大(需复制页表、文件表等) | 小(仅分配TCB和栈) |
| 上下文切换 | 慢(涉及地址空间切换、TLB刷新) | 快(同一进程内无需切换地址空间) |
| 通信方式 | IPC(管道、消息队列、共享内存) | 直接读写共享内存 |
| 调度粒度 | 进程级调度 | 线程级调度 |
| 内核视角 | task_struct 实例 | task_struct 实例(Linux线程是轻量级进程) |
Linux 实现特点: Linux 内核并不严格区分进程和线程,两者均使用
task_struct 表示。线程通过 clone() 系统调用创建,共享资源由 clone_flags 控制。这种设计使得调度器对进程和线程采用统一的调度策略。
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)
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(最早虚拟截止时间优先) | 更精确的延迟控制,改进交互性 | 仍在演进中 |
关键转折点: 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) 即可选择下一个运行进程,无需关心具体调度算法实现。这种设计使得添加新的调度策略变得非常容易。