1
2
3
4
5
6
7
作者:李晓辉

联系方式:

1. 微信:Lxh_Chat

2. 邮箱:939958092@qq.com
  1. Linux 性能调优系列(一) 建立科学的性能分析思路
  2. Linux 性能调优系列(二) 掌握基础性能监控命令
  3. Linux 性能调优系列(三) sysstat实现性能数据持久采集
  4. Linux 性能调优系列(四) 别只会用 sysstat,PCP 才是 Linux 性能监控的进阶玩法
  5. Linux 性能调优系列(五) 给你的服务器做一次全面体检
  6. Linux 性能调优系列(六) 想调内核参数?先搞懂 /proc、sysctl 和 sysfs
  7. Linux 性能调优系列(七) 还在手动改 sysctl?TuneD 智能性能调优实战来了
  8. Linux 性能调优系列(八) 进程太能吃资源怎么办?用 ulimit 和 systemd 给它加个"上限"
  9. Linux 性能调优系列(九) cgroup v2实战:防止一个烂进程干崩整台服务器
  10. Linux 性能调优系列(十) CPU 100% 到底是谁在忙?用 perf 把"元凶"揪出来
  11. Linux 性能调优系列(十一) 程序为什么卡住了?strace 和 ltrace 带你找到答案
  12. Linux 性能调优系列(十二) 服务器又卡了?SystemTap 带你杀到内核看看为啥
  13. Linux 性能调优系列(十三) 从 SystemTap 到 eBPF,Linux 性能追踪进入新时代
  14. Linux 性能调优系列(十四) CPU 这么忙,到底是谁决定“让谁先跑”?一文搞懂 Linux 调度器
  15. Linux 性能调优系列(十五) CPU 到底该先给谁?搞懂 Linux 实时调度
  16. Linux 性能调优系列(十六) Linux CPU 隔离实战,为什么实时线程绑核后延迟还会抖?
  17. Linux 性能调优系列(十七) CPU 占用不高却跑的慢?深挖 CPU 缓存性能瓶颈
  18. Linux 性能调优系列(十八) 程序申请了1GB内存,为什么系统却说它没用?
  19. Linux 性能调优系列(十九) 服务器硬盘慢得像蜗牛?先别急着换硬盘,这四招先把瓶颈找出来
  20. Linux 性能调优系列(二十) 磁盘 I/O 调度器怎么选?一文搞懂 mq-deadline、kyber、bfq 和 none
  21. Linux 性能调优系列(二十一) 磁盘为什么慢?从文件系统到 I/O 调优,一次讲透
  22. Linux 性能调优系列(二十二) Linux 网络调优别乱改 sysctl!先找瓶颈再调 TCP 缓冲区
  23. Linux 性能调优系列(二十三) MTU 从 1500 到 9000,巨帧真的能提升性能吗?
  24. Linux 性能调优系列(二十四) 从 CPU 省电到网卡卸载,聊聊 Linux 网络调优
  25. Linux 性能调优系列(二十五) 聊聊VirtIO、TuneD、virtio-mem 调优

上一篇我们学习了 CPU 调度类别、调度策略、EEVDF 公平调度器,以及 nice、renice、chrt 的基本用法。普通业务进程使用 SCHED_OTHER 通常就足够了。但是有些场景对延迟非常敏感,比如:

1
2
3
4
5
工业控制
数据采集
机器人
网络处理
多媒体处理

这些任务关心的并不是:

CPU 分得够不够公平?

而是:

关键任务能不能及时获得 CPU?

这时候,就需要:

Linux 实时调度。


什么是实时调度?

实时调度的目标,不是简单地追求”运行得最快”,而是:

让任务的响应时间更加可预测。

普通公平调度主要关心:

1
2
3
任务之间是否公平
系统吞吐量
交互响应

实时调度更加关注:

1
2
3
高优先级任务能不能及时运行?
任务的调度延迟是否可控?
任务能不能在规定时间内完成?

