3、实时调度策略概览:SCHED_FIFO、SCHED_RR、SCHED_DEADLINE、SCHED_OTHER与SCHED_BATCH对比

嵌入式Linux内核提供了多种调度策略,以满足不同工作负载对实时性、公平性和吞吐量的需求。本章将系统对比五种核心调度策略:SCHED_FIFO、SCHED_RR、SCHED_DEADLINE、SCHED_OTHER 和 SCHED_BATCH,帮助您在实际项目中做出正确的策略选择。

3.1 调度策略分类与适用场景

Linux调度器将进程分为两大类:实时进程(使用SCHED_FIFO、SCHED_RR、SCHED_DEADLINE)和普通进程(使用SCHED_OTHER、SCHED_BATCH、SCHED_IDLE)。实时进程拥有更高的优先级,确保关键任务获得确定的CPU时间。

核心原则: 实时调度策略保证高优先级任务优先执行,但必须谨慎设计,防止低优先级任务“饿死”。
选择指南: 硬实时任务(如电机控制)首选SCHED_DEADLINE;软实时周期性任务(如音频处理)可选SCHED_FIFO或SCHED_RR;批处理任务(如日志压缩)使用SCHED_BATCH;交互式应用(如UI线程)使用SCHED_OTHER。

3.2 五种调度策略核心对比

下表从优先级范围、调度规则、时间片、适用场景等维度进行横向对比:

策略 优先级范围 调度规则 时间片 典型场景
SCHED_FIFO 1~99(实时) 先入先出,运行至阻塞或主动让出 无(无限) 中断处理、高优先级控制循环
SCHED_RR 1~99(实时) 轮转调度,时间片耗尽后同优先级轮换 默认100ms(可调) 多路实时数据采集、软实时多媒体
SCHED_DEADLINE 0(内部映射) 基于Earliest Deadline First(EDF) 由Runtime/Deadline/Period参数决定 硬实时控制、自动驾驶感知融合
SCHED_OTHER 0~39(nice值) 完全公平调度(CFS),按权重分配 动态计算 普通用户进程、Shell交互
SCHED_BATCH 0~39(nice值) 类似CFS,但偏向批处理,减少唤醒抢占 动态计算(较长) 后台编译、数据转码、日志处理

3.3 调度策略层次结构图

下图展示了Linux内核中调度策略的层次关系与优先级映射:

Linux调度策略层次结构 Linux 调度策略层次结构 Linux 调度器 (CFS + RT) 实时调度域 (RT) 普通调度域 (NORMAL) SCHED_FIFO SCHED_RR SCHED_DEADLINE SCHED_OTHER SCHED_BATCH 优先级说明 实时域优先级范围 1~99(数值越大优先级越高),普通域通过 nice 值(-20~19)调整权重

3.4 各策略详细解析

3.4.1 SCHED_FIFO(先入先出)

SCHED_FIFO是最简单的实时调度策略。一旦获得CPU,任务会一直运行直到:主动调用sched_yield()、被更高优先级任务抢占、或进入阻塞状态(如等待I/O)。同优先级任务按就绪队列顺序执行。

// 设置SCHED_FIFO策略,优先级50
struct sched_param param;
param.sched_priority = 50;
if (sched_setscheduler(0, SCHED_FIFO, &param) == -1) {
    perror("sched_setscheduler failed");
}
风险提示: SCHED_FIFO任务如果陷入死循环且不主动让出CPU,将永久阻塞同优先级或更低优先级的任务。务必在实时任务中设计合理的“让步点”或使用SCHED_RR。

3.4.2 SCHED_RR(轮转调度)

SCHED_RR在SCHED_FIFO基础上增加了时间片轮转机制。同优先级的多个RR任务会轮流获得CPU时间片(默认100ms),避免单个任务独占CPU。时间片耗尽后,任务被放到同优先级队列尾部。

// 设置SCHED_RR策略,优先级80,时间片100ms
struct sched_param param;
param.sched_priority = 80;
if (sched_setscheduler(0, SCHED_RR, &param) == -1) {
    perror("sched_setscheduler failed");
}

// 查看当前时间片长度(单位:ns)
int time_slice = sched_rr_get_interval(0, NULL);
printf("RR time slice: %d ns\n", time_slice);

3.4.3 SCHED_DEADLINE(截止时间调度)

SCHED_DEADLINE基于EDF(Earliest Deadline First)算法,是Linux 3.14引入的硬实时调度策略。任务需指定三个参数:Runtime(每次执行所需CPU时间)、Deadline(相对截止时间)、Period(任务周期)。调度器保证在截止时间前完成执行。

// 设置SCHED_DEADLINE参数
// Runtime=10ms, Deadline=30ms, Period=30ms
struct sched_attr attr;
memset(&attr, 0, sizeof(attr));
attr.size = sizeof(attr);
attr.sched_policy = SCHED_DEADLINE;
attr.sched_runtime = 10 * 1000 * 1000;   // 10ms (ns)
attr.sched_deadline = 30 * 1000 * 1000;  // 30ms (ns)
attr.sched_period = 30 * 1000 * 1000;    // 30ms (ns)

