Linux Deadline 调度器的 dl_nr_migratory:可迁移 Deadline 任务统计
简介
在 Linux 内核调度体系中,CFS 调度器负责普通分时进程的公平调度,而SCHED_DEADLINE作为硬实时调度策略,专门面向工业控制、自动驾驶、航空航天测控、5G 基带实时处理等对时间确定性、调度时延、抢占时效性有严苛要求的业务场景。
多核架构下,Linux 内核依赖调度负载均衡机制,将任务从高负载 CPU 迁移到低负载 CPU,抹平各核心算力压力。但 Deadline 实时任务和普通 CFS 任务有本质区别:部分 DL 任务做了CPU 亲和性绑定、实时带宽独占预留、禁止跨核迁移的约束,不能随意做负载均衡迁移;若不加甄别盲目迁移,会破坏实时任务的截止时间保障,引发任务超时、调度抖动、抢占失效等严重线上问题。
为解决这一矛盾,内核在dl_rq运行队列中引入dl_nr_migratory核心字段,专门统计当前 CPU 就绪队列中允许跨 CPU 迁移的 Deadline 任务数量。负载均衡模块在做核间任务迁移决策时,不再遍历全部 DL 任务逐一判断迁移属性,而是直接读取该计数字段,快速判定当前 CPU 是否有可迁移 DL 任务、是否需要发起均衡迁移。
对于嵌入式 Linux 工程师、内核研发人员、实时系统调优工程师、做内核调度论文与报告的研究者来说,吃透dl_nr_migratory的统计逻辑、更新时机、内核源码实现、负载均衡联动机制,是理解多核实时调度负载均衡、排查 DL 任务迁移异常、定制实时调度策略、优化多核实时系统时延的核心必修课。本文以一线 Linux 内核工程师视角,从概念、环境、源码、实操、排错、最佳实践全链路拆解,附带可直接编译运行的代码与调试命令,完全满足工程落地、论文撰写、技术报告调研需求。
一、核心概念与术语解析
1.1 SCHED_DEADLINE 调度器基础
SCHED_DEADLINE基于EDF 最早截止时间优先调度算法,采用经典三元参数模型:
sched_runtime:单个周期内任务最大 CPU 占用时间;sched_period:任务调度周期;sched_deadline:任务必须完成执行的最晚截止时间。
DL 任务优先级高于 SCHED_FIFO、SCHED_RR,具备强抢占特性,一旦就绪可立即抢占普通进程 CPU 资源。
1.2 dl_rq Deadline 每 CPU 私有运行队列
内核为每个 CPU 单独维护struct dl_rq,作为当前核所有就绪 DL 任务的管理容器,定义在kernel/sched/sched.h,核心成员精简如下:
struct dl_rq {
struct rb_root rb_root;
struct sched_dl_entity *earliest_dl;
unsigned int nr_running;
/* 核心:可跨CPU迁移的Deadline任务计数 */
unsigned int dl_nr_migratory;
struct dl_bandwidth dl_bw;
struct timer_list dl_timer;
};
nr_running:当前 CPU 就绪态 DL 任务总数量;dl_nr_migratory:当前 CPU 就绪态中允许跨核负载均衡迁移的 DL 任务数量。
1.3 可迁移与不可迁移 DL 任务定义
-
可迁移 DL 任务未设置 CPU 亲和性绑定、无独占带宽锁、未被内核标记为禁止迁移,允许调度负载均衡模块跨 CPU 调度迁移的实时任务。
-
不可迁移 DL 任务通过
sched_setaffinity绑定指定 CPU 核心、持有实时带宽预留锁、内核标记迁移禁用,不允许负载均衡随意迁移,只能在绑定核心运行的 DL 任务。
1.4 dl_nr_migratory 核心作用
- 为多核负载均衡提供O(1) 快速决策依据,无需遍历红黑树逐个判断任务迁移属性;
- 严格隔离可迁移 / 不可迁移 DL 任务,避免盲目迁移破坏硬实时时间确定性;
- 作为调度域负载均衡的判断依据,只有
dl_nr_migratory > 0时,才会尝试从当前 CPU 迁出 DL 任务; - 任务入队、出队、亲和性变更、迁移属性修改时,内核自动维护计数器增减,保证数值实时准确。
1.5 调度负载均衡基础术语
- 调度域:内核将多个 CPU 划分为调度域,负责域内核间负载均衡;
- 负载迁出:从高负载 CPU 挑选空闲任务迁移到低负载 CPU;
- 任务迁移封禁:DL 任务因实时约束禁止跨核调度。
二、环境准备
2.1 软硬件与版本选型
| 环境类别 | 版本配置 |
|---|---|
| 宿主系统 | Ubuntu 20.04 / 22.04 64 位 |
| 内核版本 | Linux 5.15、6.1、6.6 长期稳定版(主流工业实时内核基线) |
| 硬件架构 | x86_64 多核 CPU(至少 4 核,用于复现核间负载均衡) |
| 编译依赖 | gcc 9.4+、make、bison、flex、libssl-dev、libelf-dev |
| 调试工具 | ftrace、trace-cmd、perf、gdb、kgdb、debugfs |
2.2 内核源码编译与配置
1. 安装编译依赖
sudo apt update
sudo apt install build-essential libncurses-dev bison flex libssl-dev libelf-dev
2. 下载 Linux 6.1 LTS 内核源码
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.tar.xz
tar -xf linux-6.1.tar.xz
cd linux-6.1
3. 内核关键配置开启
复制当前内核配置并打开配置界面:
cp -v /boot/config-$(uname -r) .config
make menuconfig
必须开启以下选项:
CONFIG_SCHED_DEADLINE=y # 启用Deadline调度器
CONFIG_SCHED_DEBUG=y # 调度器调试开关
CONFIG_FTRACE=y # 函数跟踪,观测dl_nr_migratory变更
CONFIG_DEBUG_KERNEL=y # 内核基础调试
CONFIG_SMP=y # 多核SMP架构支持
4. 编译安装内核
make -j$(nproc)
sudo make modules_install
sudo make install
sudo update-grub
重启系统,在 GRUB 菜单选择新编译内核进入。
2.3 核心源码路径
- 结构体定义:
kernel/sched/sched.h - dl_nr_migratory 维护逻辑、负载均衡调用:
kernel/sched/deadline.c、kernel/sched/fair.c
三、应用场景
dl_nr_migratory 在多核工业实时 Linux 架构中是负载均衡的核心判断依据。工业运动控制器多核心分别承载伺服闭环控制、轨迹插补、故障告警、日志存储等 DL 实时任务,部分控制任务需绑定固定核心禁止迁移,普通监控任务允许核间均衡调度。内核通过 dl_nr_migratory 快速统计可迁移任务数,仅在有可迁移任务时发起负载均衡,避免迁移高实时约束任务导致控制周期抖动。自动驾驶域控制器多核分区调度、5G 基站基带实时信号处理、轨道交通车载实时控制系统中,均依靠该字段过滤不可迁移 DL 任务,在保证硬实时确定性的前提下,最大化利用多核算力,兼顾实时性与资源利用率。
四、实际案例与源码深度剖析
4.1 dl_nr_migratory 字段定义源码
// kernel/sched/sched.h
struct dl_rq {
struct rb_root rb_root;
struct sched_dl_entity *earliest_dl;
unsigned int nr_running;
/*
* dl_nr_migratory:
* 统计当前CPU就绪队列中允许跨CPU负载均衡迁移的DL任务数量
* 负载均衡流程以此字段作为快速判断条件
*/
unsigned int dl_nr_migratory;
struct dl_bandwidth dl_bw;
struct timer_list dl_timer;
};
代码说明:该计数器独立于总任务数nr_running,专门做可迁移任务统计,不参与调度选任务,只服务于多核负载均衡决策。
4.2 任务入队时 dl_nr_migratory 递增逻辑
任务唤醒、新建 DL 任务加入就绪队列时,判断任务是否允许迁移,决定计数器是否自增:
// kernel/sched/deadline.c 精简核心逻辑
static void dl_enqueue_task(struct rq *rq, struct task_struct *p, int flags)
{
struct dl_rq *dl_rq = &rq->dl_rq;
struct sched_dl_entity *dl_se = &p->dl;
/* 插入DL红黑树,按截止时间排序 */
__dl_enqueue_entity(rq, dl_se);
dl_rq->nr_running++;
/* 判断:任务允许跨CPU迁移,则可迁移计数+1 */
if (!task_non_migratory(p)) {
dl_rq->dl_nr_migratory++;
}
/* 刷新earliest_dl最早截止时间指针 */
dl_rq_update_earliest_dl(dl_rq);
}
代码解析:task_non_migratory(p) 是内核辅助函数,判断任务是否绑定 CPU 亲和性、是否禁止迁移;返回 false 代表可迁移,dl_nr_migratory 做加 1 统计。
4.3 任务出队时 dl_nr_migratory 递减逻辑
任务阻塞、休眠、执行完毕、迁出当前 CPU 时,若为可迁移任务,计数器自减:
// kernel/sched/deadline.c
static void dl_dequeue_task(struct rq *rq, struct task_struct *p, int flags)
{
struct dl_rq *dl_rq = &rq->dl_rq;
struct sched_dl_entity *dl_se = &p->dl;
/* 从红黑树移除调度实体 */
__dl_dequeue_entity(rq, dl_se);
dl_rq->nr_running--;
/* 可迁移任务出队,计数减1 */
if (!task_non_migratory(p)) {
dl_rq->dl_nr_migratory--;
}
/* 若删除的是最早截止任务,刷新earliest_dl */
if (dl_se == dl_rq->earliest_dl) {
dl_rq_update_earliest_dl(dl_rq);
}
}
核心逻辑:入队加、出队减,保证dl_nr_migratory数值和当前队列可迁移任务实时同步。
4.4 任务 CPU 亲和性变更时动态修正计数
当用户态通过sched_setaffinity修改 DL 任务 CPU 绑定掩码,任务从可迁移变为不可迁移或反之,内核会动态调整dl_nr_migratory:
// 伪代码逻辑,内核实际调用链路
void dl_task_affinity_change(struct rq *rq, struct task_struct *p)
{
struct dl_rq *dl_rq = &rq->dl_rq;
int old_mig = !task_non_migratory(p);
/* 修改亲和性配置 */
set_task_cpu_affinity(p);
int new_mig = !task_non_migratory(p);
/* 状态发生变化,修正计数器 */
if (old_mig && !new_mig) {
/* 可迁移 → 不可迁移:计数减1 */
dl_rq->dl_nr_migratory--;
} else if (!old_mig && new_mig) {
/* 不可迁移 → 可迁移:计数加1 */
dl_rq->dl_nr_migratory++;
}
}
作用:不重启任务、不重新入队出队,动态适配亲和性变更,保证计数精准。
4.5 负载均衡中读取 dl_nr_migratory 做决策
多核负载均衡核心逻辑,直接读取字段快速判断是否有可迁移 DL 任务:
// kernel/sched/fair.c 负载均衡节选
static bool dl_has_migratory_tasks(struct rq *rq)
{
struct dl_rq *dl_rq = &rq->dl_rq;
/* 只要可迁移计数大于0,就存在可迁出的DL任务 */
return dl_rq->dl_nr_migratory > 0;
}
代码价值:O (1) 直接读取计数器,无需遍历红黑树所有 DL 任务,极大降低负载均衡决策开销。
4.6 用户态测试 DL 任务代码(可直接编译运行)
编写 Deadline 测试程序,支持设置 CPU 亲和性,观测 dl_nr_migratory 变化:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/sched.h>
#include <sys/syscall.h>
#include <sched.h>
#define RUNTIME 100000
#define PERIOD 1000000
static int sched_setattr(pid_t pid, struct sched_attr *attr, unsigned int flags)
{
return syscall(SYS_sched_setattr, pid, attr, flags);
}
/* 绑定任务到指定CPU核心 */
void set_cpu_affinity(int cpu)
{
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpu, &cpuset);
sched_setaffinity(0, sizeof(cpu_set_t), &cpuset);
}
int main(void)
{
struct sched_attr attr;
int ret;
attr.size = sizeof(attr);
attr.sched_policy = SCHED_DEADLINE;
attr.sched_flags = 0;
attr.sched_runtime = RUNTIME;
attr.sched_deadline = PERIOD;
attr.sched_period = PERIOD;
/* 设置为Deadline调度任务 */
ret = sched_setattr(0, &attr, 0);
if (ret < 0) {
perror("sched_setattr fail");
return -1;
}
printf("DL任务创建成功,runtime=%d period=%d\n", RUNTIME, PERIOD);
sleep(5);
printf("绑定任务到CPU0,变为不可迁移任务\n");
set_cpu_affinity(0);
while (1) {
usleep(500000);
}
return 0;
}
编译与运行命令:
gcc dl_mig_test.c -o dl_mig_test
sudo ./dl_mig_test
4.7 Ftrace 跟踪 dl_nr_migratory 相关内核函数
通过 ftrace 观测计数器增减、任务入队出队链路:
# 挂载debugfs
sudo mount -t debugfs none /sys/kernel/debug
# 清空跟踪缓存
echo > /sys/kernel/debug/tracing/trace
# 设置跟踪函数
echo dl_enqueue_task >> /sys/kernel/debug/tracing/set_ftrace_filter
echo dl_dequeue_task >> /sys/kernel/debug/tracing/set_ftrace_filter
# 开启函数跟踪
echo function > /sys/kernel/debug/tracing/current_tracer
echo 1 > /sys/kernel/debug/tracing/tracing_on
另起终端运行测试程序,之后停止跟踪并查看日志:
echo 0 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace
通过日志可清晰看到:任务入队、绑定 CPU 亲和性后,dl_nr_migratory自动减 1,完全匹配内核源码逻辑。
五、常见问题与解答
Q1:dl_nr_migratory 和 nr_running 有什么本质区别?
解答:nr_running是当前 CPU 所有就绪 DL 任务总个数;dl_nr_migratory是其中允许负载均衡跨核迁移的子集计数。前者用于队列调度管理,后者专门服务多核负载均衡决策,二者独立维护、互不替代。
Q2:修改任务 CPU 亲和性后,为什么不用重新入队出队也能修正计数?
解答:内核在sched_setaffinity系统调用内部做了钩子,检测 DL 任务亲和性状态变更,自动比对新旧迁移属性,动态加减dl_nr_migratory,避免不必要的红黑树重入队开销,性能更优。
Q3:dl_nr_migratory 计数会不会出现溢出或错乱?
解答:正常内核路径下不会。所有修改 DL 任务就绪状态、亲和性、迁移属性的内核接口,都严格成对维护入队加、出队减;只有内核模块非法篡改 dl_rq 内存、手动破坏任务调度实体属性,才会导致计数错乱,表现为负载均衡误判可迁移任务。
Q4:为什么负载均衡不直接遍历所有 DL 任务判断迁移属性?
解答:高实时任务并发场景下,红黑树遍历是 O (logN) 甚至 O (N) 开销,多核调度域频繁负载均衡会累积巨大时延;借助dl_nr_migratory O (1) 读取,把均衡决策开销降到最低,保障实时系统低抖动。
Q5:绑定 CPU 的 DL 任务,会不会被 dl_nr_migratory 统计?
解答:不会。绑定指定 CPU 亲和性的任务被标记为non_migratory,入队时不会给dl_nr_migratory加计数,负载均衡也不会选中这类任务做跨核迁移,保障实时任务运行核心固定。
六、实践建议与最佳实践
-
内核源码研读技巧学习时重点跟进
dl_enqueue_task、dl_dequeue_task两个入口函数,顺着调用链路跟踪dl_nr_migratory增减逻辑;配合 ftrace 动态跟踪,比静态读源码更容易理解计数器维护时机。 -
实时任务开发规范对控制类、闭环采样类强实时 DL 任务,务必手动绑定固定 CPU 核心,使其不进入
dl_nr_migratory统计,禁止负载均衡迁移;对监控、日志、低优先级实时任务,不设置亲和性,允许核间均衡,提升整机算力利用率。 -
性能调优建议多核实时系统中,尽量减少频繁修改 DL 任务 CPU 亲和性,每次变更都会触发
dl_nr_migratory修正与调度域重新均衡,产生微小调度抖动;业务部署阶段提前规划任务核绑定策略。 -
负载均衡排错技巧遇到 DL 任务莫名跨核迁移、实时抖动变大时,优先排查:①任务是否意外取消 CPU 亲和性;②
dl_nr_migratory计数是否异常偏大;③负载均衡域配置是否不合理,快速定位根因。 -
内核定制开发建议自研实时调度策略时,可复用
dl_nr_migratory的设计思想,拆分可调度、可迁移、可抢占等多维计数器,保留 O (1) 快速决策路径,不要回归遍历查询的低效实现。
七、总结与应用延伸
本文系统性拆解了 Linux Deadline 调度器dl_nr_migratory字段的设计初衷、核心概念、源码实现、计数维护逻辑、负载均衡联动机制,搭配可直接编译的测试代码、ftrace 调试命令、真实工程场景分析,完整覆盖理论、源码、实操、排错、调优全流程。
dl_nr_migratory的核心设计思想,是用空间换时间、用计数器替代遍历查询,在不破坏硬实时任务时间确定性的前提下,为多核调度负载均衡提供极简、高效的决策依据,是 Linux 实时调度架构中轻量化性能优化的经典设计。
在工业机器人控制、自动驾驶域控制器、航空航天嵌入式实时系统、5G 基站基带处理等工程场景中,该字段默默支撑着多核算力负载均衡与实时任务确定性的平衡;对于内核研究者、高校论文撰写、企业技术报告调研,本文的源码解析、代码示例、逻辑梳理可直接作为参考素材。
建议读者自行编译内核、运行测试程序、用 ftrace 跟踪函数调用,修改任务 CPU 亲和性观察计数器变化,彻底吃透可迁移任务统计与多核负载均衡的联动逻辑,将所学落地到实时 Linux 系统裁剪、调度策略定制、线上实时问题排查工作中。
更多推荐




所有评论(0)