比如一个每 10ms 处理一次数据的采集任务:

1
2
3
4
第 1 次:8ms 完成
第 2 次:11ms 完成
第 3 次:7ms 完成
第 4 次:35ms 完成

平均值并不差,但第 4 次的 35ms 就可能已经造成事故。所以实时系统看的不是平均值,而是最坏情况。

Linux 中常见的实时相关调度策略包括:

1
2
3
SCHED_FIFO
SCHED_RR
SCHED_DEADLINE

其中:

  • SCHED_FIFO:固定优先级 + 先进先出;
  • SCHED_RR:固定优先级 + 时间片轮转;
  • SCHED_DEADLINE:根据 runtime、deadline、period 进行截止时间调度。
flowchart TD
    A[实时任务] --> B[SCHED_FIFO]
    A --> C[SCHED_RR]
    A --> D[SCHED_DEADLINE]

    B --> E[固定优先级<br/>FIFO]
    C --> F[固定优先级<br/>时间片轮转]
    D --> G[runtime<br/>deadline<br/>period]

先看全局:调度类之间谁能压谁?

在讲具体策略之前,先建立一个重要概念:Linux 内核里有”调度类”,类与类之间是绝对的上下级关系。

flowchart TB
    A["stop 类<br/>内核内部任务,如 CPU 迁移"] --> B["deadline 类<br/>SCHED_DEADLINE"]
    B --> C["rt 类<br/>SCHED_FIFO / SCHED_RR"]
    C --> D["fair 类<br/>SCHED_OTHER / BATCH"]
    D --> E["idle 类<br/>SCHED_IDLE"]

记住一句话:

deadline 类 > rt 类 > fair 类。

这不是 nice 那种”多分一点、少分一点”,而是:只要高一级的类里有任务可运行,低一级的类就得让路(除非触发后面要讲的限流或兜底机制)。


SCHED_FIFO:先进先出实时调度

SCHED_FIFO 是最简单的实时调度策略。它没有普通任务那种周期性的时间片轮转。

一个 SCHED_FIFO 线程获得 CPU 后,只要它:

1
2
3
没有阻塞
没有退出
没有主动让出 CPU(如 sched_yield)

就可以继续运行。但是,如果有更高实时优先级的线程变成可运行状态,它会立即抢占当前线程。同一实时优先级下,则按照 FIFO 顺序运行。

例如:

1
2
3
任务 A:priority 80
任务 B:priority 80
任务 C:priority 90

如果 A 正在运行,此时 C 变成 Runnable,C 会立即抢占 A。而 B 只能继续等待。

flowchart TD
    S["有 RT 任务变成可运行"] --> Q{"优先级比当前任务高?"}
    Q -- 是 --> P["立刻抢占"]
    Q -- 同优先级 --> T{"当前任务的策略?"}
    T -- SCHED_FIFO --> W["排队,直到对方主动让出"]
    T -- SCHED_RR --> R["对方时间片用完后轮到我"]
    Q -- 更低 --> L["继续等待"]

一个容易被忽略的前提:上面”B 只能继续等待”,指的是 A 和 B 在同一个 CPU 的运行队列上。在多核机器上,内核有 RT 任务的 push/pull 机制,B 往往会被迁移到别的空闲 CPU 上去跑。所以真正出问题的,经常是绑核或CPU 隔离的场景:B 没处可去,只能干等。

SCHED_FIFO 的风险

如果一个 SCHED_FIFO 任务长期不阻塞、不让出 CPU,同一 CPU 上其他低优先级任务就可能长时间得不到运行机会。

因此:

SCHED_FIFO 很强,但也很容易被滥用。


SCHED_RR:时间片轮转

SCHED_RR 和 SCHED_FIFO 非常相似。最大的区别就是:

同一实时优先级下,SCHED_RR 会进行时间片轮转。

例如三个 priority 50 的任务,会按 A → B → C → A → B → C 不断轮转。

