1. 进程调度概述

1.1 进程与线程的本质区别

在 Linux 内核中,进程是资源分配的最小单位,线程是 CPU 调度的最小单位。两者共享同一个内核任务结构 task_struct,但资源视图不同。

对比维度 进程 (Process) 线程 (Thread)
资源拥有 独立地址空间、文件描述符表、信号处理表 共享所属进程的地址空间和资源
创建开销 高(需要复制页表、文件表等) 低(仅分配栈和寄存器上下文)
调度实体 内核以进程/线程统一视为可调度实体 内核以进程/线程统一视为可调度实体
上下文切换 涉及地址空间切换(TLB 刷新) 不涉及地址空间切换(同一进程内)
内核视角 task_struct 独立存在 task_struct 共享 mm_struct
关键理解:Linux 内核并不区分进程和线程的调度优先级——两者都通过 task_struct 参与调度。区别仅在于资源隔离程度。调度器面对的是“可调度实体”(sched_entity),而非进程或线程的概念。

1.2 调度器在操作系统中的位置

调度器位于内核核心层,介于硬件中断与系统调用之间,是 CPU 资源分配的中枢。其上下层关系如下:

调度器在操作系统中的位置 用户态进程 / 线程 应用程序、服务、用户交互任务 系统调用接口(syscall) 内核核心层 进程管理 · 内存管理 · 文件系统 · 网络栈 ★ 调度器(sched/core.c) 硬件抽象层(中断 · 时钟 · 上下文切换) CPU 核心 · 缓存 · 内存控制器
调度器核心职责:在时钟中断或任务主动让出 CPU 时,从就绪队列中选出下一个要运行的 task_struct,执行上下文切换,恢复其寄存器状态和页表,使该任务继续执行。

1.3 调度器的三大核心目标

Linux 调度器设计始终围绕三个有时相互冲突的目标进行权衡:

  1. 公平(Fairness) — 每个可调度实体应获得与其优先级/权重成比例的 CPU 时间,避免饥饿。
  2. 高效(Efficiency) — 调度开销(上下文切换、缓存污染)最小化,吞吐量最大化。
  3. 低延迟(Low Latency) — 交互式任务(键盘输入、鼠标移动)能快速响应,减少调度延迟。

这三个目标在 CFS(完全公平调度器)中通过虚拟运行时间(vruntime)机制统一实现:

调度器核心目标三角与 CFS 实现 公平 每个任务获得公平份额 高效 最小化调度开销 低延迟 快速响应交互任务 CFS 调度器实现机制 红黑树按 vruntime 排序 → 始终选择最左侧(最小 vruntime)任务 nice 值 → 权重 → 虚拟运行时间缩放 → 兼顾公平与优先级 调度延迟 / 最小粒度 → 控制上下文切换频率 → 平衡效率与延迟

1.4 调度器核心目标权衡矩阵

实际系统中,三个目标无法同时达到最优,需要根据负载特征进行取舍:

场景 优先目标 调度策略倾向 典型参数
批处理 / 科学计算 高效(吞吐量) SCHED_NORMAL + 较大调度延迟 sched_latency_ns 调大
桌面 / 交互式应用 低延迟 SCHED_NORMAL + 较小调度延迟 sched_latency_ns 调小
实时音视频 低延迟 + 确定性 SCHED_FIFO / SCHED_RR 实时优先级 80-99
虚拟化宿主机 公平 + 高效 CFS + CPU pinning cpu_shares 权重分配
常见误区:追求“绝对公平”会导致频繁上下文切换,降低吞吐量;追求“极致低延迟”可能导致 CPU 密集型任务被过度抢占。CFS 通过可配置的 sched_latency_ns 和 sched_min_granularity_ns 提供调优空间,让管理员根据负载特征在三个目标间滑动。

1.5 调度器核心数据结构关系

调度器在运行时依赖以下关键数据结构协同工作:

  • task_struct — 每个任务的描述符,包含调度策略、优先级、vruntime、调度实体等字段。
  • sched_entity — 调度实体,嵌入在 task_struct 中,是 CFS 红黑树的节点。
  • cfs_rq — 每个 CPU 核心一个的就绪队列,维护红黑树根节点和运行统计。
  • rq — 每个 CPU 核心的全局运行队列,包含 cfs_rq、rt_rq、dl_rq 等子队列。
调度决策路径:时钟中断 → scheduler_tick() → 更新当前任务 vruntime → 检查是否需要抢占 → 若需要则设置 TIF_NEED_RESCHED → 返回用户态或中断结束时 → schedule() → pick_next_task() → 从 cfs_rq 红黑树选最左节点 → context_switch()。