一、核心概念

1. RT 调度基础

  • SCHED_FIFO/SCHED_RR:Linux 标准实时调度策略,优先级 1–99,数值越高优先级越高,可抢占普通 CFS 任务。
  • rt_rq:每个 CPU 运行队列 rq 内嵌的实时队列,按优先级位图管理就绪任务,是 RT 调度的核心数据结构。
  • pushable_tasks:CPU 上可被迁移的 RT 任务链表,用于过载与 CPU 下线场景的任务推送。
  • root_domain:多核系统中 RT 任务可迁移的 CPU 集合,决定任务迁移范围。

2. CPU 热插拔与调度回调

  • CPUHP_AP_ONLINE:CPU 上线完成后触发,调度器执行 rq_online 初始化 RT 队列。
  • CPUHP_AP_OFFLINE:CPU 下线前触发,调度器执行 rq_offline 迁移所有 RT 任务。
  • rq_online():启用当前 CPU 的 RT 运行队列,刷新优先级位图,加入根域均衡拓扑。
  • rq_offline():排空当前 CPU 所有 RT 任务,推送至其他在线 CPU,关闭本地 RT 队列。

3. RT 任务迁移机制

  • push_rt_tasks:CPU 主动将可迁移 RT 任务推送到其他轻载 CPU。
  • pull_rt_task:空闲 CPU 主动拉取高优先级 RT 任务,保障低延迟调度。
  • 迁移安全点:任务必须处于可调度状态,禁止迁移正在运行的 RT 任务,避免死锁与数据不一致。

二、环境准备

硬件环境

  • x86_64 多核服务器(≥4 核),支持 CPU 热插拔;嵌入式 ARM64 平台亦可。

软件环境

  • 操作系统:CentOS Stream 9 / Ubuntu 22.04
  • 内核版本:Linux 5.15 LTS(开启 CONFIG_CPU_HOTPLUG、CONFIG_RT_GROUP_SCHED、CONFIG_SCHED_DEBUG)
  • 开发工具:gcc、gdb、trace-cmd、kernel-debuginfo、chrt、taskset

环境配置步骤

  1. 安装依赖
yum install -y gcc gdb trace-cmd kernel-debuginfo taskset util-linux
  1. 检查内核配置
zcat /proc/config.gz | grep -E "CPU_HOTPLUG|RT_GROUP_SCHED|SCHED_DEBUG"
  1. 临时启用 CPU 热插拔(非物理热插拔)
# 下线CPU1
echo 0 > /sys/devices/system/cpu/cpu1/online
# 上线CPU1
echo 1 > /sys/devices/system/cpu/cpu1/online
  1. 开启调度调试
echo 1 > /sys/kernel/debug/sched_features
mount -t debugfs none /sys/kernel/debug

三、典型应用场景

在 5G 小基站 DU 单元中,设备会根据业务负载动态关闭空闲 CPU 以降低功耗。基带处理与空口调度为优先级 80 的 SCHED_FIFO RT 任务,当 CPU1 被系统下线时,RT 调度器必须在毫秒级将该 CPU 上的 RT 任务迁移至 CPU0/2/3,若迁移失败会导致空口失步引发终端掉线。在车载域控制器中,MCU 故障触发 CPU 离线时,自动驾驶感知 RT 任务需无缝迁移至备用 CPU,保证制动与转向控制不中断。CPU 上线时,调度器快速重建 rt_rq,接纳迁移回流的 RT 任务,恢复系统吞吐与实时性。该机制同样用于边缘计算节点的故障自愈与工业 PLC 的冗余切换,是实时 Linux 系统高可用的关键保障。


四、内核源码与实战案例

1. 核心源码路径

kernel/sched/core.c      # rq_online/rq_offline 入口
kernel/sched/rt.c        # RT任务迁移、push/pull实现
include/linux/sched/rt.h # RT队列结构体定义

2. rq_offline 流程(CPU 下线)

// kernel/sched/core.c
void rq_offline(struct rq *rq)
{
    raw_spin_lock_irq(&rq->lock);
    // 标记队列离线,禁止新任务入队
    rq->online = false;
    // 排空RT任务,强制迁移所有可推送任务
    push_rt_tasks(rq);
    // 清空本地RT优先级位图
    bitmap_zero(rq->rt.rt_prio.bitmap, MAX_RT_PRIO);
    rq->rt.highest_prio.curr = MAX_RT_PRIO;
    rq->rt.highest_prio.next = MAX_RT_PRIO;
    raw_spin_unlock_irq(&rq->lock);
}

作用:CPU 下线前锁定队列,推送全部 RT 任务,清空优先级状态,避免离线 CPU 残留任务导致调度死锁。

3. rq_online 流程(CPU 上线)

// kernel/sched/core.c
void rq_online(struct rq *rq)
{
    raw_spin_lock_irq(&rq->lock);
    // 标记队列在线
    rq->online = true;
    // 重置RT队列状态
    rq->rt.rt_nr_running = 0;
    bitmap_zero(rq->rt.rt_prio.bitmap, MAX_RT_PRIO);
    rq->rt.highest_prio.curr = MAX_RT_PRIO;
    rq->rt.highest_prio.next = MAX_RT_PRIO;
    // 将CPU加入根域,允许任务pull/push
    rq->rd = cpu_rq(smp_processor_id())->rd;
    raw_spin_unlock_irq(&rq->lock);
    // 触发全局均衡,拉取高优RT任务
    schedule_run_next();
}