特性SCHED_FIFOSCHED_RR
高优先级抢占支持支持
同优先级轮转不支持支持
同优先级多任务需要谨慎更适合
时间片无普通意义上的时间片有

注意:SCHED_RR 的时间片是实时调度类内部的机制,和 Fair 调度的时间片不是一回事。可以这样查看:

1
cat /proc/sys/kernel/sched_rr_timeslice_ms

默认通常是 100ms。

经验:拿不准的时候,同优先级多任务用 SCHED_RR,更不容易被某一个任务长期霸占。


实时优先级

Linux 实时调度策略使用 1 ~ 99 的实时优先级,99 最高,1 最低。

1
2
3
4
5
# 查看优先级范围
chrt -m

# 查看线程调度策略
ps -eo pid,tid,comm,rtprio,policy

输出里 FF 表示 SCHED_FIFO,RR 表示 SCHED_RR,TS 表示普通调度,此时 rtprio 显示 -。

1
2
3
PID     TID COMMAND         RTPRIO POL
19 19 migration/0 99 FF
20 20 idle_inject/0 50 FF

请注意上面这两行。内核自己的关键线程,比如 migration/N、watchdog/N,本身就跑在 99。

这就是为什么不建议业务任务设成 99:

你的任务如果也是 99,就会和负责 CPU 迁移、软锁死检测的内核线程平起平坐,甚至把它们挡在门外。系统一旦出问题,连”自救”的能力都会受影响。

**实时优先级不是越高越好。**建议:

  • 从较低优先级开始,确认存在延迟问题后再逐步提高;
  • 业务任务一般放在 10 ~ 80 之间,留出层次;
  • 把优先级设计写进文档,比如”数据采集 60 > 数据处理 50 > 辅助线程 40”,不要靠口头约定。

使用 chrt 管理实时任务

查看某个线程:

1
chrt -p 12345

设置已有线程为 SCHED_FIFO,优先级 50:

1
chrt -f -p 50 12345

设置已有线程为 SCHED_RR,优先级 40:

1
chrt -r -p 40 12345

恢复普通调度:

1
chrt -o -p 0 12345

如果是启动一个新程序,则不需要 -p:

1
2
chrt -f 50 ./my_realtime_task
chrt -r 40 ./my_realtime_task

**两种语法不能混用:**改已有进程是 -p <优先级> <PID>,启动新进程是 <优先级> <命令>。

子进程会继承实时策略

默认情况下,子进程会继承父进程的实时策略。如果不希望 fork 出一堆实时子进程,可以加 -R(reset-on-fork):

1
chrt -R -f 50 ./my_realtime_task

权限

设置实时策略不是只有 root 才行,有三种方式:

1
2
3
1. root
2. 具备 CAP_SYS_NICE 能力
3. 在 /etc/security/limits.conf 中给用户配置 rtprio 限额

例如:

1
appuser  -  rtprio  50

生产环境:交给 systemd

手动 chrt 的设置在进程重启后就丢了。生产环境应该写进 unit 文件:

1
2
3
4
[Service]
CPUSchedulingPolicy=fifo
CPUSchedulingPriority=50
CPUSchedulingResetOnFork=yes

实时限流:防止实时任务把普通任务饿死

实时任务优先级很高。如果实时任务一直运行,就可能出现:

1
实时任务 → 持续占用 CPU → 普通任务长时间得不到 CPU

Linux 因此提供了实时任务 CPU 带宽限制:

1
2
kernel.sched_rt_period_us
kernel.sched_rt_runtime_us
1
2
sysctl kernel.sched_rt_period_us
sysctl kernel.sched_rt_runtime_us

典型默认值:

1
2
sched_rt_period_us  = 1000000
sched_rt_runtime_us = 950000

可以理解为:每 1 秒的周期里,整个 RT 调度类(FIFO 和 RR)累计最多使用约 0.95 秒 CPU 时间,剩下约 0.05 秒留给普通任务。

pie title 默认配置下一个周期的时间分配
    "RT 调度类上限" : 95
    "留给普通任务" : 5