if (sched_setattr(0, &attr, 0) == -1) {
    perror("sched_setattr failed");
}
关键指标: 必须满足 Runtime ≤ Deadline ≤ Period,且CPU利用率 = Runtime / Period ≤ 1(单核)。否则调度器可能无法保证所有任务满足截止时间。

3.4.4 SCHED_OTHER(完全公平调度)

SCHED_OTHER是Linux默认调度策略,基于CFS(Completely Fair Scheduler)算法。CFS维护一个红黑树,按虚拟运行时间(vruntime)排序,每次选择vruntime最小的任务运行。nice值用于调整任务权重,nice值越低,获得的CPU时间越多。

// 设置SCHED_OTHER策略,nice值-5(较高优先级)
struct sched_param param;
param.sched_priority = 0;  // SCHED_OTHER必须为0
if (sched_setscheduler(0, SCHED_OTHER, &param) == -1) {
    perror("sched_setscheduler failed");
}

// 调整nice值
if (setpriority(PRIO_PROCESS, 0, -5) == -1) {
    perror("setpriority failed");
}

3.4.5 SCHED_BATCH(批处理调度)

SCHED_BATCH专为CPU密集型批处理任务设计。它与SCHED_OTHER共享CFS基础,但调度器会减少唤醒抢占行为,让批处理任务运行更长时间,降低上下文切换开销。SCHED_BATCH任务在唤醒时不会立即抢占当前任务,而是等待当前任务时间片用完。

// 设置SCHED_BATCH策略
struct sched_param param;
param.sched_priority = 0;
if (sched_setscheduler(0, SCHED_BATCH, &param) == -1) {
    perror("sched_setscheduler failed");
}

3.5 调度策略选择决策流程

以下SVG流程图帮助您根据任务特性快速选择调度策略:

调度策略选择决策流程 调度策略选择决策流程 任务特性分析 是否实时? 是 否 实时调度域 普通调度域 硬实时? 是 SCHED_DEADLINE 否 多任务同优先级? 是 SCHED_RR 否 SCHED_FIFO 批处理? 是 SCHED_BATCH 否 SCHED_OTHER 提示: SCHED_IDLE(未列出)用于极低优先级后台任务,仅在CPU空闲时运行

3.6 调度策略切换与优先级继承

在实际嵌入式系统中,任务可能需要在不同调度策略间切换。例如,一个普通任务在进入关键代码段时临时提升为实时优先级。Linux提供了sched_setscheduler()系统调用实现动态切换。

优先级反转问题: 当低优先级任务持有高优先级任务所需的锁时,会发生优先级反转。解决方案包括:
- 使用 优先级继承协议(PI Futex)
- 使用 优先级天花板协议
- 避免实时任务与非实时任务共享锁
// 动态切换策略示例:从SCHED_OTHER切换到SCHED_FIFO
struct sched_param param;
param.sched_priority = 60;

// 进入临界区前提升优先级
if (sched_setscheduler(0, SCHED_FIFO, &param) == -1) {
    perror("Failed to enter real-time mode");
}

// ... 执行关键操作 ...

// 退出临界区后恢复普通调度
param.sched_priority = 0;
if (sched_setscheduler(0, SCHED_OTHER, &param) == -1) {
    perror("Failed to leave real-time mode");
}

3.7 性能指标对比总结

指标 SCHED_FIFO SCHED_RR SCHED_DEADLINE SCHED_OTHER SCHED_BATCH
最坏情况延迟 极低(μs级) 低(受时间片影响) 可预测(满足截止时间) 高(ms级) 高(ms级)
调度开销 低 低 中等(EDF计算) 中等(CFS红黑树) 低(减少抢占)
公平性 无(按优先级) 同优先级公平 按截止时间 完全公平 偏向批处理
适用CPU核数 单核/多核 单核/多核 单核/多核 单核/多核 单核/多核
配置复杂度 低(仅优先级) 低(优先级+时间片) 高(三个时间参数) 低(nice值) 低(nice值)

3.8 实践建议

  • 硬实时任务(如电机FOC控制、激光雷达数据采集):优先使用SCHED_DEADLINE,确保截止时间可预测。
  • 软实时周期性任务(如音频编码、传感器融合):使用SCHED_FIFO或SCHED_RR,优先级根据周期和重要性分配。
  • 非实时后台任务(如日志写入、固件升级):使用SCHED_BATCH,减少对实时任务的干扰。
  • 交互式任务(如命令行、监控UI):使用SCHED_OTHER,配合nice值调整响应速度。
  • 避免混合使用:实时任务和非实时任务应尽量隔离到不同CPU核,或使用cgroup进行资源限制。
验证命令: 使用 chrt -p <pid> 查看进程当前调度策略和优先级;使用 chrt -f 50 ./my_realtime_app 以SCHED_FIFO优先级50启动程序。