作用:初始化 RT 队列,加入负载均衡拓扑,触发任务拉取,快速承接 RT 负载。

4. RT 任务推送核心实现

// kernel/sched/rt.c
static int push_rt_tasks(struct rq *rq)
{
    struct task_struct *p, *n;
    int pushed = 0;

    list_for_each_entry_safe(p, n, &rq->rt.pushable_tasks, pushable_tasks) {
        struct rq *target_rq;
        // 选择同根域内最优目标CPU
        target_rq = pick_optimal_rt_cpu(rq, p);
        if (!target_rq)
            continue;
        // 迁移任务至目标CPU
        if (migrate_task(rq, p, target_rq) == 0)
            pushed++;
    }
    return pushed;
}

作用:遍历可迁移 RT 任务,按优先级与负载选择目标 CPU,完成原子迁移。

5. 用户态 RT 任务测试程序

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sched.h>
#include <unistd.h>
#include <sys/types.h>

#define RT_PRIO 80
#define CPU_ID 1

void *rt_thread_func(void *arg)
{
    cpu_set_t cpuset;
    CPU_ZERO(&cpuset);
    CPU_SET(CPU_ID, &cpuset);
    // 绑定CPU1
    if (pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset) != 0)
        perror("pthread_setaffinity_np failed");

    while (1)
    {
        // 空循环占用CPU
        usleep(1000);
    }
    return NULL;
}

int main()
{
    pthread_t tid;
    struct sched_param param;
    pthread_attr_t attr;

    pthread_attr_init(&attr);
    pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
    param.sched_priority = RT_PRIO;
    pthread_attr_setschedparam(&attr, &param);
    pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);

    if (pthread_create(&tid, &attr, rt_thread_func, NULL) != 0)
    {
        perror("pthread_create failed");
        return -1;
    }

    pthread_join(tid, NULL);
    return 0;
}

编译运行

gcc rt_test.c -o rt_test -lpthread -rt
chrt -f 80 ./rt_test

6. 调试命令实战

  1. 查看 RT 任务绑定 CPU
ps -eo pid,pri,cmd,psr | grep -E "rt_test|chrt"
  1. 下线 CPU 并观察迁移
echo 0 > /sys/devices/system/cpu/cpu1/online
ps -eo pid,pri,cmd,psr | grep rt_test
  1. 跟踪调度事件
trace-cmd record -e sched:sched_switch -e sched:sched_migrate_task
trace-cmd report
  1. 查看 RT 队列状态
cat /sys/kernel/debug/sched/rt_rq/cpu1

五、常见问题与解答

Q1:CPU 下线后 RT 任务未迁移,导致任务卡住

原因:任务被 CPU 亲和性严格绑定(nr_cpus_allowed=1),无可用迁移目标。解决方案:放宽 cpuset 允许跨 CPU 迁移,或在离线前主动重设亲和性。

taskset -cp 0-3 <pid>

Q2:rq_offline 触发死锁,系统卡死

原因:迁移时持有 rq 锁重入,或任务处于 RUNNING 状态被强制迁移。解决方案:升级内核至 5.15+,关闭 PREEMPT_RT 与热插拔混用,迁移前确保任务可调度。

Q3:CPU 上线后 RT 任务不回流,负载不均

原因:root_domain 未更新,均衡未触发。解决方案:手动触发均衡

echo 1 > /proc/sys/kernel/sched_rt_period_us

Q4:RT 任务迁移后调度延迟飙升

原因:缓存失效、目标 CPU 过载、优先级反转。解决方案:使用 SCHED_DEADLINE 替代,绑定 LLC 节点,避免跨 NUMA 迁移。


六、实践建议与最佳实践

  1. RT 任务亲和性配置线上 RT 任务禁止绑定单一 CPU,应设置为同 NUMA 节点内多 CPU,提升热插拔容错。
CPU_SET(0, &cpuset);
CPU_SET(1, &cpuset);
CPU_SET(2, &cpuset);
  1. 热插拔顺序优化下线 CPU 前先降低其负载,避免瞬时大规模迁移;上线后立即触发 pull_rt_task,快速承接高优任务。

  2. 优先级保护关键 RT 任务优先级≥80,避免被非关键任务抢占;禁用普通任务提升优先级至 RT 范围。

  3. 内核参数调优

sysctl -w kernel.sched_rt_runtime_us=950000
sysctl -w kernel.sched_rt_period_us=1000000

限制 RT 任务总占用,防止系统饿死。

  1. 监控告警持续监控 rt_rq.nr_running、迁移次数、CPU 在线状态,异常时自动触发冗余切换。

七、总结

rq_online 与 rq_offline 是 RT 调度器适配 CPU 热插拔的基石,通过队列状态管理、RT 任务原子推送 / 拉取、优先级位图刷新与根域重均衡,保证 CPU 上下线过程中 RT 任务不丢失、不卡死、调度延迟可控。在实时系统中,CPU 热插拔不再是单纯的节能手段,而是高可用自愈的核心能力。

本文提供的源码分析、测试程序与调试命令可直接用于论文实验、项目验证与线上故障定位。建议开发者结合 trace-cmd 与内核调试深入理解迁移细节,在工业控制、车载、5G 等场景中落地时,务必做长时间压力测试,确保极端场景下实时性与稳定性达标。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