这里有两个容易理解错的点:

  • 它不是单个任务的限额,而是 RT 调度类整体的配额;
  • 它是周期内的累计用量,不是”连续运行”的时间。

触发限流时,内核日志里会出现类似:

1
sched: RT throttling activated

看到这行日志,说明实时任务已经把配额用光了。不要把它当噪音,要去查谁在狂吃 CPU。

修改配置

1
2
3
4
5
6
7
8
9
10
# 临时修改
sysctl -w kernel.sched_rt_runtime_us=900000

# 永久修改(RHEL 系推荐放到 sysctl.d)
cat > /etc/sysctl.d/90-rt.conf <<'EOF'
kernel.sched_rt_period_us = 1000000
kernel.sched_rt_runtime_us = 900000
EOF

sysctl --system

设成 -1 会怎样?

1
kernel.sched_rt_runtime_us = -1

表示关闭实时任务的 CPU 带宽限制,等于撤掉了安全网。生产环境不要为了”让实时任务跑得更快”就随便关闭它,除非是在充分隔离的 CPU 上运行经过压测的任务。

兜底机制的变化:Deadline Server

较新的内核(RHEL 10 基于 6.12)引入了 fair server(Deadline Server):内核通过 deadline 机制,给普通 Fair 任务保留一小段兜底运行时间,减少被 RT/DL 任务长期挤压的情况。这也影响了后面 stalld 的定位。


SCHED_DEADLINE:按照截止时间调度

SCHED_FIFO 和 SCHED_RR 主要回答:

谁优先运行?

而 SCHED_DEADLINE 更进一步关注:

任务应该在什么时候之前完成?

它使用三个核心参数:

参数含义
runtime每个周期可以获得的 CPU 执行时间
deadline相对截止时间
period任务周期

例如 runtime=3ms, deadline=5ms, period=10ms:

flowchart LR
    A["t=0ms<br/>任务释放"] --> B["最多执行 3ms"]
    B --> C["t=5ms<br/>截止时间,必须完成"]
    C --> D["t=10ms<br/>下一个周期开始"]

这个任务的 CPU 利用率是 3/10 = 30%。

内核怎么调度:EDF + CBS

  • EDF(最早截止时间优先):谁的截止时间最近,谁先运行;
  • CBS(恒定带宽服务器):每个任务有预算,用完 runtime 就被节流到下个周期,防止单个任务拖垮系统。

所以 DEADLINE 比 FIFO 更”安全”:死循环的 FIFO 任务会占满 CPU,死循环的 DEADLINE 任务只会被按预算掐断。

使用 chrt 设置

1
2
3
4
5
chrt -d \
--sched-runtime 3000000 \
--sched-deadline 5000000 \
--sched-period 10000000 \
0 ./my_task

单位是 ns。注意最后的 0:SCHED_DEADLINE 不使用优先级,但 chrt 的语法要求这个位置必须有一个优先级参数,只能填 0。漏掉它,./my_task 会被当成优先级解析,报 invalid priority argument。

周期任务要主动交还 CPU

每个周期的工作做完后,任务应该调用 sched_yield(),告诉内核”这一轮结束了”,把剩余预算让出去,等下个周期再运行。只会死循环的程序,会在 runtime 用完后被节流。

准入控制:为什么有时候起不来

内核在设置 SCHED_DEADLINE 时会做两层检查:

1
2
1. 单任务合法性:runtime <= deadline <= period,否则返回 EINVAL
2. 全局带宽:所有 DEADLINE 任务的 runtime/period 之和不能超过上限,否则返回 EBUSY

这里有个关键细节:全局上限复用的就是上一节的 sched_rt_runtime_us / sched_rt_period_us(再乘以 CPU 数)。

也就是说:

任务启动报 EBUSY 时,不一定是你的参数有问题,也可能是系统的实时带宽上限不够。调整 sched_rt_runtime_us 会同时影响 RT 任务和 DEADLINE 任务。

