struct sched_rt_entity 是 Linux 内核中专门用于实时进程(Real-Time, RT)调度的核心数据结构。
它与我们之前看到的 struct sched_entity(用于普通进程 CFS 调度)不同:
- CFS (sched_entity):基于红黑树,追求“公平”,按虚拟运行时间 (vruntime) 排序。
- RT (sched_rt_entity):基于优先级队列(位图 + 链表),追求“确定性”和“低延迟”,严格按优先级抢占。
这个结构体 encapsulates(封装)了一个实时任务(或实时任务组)在调度器眼中的所有状态。
1. 字段详解
A. 核心链表与状态
struct list_head run_list;
- 作用:这是该实体所在的运行队列节点。
- 机制:Linux RT 调度器为每个优先级(0-99)维护一个双向链表。run_list 就是用来把这个进程挂到对应优先级的链表上的。
- 特点:同优先级的实时进程通过此字段串联,遵循 FIFO (先进先出) 或 RR (轮转) 规则。
unsigned short on_rq;
unsigned short on_list;
- on_rq: 标记该实体是否已经在运行队列 (rt_rq) 中。防止重复入队。
- on_list: 标记是否已经挂在具体的 run_list 链表上。这两个标志位用于优化调度器的逻辑判断,避免无效的链表操作。
B. 时间控制 (Time Slicing & Watchdog)
unsigned int time_slice;
- 作用:进程的剩余时间片(单位通常是纳秒或时钟滴答)。
- 场景:
- 对于 SCHED_FIFO:这个值通常被忽略(或者是无限大),进程一直运行直到自愿让出或被更高优先级抢占。
- 对于 SCHED_RR (Round Robin):这是关键。每次调度时递减,当减为 0 时,进程会被移到同优先级链表的末尾,让出 CPU 给同优先级的其他进程。
unsigned long timeout;
unsigned long watchdog_stamp;
- timeout: 记录该进程下一次应该被调度的绝对时间点(主要用于 RR 策略的时间片到期计算)。
- watchdog_stamp: 看门狗时间戳。用于检测“实时进程饥饿”或“独占 CPU 过久”。如果某个实时进程运行时间超过阈值(由 /proc/sys/kernel/watchdog_thresh 控制),内核可能会触发软锁死检测 (Soft Lockup Detector),甚至强制剥夺其 CPU,以防止系统卡死。
C. 层级与组调度 (Group Scheduling)
struct sched_rt_entity *back;
- 作用:指向上一个调度实体(通常用于在层级结构中回溯,或者在某些实现中作为父指针的辅助,具体取决于内核版本,有时用于恢复上下文)。
#ifdef CONFIG_RT_GROUP_SCHED
struct sched_rt_entity *parent;
struct rt_rq *rt_rq;
struct rt_rq *my_q;
#endif
- 背景:当开启 CONFIG_RT_GROUP_SCHED 时,Linux 支持实时进程组调度(类似于 Cgroups 的 CPU 控制器)。这允许将一组实时进程作为一个整体来分配 CPU 带宽。
- parent: 指向父组的调度实体。形成了树状结构(根是 CPU,叶子是具体进程)。
- rt_rq: 指向该实体当前所属的运行队列。如果是子进程,这指向父组的队列;如果是根组,指向 CPU 的全局队列。
- my_q: 如果该实体本身代表一个组(Group),那么这个指针指向该组内部独立的运行队列。
- 逻辑:组调度实体既是父队列中的一个“大进程”,又是自己内部子进程的“小调度器”。
2. 核心工作机制举例
为了理解这个结构体,我们需要看它如何在两种主要的实时策略下工作:SCHED_FIFO 和 SCHED_RR。
场景设定
假设系统有两个实时进程:
它们都位于同一个 CPU 的 RT 运行队列 (struct rt_rq) 中,且都在优先级 90 的链表上。
阶段一:入队 (Enqueue)
当 A, B, C 依次变为可运行状态时:
- 内核找到 rt_rq->queue[90] (优先级 90 的链表头)。
- A 入队: A.run_list 加入链表尾部。A.on_rq = 1.
- B 入队: B.run_list 加入链表尾部。B.time_slice 被重置为 100ms。
- C 入队: C.run_list 加入链表尾部。
此时链表顺序:Head -> [A] -> [B] -> [C] -> NULL
阶段二:调度与运行 (Pick Next & Run)
调度器调用 pick_next_task_rt():
- 因为是 SCHED_FIFO,内核不检查 A.time_slice 是否耗尽。
- A 会一直运行,直到:
- A 主动睡眠 (sleep)。
- A 被更高优先级(如 91)抢占。
- A 显式调用 sched_yield()。
阶段三:时间片轮转 (RR Time Slice Expiration)
假设 A 退出了,现在轮到 Process B (SCHED_RR) 运行。
- 内核将 B 从链表头移除。
- 重置 B.time_slice = 100ms。
- 将 B.run_list 插入到优先级 90 链表的尾部。
- 此时链表顺序:Head -> [C] -> [B] -> NULL。
阶段四:看门狗 (Watchdog)
假设有一个恶意的 SCHED_FIFO 进程 D (优先级 99),它进入了一个死循环 while(1); 且不睡眠。
3. 组调度示例 (CONFIG_RT_GROUP_SCHED)
这是一个高级用法,常用于容器或复杂的服务隔离。
结构:
- Root RT Queue (CPU 总资源 100%)
- Group "Database" (分配 50% CPU 给实时任务)
- struct sched_rt_entity (代表这个组)
- my_q -> 指向组内私有队列
- Process DB_1 (RT, prio 80)
- Process DB_2 (RT, prio 80)
- Group "Web" (分配 30% CPU)
- …
- Default (剩余 20%)
- Group "Database" (分配 50% CPU 给实时任务)
工作流程:
4. 总结对比
|
特性 |
struct sched_entity (CFS) |
struct sched_rt_entity (RT) |
|
适用策略 |
SCHED_NORMAL, SCHED_BATCH |
SCHED_FIFO, SCHED_RR |
|
数据结构 |
红黑树 (rb_node) |
双向链表 (list_head) + 优先级数组 |
|
排序依据 |
vruntime (虚拟运行时间,越左越优先) |
优先级 (固定数字,越小/大越优先,取决于定义) + FIFO/RR |
|
时间片 |
动态计算,追求公平 |
固定 (time_slice) 或 无限 (FIFO) |
|
抢占行为 |
温和,通常等待时间片结束或新进程 vruntime 更小 |
强硬,高优先级就绪立即抢占低优先级 |
|
关键字段 |
vruntime, sum_exec_runtime |
time_slice, run_list, watchdog_stamp |
struct sched_rt_entity 的设计哲学是简单、快速、确定。它不需要像 CFS 那样复杂的数学计算来保证公平,它的目标仅仅是:确保高优先级的任务能立刻运行,并且(如果是 RR)同优先级的任务能轮流运行。

