欢迎光临
我们一直在努力

进程调度:实时调度类实体sched_rt_entity

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。

场景设定

假设系统有两个实时进程:

  • Process A: SCHED_FIFO, 优先级 90。
  • Process B: SCHED_RR, 优先级 90, 时间片 100ms。
  • Process C: SCHED_FIFO, 优先级 90。
  • 它们都位于同一个 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():

  • 扫描位图,发现优先级 90 有任务。
  • 取出链表头 Process A。
  • A 开始运行。
    • 因为是 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 的时间片用完。
  • 重入队逻辑:
    • 内核将 B 从链表头移除。
    • 重置 B.time_slice = 100ms。
    • 将 B.run_list 插入到优先级 90 链表的尾部。
    • 此时链表顺序:Head -> [C] -> [B] -> NULL。
  • 重新调度:调度器再次运行,取出新的链表头 Process C。
  • 结果:B 被迫让出 CPU,尽管它还想运行,但 RR 策略强制它排队。
  • 阶段四:看门狗 (Watchdog)

    假设有一个恶意的 SCHED_FIFO 进程 D (优先级 99),它进入了一个死循环 while(1); 且不睡眠。

  • D 抢占所有低优先级进程,独占 CPU。
  • 内核的看门狗线程(运行在稍低优先级或单独机制)检测到 D 的 watchdog_stamp 已经超过阈值(例如 10 秒未调度其他任务)。
  • 触发 Soft Lockup Warning:内核打印堆栈跟踪,警告系统可能已挂起。
  • 在某些配置下(如 sysctl kernel.rt_runtime_us 限制),内核可能会强制限制 D 的运行时间,即使它是 FIFO,从而保护系统不被完全锁死。
  • 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%)

    工作流程:

  • DB_1 变为可运行。它被加入到 Group "Database" 的私有队列 (my_q) 中。
  • Group "Database" 的实体 (sched_rt_entity) 因为内部有任务,变得“可运行”,于是它自己被加入到 Root RT Queue 的对应优先级链表中。
  • 调度器 从 Root 队列选中了 Group "Database" 这个实体。
  • 组调度器 介入:它不会直接运行“组”这个概念,而是去查自己的 my_q,从中选出 DB_1 来真正执行。
  • 意义:即使 DB_1 和 DB_2 有极高的优先级,它们总共也只能消耗 50% 的 CPU 时间,防止它们饿死 "Web" 组的实时任务。这里 parent 和 my_q 字段起到了关键的层级隔离作用。
  • 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)同优先级的任务能轮流运行。

    赞(0)
    未经允许不得转载:171主机测评 » 进程调度:实时调度类实体sched_rt_entity
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址