不等于硬实时

SCHED_DEADLINE 并不等于”配置了它就是硬实时系统”。

真正的实时保证还取决于:

1
2
3
4
5
6
7
8
调度器
CPU 抢占
中断
CPU 亲和性
内存
I/O
硬件
最坏情况延迟

更准确的说法是:

SCHED_DEADLINE 为具有明确时间约束的任务提供了截止时间调度机制。


stalld:专治”被实时任务饿死的线程”

先纠正一个很容易理解反的地方:stalld 不是帮实时任务的,它救的是被实时任务压住的线程。

典型场景:你隔离了一个 CPU,上面跑一个 SCHED_FIFO 的忙等轮询线程(网络转发、DPDK 里很常见),它从不睡眠。结果同一个 CPU 上的 kworker、ksoftirqd 等线程一直拿不到 CPU,内核内部的工作越积越多,最终出现各种诡异问题。

stalld(stall daemon)会周期性扫描各 CPU 的运行队列,寻找长时间处于 Runnable、却没有被调度的线程,然后:

flowchart LR
    A["周期性扫描运行队列"] --> B{"有线程超过阈值<br/>仍没有运行?"}
    B -- 否 --> A
    B -- 是 --> C["临时提权<br/>(短时间的 DEADLINE 等)"]
    C --> D["线程跑一小会儿<br/>处理积压的工作"]
    D --> E["恢复原调度属性"]
    E --> A
1
2
3
4
# 仓库和订阅随版本不同,以你的环境为准
dnf install stalld -y
systemctl enable --now stalld
systemctl status stalld

阈值等参数可以调整,参见 man stalld。

需要注意两点:

**1. stalld 是兜底手段,不是正解。**如果它频繁介入,说明实时任务设计有问题,比如该睡眠的地方在忙等,或者隔离核上混进了不该有的线程。

**2. 它是否必装,要看版本和负载。**RHEL 10 内核已经有 Deadline Server 为普通任务提供兜底,是否还需要 stalld,要结合你使用普通内核还是 RT 内核,以及具体的实时负载来判断。


PREEMPT_DYNAMIC:内核抢占

前面讲的策略,主要讨论的是用户任务怎么调度。但是任务进入内核态以后,还有一个问题:

内核代码什么时候允许被抢占?

实时任务就绪后,如果 CPU 正在执行一段不可抢占的内核代码,调度器就必须等它执行完才能切换,这段等待就是延迟。

现代内核可以通过 PREEMPT_DYNAMIC 在不同抢占模型之间选择:

模式特点取舍
none内核代码基本不被强制抢占吞吐最高,延迟抖动最大,服务器的经典选择
voluntary只在显式的抢占点让出折中
full内核大部分位置可被抢占延迟更低,吞吐略降

查看与切换

先确认内核是否支持:

1
2
grep PREEMPT /boot/config-$(uname -r)
# 看到 CONFIG_PREEMPT_DYNAMIC=y 才支持动态切换

查看当前模式(需要 root,且 debugfs 已挂载;括号标出的是当前模式):

1
cat /sys/kernel/debug/sched/preempt

提示:在开启 Secure Boot 锁定模式的环境中,debugfs 可能不可读写,这时只能通过启动参数设置。

永久生效,用 grubby 修改启动参数:

1
2
3
grubby --update-kernel=ALL --args="preempt=full"
reboot
cat /proc/cmdline # 验证

两个常见误区

**误区一:full 一定更好。**它是在吞吐量、调度延迟和抢占开销之间取舍,是否调整要用实际延迟指标验证。

**误区二:preempt=full 就是实时内核。**不是。full 只是让更多内核路径可被抢占,但自旋锁临界区和硬中断仍然不可打断。这正是下一节 RT 内核要解决的问题。


RHEL Real-Time 实时内核

如果 full 抢占仍然满足不了最坏延迟指标,就轮到 RT 内核了。它的本质是 PREEMPT_RT,主要改动有三点:

