第四十五章:RT 调度
结丹中期涉及内核源码:
一
林小源在调度竞技场中,第一次看到了"特权阶层"。
那是一群穿着金色铠甲的进程,它们不走红黑树的通道,而是从一条独立的、更加宽阔的道路上走来。道路的尽头是一座高台——高台上有一排石座,每个石座上都刻着一个数字,从 1 到 99。
"RT 进程。"调度仙子站在林小源身旁,声音平静但带着一丝凝重,"实时进程。它们不参与 CFS 的公平竞争。它们有自己独立的运行队列,有自己独立的调度策略,有更高的优先级。"
林小源看着那些金色铠甲的进程:"它们的优先级比普通进程高?"
"不是'高'——是'绝对'。"调度仙子说,"只要有一个 RT 进程可运行,CFS 进程就得不到 CPU。这是调度类的优先级顺序:stop > dl > rt > fair > idle。"
一个穿着金色铠甲的进程走到高台前,向调度仙子行了一礼。林小源注意到它的铠甲上刻着两个字:FIFO。
"这是 SCHED_FIFO 进程,"调度仙子说,"先进先出。它会一直运行,直到主动让出 CPU。没有时间片限制。"
另一个穿着金色铠甲的进程走了过来,它的铠甲上刻着 RR。
"这是 SCHED_RR 进程,"调度仙子说,"轮转。同优先级的 RR 进程轮流运行,每个进程有固定的时间片——通常是 10ms。时间片用完后,进程会被移到同优先级队列的末尾。"
二
林小源在高台前观察了很久,发现了一个问题。
"如果一个 RT 进程陷入死循环,"他说,"它会独占 CPU?"
调度仙子点头:"对。因为 RT 优先级总是高于普通进程——只要 RT 进程可运行,普通进程就永远得不到 CPU。如果 RT 进程死循环,普通进程会被'饿死',系统变得无响应。"
"这不是一个严重的问题吗?"
"是。"调度仙子说,"所以内核有 RT 带宽限制—— 和 。默认情况下,RT 进程在每个周期内最多使用 950ms 的 CPU 时间,留 50ms 给普通进程。"
"950ms?"林小源皱眉,"那还是很多。"
"是的。"调度仙子说,"但这是为了保证实时任务的确定性。实时任务需要可预测的响应时间——如果内核频繁地抢占 RT 进程,实时任务的延迟就不可预测了。"
"所以这是'确定性' vs '公平'的权衡。"
"对。"调度仙子说,"CFS 追求'公平'——每个进程按权重获得 CPU 时间。RT 追求'确定性'——RT 进程的响应时间是可预测的。这是两种不同的价值。"
三
林小源在高台前站了很久,看着那些穿着金色铠甲的进程。
"创建 RT 进程需要特权吗?"他问。
调度仙子点头:"需要 能力。普通用户不能创建 RT 进程——否则恶意进程可以创建一个 RT 进程,独占 CPU,让整个系统无响应。"
"所以'特权'也是一种'责任'。"
"对。"调度仙子说,"RT 进程的特权是'绝对优先'——它们可以抢占任何普通进程。但这个特权的代价是'责任'——RT 进程必须正确地管理自己的行为,不能死循环,不能独占 CPU。"
林小源想起了自己在内核中看到的很多设计:特权总是伴随着责任,自由总是伴随着代价。nice 值可以自由调整,但只能降低自己;亲和性可以限制迁移范围,但会降低灵活性;RT 进程可以获得绝对优先,但需要 能力。
"还有两种 RT 策略的区别,"调度仙子补充道,"FIFO 进程会一直运行直到主动让出——适合那些知道什么时候该停的实时任务。RR 进程有时间片轮转——适合那些需要公平分享 CPU 的实时任务。"
"音频解码器用 FIFO?"
"对。"调度仙子说,"音频解码器需要在固定的时间内完成解码——它知道什么时候该停。而传感器读取器用 RR——多个传感器需要轮流读取数据。"
林小源点了点头。他终于理解了 RT 调度的本质:它不是"更好"的调度,而是"不同"的调度。CFS 和 RT 是两种不同的价值取向——公平 vs 确定性。内核的设计者没有选择其中之一,而是让两者共存,各司其职。
/*
* RT 调度器的两种策略:
*
* SCHED_FIFO (先进先出):
* - 进程运行直到主动让出 CPU
* - 没有时间片限制
* - 同优先级的 FIFO 进程按先来后到调度
*
* SCHED_RR (轮转):
* - 同优先级的进程轮流运行
* - 每个进程有固定的时间片
* - 时间片用完后,移到同优先级队列的末尾
*
* RT 优先级:1 ~ 99
* 值越大优先级越高
* RT 进程总是优先于普通进程(CFS)
*/
struct rt_entity {
int pid;
char comm[16];
int rt_priority; /* 1 ~ 99 */
int policy; /* FIFO 或 RR */
int time_slice; /* 时间片 (ms) */
int remaining; /* 剩余时间片 */
};
#define SCHED_FIFO 1
#define SCHED_RR 2
printf("=== RT 调度器 — 特权阶层 ===\n\n");
/* RT 进程 */
struct rt_entity rt_procs[] = {
{ 100, "audio-daemon", 80, SCHED_FIFO, 0, 0 },
{ 200, "video-decoder", 70, SCHED_RR, 10, 10 },
{ 300, "sensor-reader", 60, SCHED_RR, 10, 10 },
{ 400, "logger", 50, SCHED_FIFO, 0, 0 },
};
int nr = sizeof(rt_procs) / sizeof(rt_procs[0]);
printf("RT 进程列表:\n");
printf("%-6s %-16s %-10s %-10s %-10s\n",
"PID", "COMM", "RT_PRIO", "策略", "时间片");
printf("%-6s %-16s %-10s %-10s %-10s\n",
"---", "---", "---", "---", "---");
for (int i = 0; i < nr; i++) {
printf("%-6d %-16s %-10d %-10s %-10s\n",
rt_procs[i].pid, rt_procs[i].comm,
rt_procs[i].rt_priority,
rt_procs[i].policy == SCHED_FIFO ? "FIFO" : "RR",
rt_procs[i].policy == SCHED_FIFO ? "无限" : "10ms");
}
printf("\n--- RT 调度的规则 ---\n");
printf("1. RT 进程总是优先于普通进程\n");
printf("2. 高 RT_PRIO 的进程优先于低 RT_PRIO\n");
printf("3. 同优先级:\n");
printf(" FIFO: 先来后到,运行直到主动让出\n");
printf(" RR: 轮流运行,时间片 10ms\n\n");
printf("--- RT 调度的风险 ---\n");
printf("RT 进程可以\"饿死\"普通进程:\n");
printf(" 如果一个 RT 进程死循环,普通进程永远得不到 CPU\n");
printf(" 解决方案:RT 带宽限制(rt_runtime_us / rt_period_us)\n\n");
printf("RT 进程的创建需要特权:\n");
printf(" 普通用户不能创建 RT 进程\n");
printf(" 需要 CAP_SYS_NICE 能力\n");#include <stdio.h>
/*
* RT 调度器的两种策略:
*
* SCHED_FIFO (先进先出):
* - 进程运行直到主动让出 CPU
* - 没有时间片限制
* - 同优先级的 FIFO 进程按先来后到调度
*
* SCHED_RR (轮转):
* - 同优先级的进程轮流运行
* - 每个进程有固定的时间片
* - 时间片用完后,移到同优先级队列的末尾
*
* RT 优先级:1 ~ 99
* 值越大优先级越高
* RT 进程总是优先于普通进程(CFS)
*/
struct rt_entity {
int pid;
char comm[16];
int rt_priority; /* 1 ~ 99 */
int policy; /* FIFO 或 RR */
int time_slice; /* 时间片 (ms) */
int remaining; /* 剩余时间片 */
};
#define SCHED_FIFO 1
#define SCHED_RR 2
int main() {
printf("=== RT 调度器 — 特权阶层 ===\n\n");
/* RT 进程 */
struct rt_entity rt_procs[] = {
{ 100, "audio-daemon", 80, SCHED_FIFO, 0, 0 },
{ 200, "video-decoder", 70, SCHED_RR, 10, 10 },
{ 300, "sensor-reader", 60, SCHED_RR, 10, 10 },
{ 400, "logger", 50, SCHED_FIFO, 0, 0 },
};
int nr = sizeof(rt_procs) / sizeof(rt_procs[0]);
printf("RT 进程列表:\n");
printf("%-6s %-16s %-10s %-10s %-10s\n",
"PID", "COMM", "RT_PRIO", "策略", "时间片");
printf("%-6s %-16s %-10s %-10s %-10s\n",
"---", "---", "---", "---", "---");
for (int i = 0; i < nr; i++) {
printf("%-6d %-16s %-10d %-10s %-10s\n",
rt_procs[i].pid, rt_procs[i].comm,
rt_procs[i].rt_priority,
rt_procs[i].policy == SCHED_FIFO ? "FIFO" : "RR",
rt_procs[i].policy == SCHED_FIFO ? "无限" : "10ms");
}
printf("\n--- RT 调度的规则 ---\n");
printf("1. RT 进程总是优先于普通进程\n");
printf("2. 高 RT_PRIO 的进程优先于低 RT_PRIO\n");
printf("3. 同优先级:\n");
printf(" FIFO: 先来后到,运行直到主动让出\n");
printf(" RR: 轮流运行,时间片 10ms\n\n");
printf("--- RT 调度的风险 ---\n");
printf("RT 进程可以\"饿死\"普通进程:\n");
printf(" 如果一个 RT 进程死循环,普通进程永远得不到 CPU\n");
printf(" 解决方案:RT 带宽限制(rt_runtime_us / rt_period_us)\n\n");
printf("RT 进程的创建需要特权:\n");
printf(" 普通用户不能创建 RT 进程\n");
printf(" 需要 CAP_SYS_NICE 能力\n");
return 0;
}道藏笔记
内核启示
RT 调度器为实时进程提供确定性的调度。
RT 调度器的两种策略:
- SCHED_FIFO — 先进先出,进程运行直到主动让出
- SCHED_RR — 轮转,同优先级进程轮流运行
RT 优先级:1 ~ 99,值越大优先级越高
RT 调度器的特性:
- RT 进程总是优先于普通进程(CFS)
- 高 RT_PRIO 的进程优先于低 RT_PRIO
- SCHED_FIFO 没有时间片限制
- SCHED_RR 有固定的时间片
RT 调度的风险:
- RT 进程可以"饿死"普通进程
- RT 带宽限制( / )
- 创建 RT 进程需要 能力
RT 调度器追求"确定性",CFS 追求"公平"——两种不同的价值。
RT 调度之试
普通进程不能随便进入 RT 调度,本章指出设置实时优先级需要哪项能力?