flowchart TB
    RT["PREEMPT_RT 内核"] --> A["自旋锁变成可睡眠的锁<br/>临界区内也能被抢占"]
    RT --> B["中断处理线程化<br/>中断变成可调度的线程"]
    RT --> C["优先级继承<br/>避免优先级反转"]
    A --> R["最坏延迟显著降低"]
    B --> R
    C --> R

为什么这三点重要:

  • 自旋锁可睡眠:标准内核里,持有自旋锁时不能被抢占,实时任务只能等;
  • 中断线程化:标准内核里,硬中断不管你优先级多高都会打断你;线程化后,中断也要按优先级排队;
  • 优先级继承:低优先级任务持锁、高优先级任务等锁时,低优先级任务会临时”借”到高优先级,避免被中间优先级任务插队(即优先级反转)。

安装与验证

1
2
3
4
5
6
7
8
9
10
# 仓库名随版本和订阅变化,以你的环境为准
dnf install kernel-rt tuned-profiles-realtime rt-tests

# 把隔离给实时任务的 CPU 写进配置
echo "isolated_cores=2-7" >> /etc/tuned/realtime-variables.conf
tuned-adm profile realtime
reboot

# 验证
cat /sys/kernel/realtime # 输出 1 表示 RT 内核

一个常被忽略的坑

RT 内核里,中断线程默认就是 SCHED_FIFO 优先级 50。如果你的业务线程优先级设成 40,就会被网卡中断线程压着打。设计优先级之前,先看清内核线程的分布:

1
ps -eo pid,comm,cls,rtprio | grep -E "irq|FF"

不要盲目使用

1
2
收益:最坏延迟显著降低,行为更可预测
代价:吞吐量可能下降,第三方驱动和软件需要验证兼容性,调试和维护成本更高

RT 内核不是普通业务的”性能加速开关”。对 Nginx、MySQL、Redis 这类业务,装了 RT 内核不一定更快。判断标准只有一个:你的业务有没有明确的、用数字写出来的延迟指标。没有,就别用。


用数据说话:cyclictest

实时调优不能凭感觉。cyclictest(来自 rt-tests)专门测量调度延迟:

1
cyclictest -m -p 95 -a 2-7 -t -D 10m -q

重点看 Max(最坏延迟),平均值在实时系统里几乎没有意义。每次改完配置,都要对比 Max 值。


实时任务出现延迟怎么排查?

flowchart TD
    A[实时任务延迟] --> B{"调度策略和优先级<br/>真的生效了吗?<br/>chrt -p PID"}
    B -- 没有 --> B1["检查 systemd 配置<br/>权限 / rtprio 限额"]
    B -- 有 --> C{"dmesg 有<br/>RT throttling?"}
    C -- 有 --> C1["查谁用光了配额<br/>sched_rt_runtime_us 是否被改"]
    C -- 没有 --> D{"被更高优先级<br/>RT 任务抢占?"}
    D -- 是 --> D1["重新规划优先级<br/>留意内核线程"]
    D -- 否 --> E{"中断落在同一个 CPU?<br/>/proc/interrupts"}
    E -- 是 --> E1["IRQ 亲和性绑到其他核"]
    E -- 否 --> F["检查 CPU 隔离 / 内存锁定<br/>抢占模式 / RT 内核"]

第一步:确认调度策略

1
2
ps -eo pid,tid,comm,cls,rtprio,psr
chrt -p PID

确认任务到底是不是 SCHED_FIFO、SCHED_RR 或 SCHED_DEADLINE。psr 列可以看到线程当前跑在哪个 CPU 上。

第二步:检查实时 CPU 带宽限制

1
2
sysctl kernel.sched_rt_period_us kernel.sched_rt_runtime_us
dmesg | grep -i throttl

确认有没有被改成 -1,或者已经触发了限流。

第三步:检查高优先级任务

1
2
3
4
谁的优先级更高?
是否存在任务长期占用 CPU?
是否存在低优先级任务长期饥饿?
是否被内核线程(包括 RT 内核里的中断线程)压住?

第四步:检查 CPU 和中断

1
2
3
top
mpstat -P ALL 1
cat /proc/interrupts

某个 CPU 负载很高或中断非常频繁,都会影响实时任务。

第五步:检查 CPU 亲和性和隔离

1
taskset -cp PID

对于高实时性要求的任务,还需要结合:

1
2
3
4
CPU affinity
CPU isolation
IRQ affinity
内存锁定(mlockall,避免缺页造成延迟)

这已经从单纯的”调度策略”进入完整的低延迟系统优化。


生产环境几个重要注意事项

**不要把所有任务都设置成实时。**滥用会导致普通业务饿死、系统管理任务得不到 CPU、问题更难排查。

**不要随便把优先级设置成 99。**内核的 migration、watchdog 线程就在 99,业务任务与它们抢占,会影响系统自我保护。

**不要随便关闭实时限流。**尤其不要为了”提高实时性能”直接设 kernel.sched_rt_runtime_us = -1。

**不要把 SCHED_DEADLINE 等同于硬实时。**真正的硬实时,需要考虑整个系统的最坏情况延迟。

**不要靠手工 chrt。**用 systemd 固化配置。

**不要凭感觉优化。**用 cyclictest 的 Max 值对比前后效果。


把三种实时调度策略串起来

flowchart TD
    A[实时任务] --> B{采用什么调度方式}

    B --> C[SCHED_FIFO]
    B --> D[SCHED_RR]
    B --> E[SCHED_DEADLINE]

    C --> C1[固定优先级]
    C1 --> C2[同优先级 FIFO]

    D --> D1[固定优先级]
    D1 --> D2[同优先级时间片轮转]

    E --> E1[runtime]
    E --> E2[deadline]
    E --> E3[period]
1
2
3
4
5
6
7
8
SCHED_FIFO
谁优先级高,谁先运行;同优先级,先来先运行

SCHED_RR
谁优先级高,谁先运行;同优先级,轮流运行

SCHED_DEADLINE
关注任务的时间约束:runtime + deadline + period

系列小结

调度策略类型核心特点常见场景
SCHED_OTHER普通默认公平调度普通业务
SCHED_BATCH普通假定任务是 CPU 密集型,减少唤醒抢占离线计算、批处理
SCHED_IDLE普通极低优先级低优先级后台任务
SCHED_FIFO实时固定优先级,同优先级 FIFO对响应延迟敏感的短任务
SCHED_RR实时固定优先级,同优先级时间片轮转多个同优先级实时任务
SCHED_DEADLINE实时EDF + CBS,按 runtime/deadline/period 调度有明确时间约束的任务

如果这一篇只记住一句话:

普通调度解决的是”CPU 怎么公平地分”;实时调度解决的是”关键任务怎么更及时、更可预测地获得 CPU”。

而真正的实时优化,也绝不仅仅是执行一条 chrt -f 99 ... 就结束了:

1
调度策略 → 优先级 → CPU 亲和性 → CPU 隔离 → 中断 → 内存 → 抢占模型 → 实时内核 → 实测验证

下一步继续往下深入,可以研究:

为什么实时任务明明优先级很高,却还是可能出现延迟?

这时候,就要进入 CPU affinity、CPU isolation、IRQ affinity、内存锁定、优先级反转这些真正影响 Linux 实时性能的内容。


课后思考

  1. 一台 8 核机器,sched_rt_runtime_us=950000。如果每个 DEADLINE 任务是 runtime=4ms, period=10ms,最多能放几个?(提示:计算利用率总和与全局上限)
  2. 为什么 preempt=full 仍然不等于 PREEMPT_RT?
  3. 隔离核上跑着忙等的 FIFO 线程,为什么还需要 stalld?这反映了设计上的什么问题?

参考手册

1
2
3
4
5
6
man chrt
man 7 sched
man sched_setattr
man stalld
man systemd.exec
man cyclictest