Linux Deadline 调度器的 dl_nr_running:Deadline 任务数量统计
简介
在 Linux 内核调度体系中,SCHED_DEADLINE作为硬实时调度策略,依托 EDF 最早截止时间优先算法与 CBS 恒定带宽服务器机制,承担着工业控制、自动驾驶域控制器、航空航天实时测控、5G 基带处理、专业音视频低延迟编解码等核心场景的确定性调度任务。
内核为每个 CPU 独立维护dl_rq调度运行队列,专门管理本 CPU 上所有就绪、运行、待调度的 Deadline 实时任务。而dl_nr_running作为dl_rq结构体中最基础、最核心的计数字段,专职统计当前 CPU 就绪队列中处于可调度状态的 Deadline 任务总数量。
很多初学者容易把dl_nr_running单纯理解成一个普通计数器,实则它是内核负载均衡、带宽准入控制、调度路径分支判断、任务迁移决策、限流节流触发的核心依据。内核在多核负载均衡时,依靠各 CPU 的dl_nr_running数值判断 CPU 间实时任务负载差异;在 Deadline 带宽准入校验时,结合任务数量与 CPU 总带宽判断是否允许新建实时任务入队;在调度器快速路径中,通过该字段快速判定队列是否为空,跳过无意义的红黑树遍历与调度计算。
对于 Linux 内核研发、嵌入式实时系统工程师、自动驾驶底层软件开发者、做调度子系统课题研究的学生而言,吃透dl_nr_running的增减逻辑、更新时机、源码调用链路、负载均衡协同机制,是理解 Deadline 调度器整体架构、排查实时任务负载不均、定制实时调度策略、撰写内核相关论文与工程报告的必备基础。本文以一线 Linux 工程师实战视角,从核心概念、环境搭建、源码逐行剖析、实操编程、问题排查到工程最佳实践,完整拆解dl_nr_running底层运行逻辑,全文源码充足、步骤可复现,可直接用于项目调研与学术写作。
一、核心概念与术语解析
1.1 SCHED_DEADLINE 调度基础模型
Deadline 任务采用经典三元组调度模型:
runtime:任务单个周期内最大占用 CPU 时长(纳秒级);period:任务周期性触发的时间间隔;deadline:任务必须完成执行的最晚截止时间,默认等于 period,支持自定义配置。调度规则基于EDF 最早截止时间优先,同时内置 CBS 带宽隔离机制,避免实时任务之间互相抢占干扰,保障时间确定性。
1.2 dl_rq 每 CPU Deadline 私有运行队列
内核为系统每一个 CPU 核心,都单独创建struct dl_rq运行队列,是管理本 CPU 所有 Deadline 任务的容器,定义在kernel/sched/sched.h。关键成员如下:
struct dl_rq {
// 红黑树根节点,任务按截止时间升序排序
struct rb_root rb_root;
// 缓存队列中截止时间最早的任务
struct sched_dl_entity *earliest_dl;
// 核心字段:当前CPU就绪态Deadline任务统计计数
unsigned int dl_nr_running;
// 实时带宽管理、定时器、带宽节流相关成员
struct dl_bandwidth dl_bw;
struct timer_list dl_timer;
};
1.3 dl_nr_running 字段核心定义
dl_nr_running是每 CPU 级别的无符号整型计数器,语义严格定义为:当前 CPU 的 Deadline 运行队列中,处于就绪态、可被调度执行的实时任务总数。关键特性:
- 任务入队就绪:计数加 1;
- 任务出队阻塞、睡眠、迁移离开当前 CPU:计数减 1;
- 任务仅时间片耗尽被限流、未离开就绪队列:计数不减少;
- 每个 CPU 的
dl_nr_running完全独立,互不干扰,是多核负载均衡的基础数据。
1.4 关联核心术语
- 就绪态 DL 任务:任务已唤醒、无阻塞、等待 CPU 调度的 Deadline 任务;
- 带宽准入控制:内核根据 CPU 总实时带宽、现有任务数量,限制新增 DL 任务创建,防止系统过载;
- 多核负载均衡:内核周期比对各 CPU 的
dl_nr_running,将高负载 CPU 的 DL 任务迁移到低负载 CPU; - 任务入队 / 出队:任务加入 DL 红黑树队列称为入队,从队列移除称为出队,是
dl_nr_running变更的唯一触发场景。
1.5 与普通 rq.nr_running 的区别
rq->nr_running:统计当前 CPU 所有普通进程、实时进程、Deadline 任务的总就绪数;dl_rq->dl_nr_running:仅单独统计 Deadline 调度策略的就绪任务,做精细化实时负载统计,为实时调度专属逻辑提供数据支撑。
二、环境准备
2.1 软硬件环境配置
| 环境类别 | 版本与配置要求 |
|---|---|
| 操作系统 | Ubuntu 20.04 / Ubuntu 22.04 64 位 |
| 内核版本 | Linux 5.15 LTS、Linux 6.1 LTS、Linux 6.6 LTS(源码逻辑一致,适配性最好) |
| 硬件架构 | x86_64 多核 CPU,4 核 8G 及以上,支持内核调试、ftrace 跟踪 |
| 编译依赖 | gcc 9.4+、make、bison、flex、libncurses-dev、libssl-dev、libelf-dev |
| 调试工具 | gdb、kgdb、perf、trace-cmd、ftrace、systemtap |
2.2 内核源码获取与编译配置
1. 安装编译依赖
sudo apt update
sudo apt install -y build-essential libncurses-dev bison flex libssl-dev libelf-dev
作用:安装内核编译、配置、调试必备工具链,确保后续源码编译无依赖缺失。
2. 下载 Linux 6.1 长期支持内核源码
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_DEBUG_KERNEL=y # 内核调试开关
CONFIG_SCHED_DEBUG=y # 调度器调试接口
CONFIG_FTRACE=y # 函数跟踪,观测dl_nr_running调用链路
CONFIG_KGDB=y # 内核远程调试,可直接查看dl_rq字段数值
CONFIG_SMP=y # 多核支持,负载均衡必备
4. 编译并安装内核
make -j$(nproc)
sudo make modules_install
sudo make install
sudo update-grub
重启设备,在 GRUB 菜单选择新编译的内核进入实验环境。
2.3 核心源码路径
kernel/sched/deadline.c // dl_nr_running增减、维护、负载均衡逻辑
kernel/sched/sched.h // dl_rq、sched_dl_entity 结构体定义
kernel/sched/fair.c // 多核负载均衡中读取dl_nr_running做负载判断
三、应用场景
dl_nr_running作为 Deadline 任务数量的精准统计指标,在工业实时 Linux 落地场景中起到基石作用。工业机器人多轴伺服控制场景下,轨迹规划、电机闭环控制、故障诊断等多个 Deadline 周期任务常驻 CPU,内核通过dl_nr_running统计单 CPU 实时任务密度,触发多核负载均衡,避免单核心任务过多导致调度抖动超标。自动驾驶域控制器中,环境感知、路径规划、车辆制动控制等高优先级硬实时任务,依靠dl_nr_running做带宽准入校验,防止无限制创建实时任务造成 CPU 过载、任务超时。同时在 5G 基站基带信号处理、轨道交通信号实时调度、专业音视频超低延迟编解码场景中,内核均通过dl_nr_running判断队列是否为空、决策调度分支、平衡多核实时负载,从底层保障实时系统的稳定性与时间确定性。
四、实际案例与源码深度剖析
4.1 dl_nr_running 基础增减核心源码
4.1.1 任务入队时 dl_nr_running 自增
任务唤醒、新建 Deadline 任务加入就绪队列时,计数加 1,源码位于deadline.c:
static void dl_add_task(struct rq *rq, struct sched_dl_entity *dl_se)
{
struct dl_rq *dl_rq = &rq->dl_rq;
struct rb_node **link, *parent = NULL;
struct sched_dl_entity *entry;
// 按任务截止时间大小,插入红黑树合适位置
link = &dl_rq->rb_root.rb_node;
while (*link) {
parent = *link;
entry = rb_entry(parent, struct sched_dl_entity, rb_node);
if (dl_se->dl_deadline < entry->dl_deadline)
link = &(*link)->rb_left;
else
link = &(*link)->rb_right;
}
rb_link_node(&dl_se->rb_node, parent, link);
rb_insert_color(&dl_se->rb_node, &dl_rq->rb_root);
// 关键:Deadline任务入队就绪,任务计数+1
dl_rq->dl_nr_running++;
// 维护earliest_dl最早截止时间指针
if (!dl_rq->earliest_dl || dl_se->dl_deadline < dl_rq->earliest_dl->dl_deadline) {
dl_rq->earliest_dl = dl_se;
}
}
代码说明:只要任务成功加入 DL 红黑树就绪队列,dl_nr_running就执行自增,代表当前 CPU 新增一个可调度实时任务。
4.1.2 任务出队时 dl_nr_running 自减
任务阻塞、睡眠、执行结束、迁移出当前 CPU 队列时,计数减 1:
static void dl_del_task(struct rq *rq, struct sched_dl_entity *dl_se)
{
struct dl_rq *dl_rq = &rq->dl_rq;
// 从红黑树移除任务节点
rb_erase(&dl_se->rb_node, &dl_rq->rb_root);
// 关键:任务离开就绪队列,任务计数-1
dl_rq->dl_nr_running--;
// 如果删除的是当前最早截止任务,刷新缓存指针
if (dl_se == dl_rq->earliest_dl) {
dl_rq_update_earliest_dl(dl_rq);
}
}
代码说明:任务只要从就绪红黑树中移除,必然触发dl_nr_running自减,保证计数与实际就绪任务数量严格一致。
4.2 利用 dl_nr_running 做调度分支判断
调度器挑选下一个任务时,先通过dl_nr_running快速判空,避免无效遍历:
struct task_struct *pick_next_task_dl(struct rq *rq)
{
struct dl_rq *dl_rq = &rq->dl_rq;
// 快速路径:无就绪Deadline任务,直接返回,无需后续遍历
if (!dl_rq->dl_nr_running)
return NULL;
// 有就绪任务,通过earliest_dl O(1)获取最优任务
return dl_task_of(dl_rq->earliest_dl);
}
代码价值:利用dl_nr_running做前置判断,队列空则直接退出调度分支,省去红黑树查找开销,是内核调度快速路径的典型优化思路。
4.3 多核负载均衡中读取 dl_nr_running
内核负载均衡模块通过对比各 CPU 的dl_nr_running,识别实时任务负载不均:
// 负载均衡简易逻辑示意
static int dl_rq_overloaded(struct rq *rq)
{
struct dl_rq *dl_rq = &rq->dl_rq;
// 单CPU Deadline任务数量超过阈值,判定为过载
return dl_rq->dl_nr_running > 4;
}
内核周期扫描所有 CPU 运行队列,若某 CPUdl_nr_running数值远高于其他 CPU,会触发 Deadline 任务迁移,平衡多核实时负载。
4.4 用户态编写测试 Deadline 任务程序
编写可直接编译运行的测试代码,创建多个 SCHED_DEADLINE 任务,观察计数变化:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/sched.h>
#include <sys/syscall.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);
}
// 创建Deadline任务
void create_dl_task()
{
struct sched_attr attr;
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;
if (sched_setattr(0, &attr, 0) < 0) {
perror("sched_setattr failed");
exit(1);
}
while(1) {
usleep(5000);
}
}
int main()
{
// 创建3个Deadline实时任务
for(int i = 0; i < 3; i++) {
if(fork() == 0) {
create_dl_task();
}
}
while(1) {
sleep(1);
}
return 0;
}
编译运行命令:
gcc dl_nr_test.c -o dl_nr_test
sudo ./dl_nr_test
实操说明:运行程序后系统会创建 3 个 Deadline 周期任务,绑定在同一 CPU 核心上,此时对应 CPU 的dl_nr_running数值会变为 3。
4.5 Ftrace 跟踪 dl_nr_running 相关函数
通过 ftrace 跟踪任务入队、出队函数,观测计数变更时机:
# 挂载调试文件系统
mount -t debugfs none /sys/kernel/debug
# 清空跟踪日志
echo > /sys/kernel/debug/tracing/trace
# 设置跟踪函数
echo dl_add_task >> /sys/kernel/debug/tracing/set_ftrace_filter
echo dl_del_task >> /sys/kernel/debug/tracing/set_ftrace_filter
# 开启函数跟踪
echo function > /sys/kernel/debug/tracing/current_tracer
echo 1 > /sys/kernel/debug/tracing/tracing_on
另开终端执行测试程序:
sudo ./dl_nr_test
停止跟踪并查看日志:
echo 0 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace
通过日志可清晰看到dl_add_task调用次数与创建任务数量一致,每调用一次对应dl_nr_running加 1。
五、常见问题与解答
Q1:dl_nr_running 统计的是所有 DL 任务,还是仅就绪态任务?
解答:只统计当前 CPU 处于就绪态、可被调度的 Deadline 任务。已经休眠、阻塞、被带宽限流暂时挂起的任务,不在统计范围内;只有进入红黑树就绪队列的任务,才会计数。
Q2:dl_nr_running 会不会出现计数错乱、数值不准的情况?
解答:内核原生路径下不会错乱。dl_add_task和dl_del_task成对维护增减,且调度队列操作自带锁保护,防止并发竞态。只有内核模块非法篡改dl_rq结构体、自定义调度补丁存在逻辑 BUG 时,才会出现计数偏差,进而导致负载均衡判断异常。
Q3:多核场景下,任务跨 CPU 迁移会如何影响两端 dl_nr_running?
解答:任务从 CPU0 迁移到 CPU1 时,CPU0 执行dl_del_task,dl_nr_running减 1;CPU1 执行dl_add_task,dl_nr_running加 1。两端计数同步更新,保证每个 CPU 的任务统计始终精准。
Q4:dl_nr_running 为 0 代表什么业务状态?
解答:代表当前 CPU 没有任何就绪态 Deadline 实时任务,调度器会直接跳过 EDF 调度分支,降级调度普通 CFS 进程,节省 CPU 调度开销。
Q5:能否在用户态直接读取 dl_nr_running 数值?
解答:原生内核未提供用户态接口,只能通过内核调试器 kgdb、ftrace、systemtap 在内核态查看;可自行添加 proc 文件系统接口,导出该字段数值,用于实时负载监控。
六、实践建议与最佳实践
-
内核源码研读技巧学习
dl_nr_running不要孤立看字段,要跟着dl_add_task、dl_del_task、负载均衡函数串联梳理,结合 ftrace 动态跟踪执行流程,比静态读源码更容易理解计数变更逻辑。 -
实时任务部署最佳实践工业实时项目中,通过观察各 CPU
dl_nr_running数值,手动做任务 CPU 亲和性绑定,把 Deadline 任务均匀分摊到多个核心,避免单核心任务扎堆导致调度时延增大、抖动超标。 -
性能调试优化技巧排查实时系统卡顿、负载不均问题时,优先核查各 CPU
dl_nr_running分布,若某核心数值远高于其他核心,基本可判定为实时任务负载失衡,优先做任务迁移或亲和性调整。 -
内核定制开发规范自研 Deadline 调度扩展功能时,严禁绕过
dl_add_task/dl_del_task直接修改dl_nr_running,必须遵循内核原有入队出队接口维护计数,否则会破坏负载均衡、调度判空逻辑,引发系统异常。 -
高并发实时任务设计不要在单个 CPU 核心上创建过多 Deadline 任务,控制
dl_nr_running合理阈值,减少红黑树操作与负载均衡触发频率,降低内核调度开销。
七、总结与应用延伸
本文系统拆解了 Linux Deadline 调度器dl_nr_running字段的设计思想、计数维护逻辑、源码实现、调度分支应用、多核负载均衡协同机制,配合可直接复现的内核编译、ftrace 跟踪、用户态测试代码,完整覆盖理论与实战落地。
dl_nr_running看似只是一个简单任务计数器,实则是 Deadline 调度器的负载感知基石:既是调度器快速判空、跳过无效遍历的优化依据,也是多核负载均衡的数据源、带宽准入控制的判断条件、实时系统负载监控的核心指标。
在工程落地层面,该字段支撑着工业机器人、自动驾驶、5G 基站、航空航天测控等硬实时场景的负载均衡与调度稳定性;在学术研究与内核开发层面,掌握dl_nr_running的维护逻辑,能够深入理解 Linux 每 CPU 调度队列设计、实时任务管理机制、多核负载均衡架构,可直接用于内核课题论文、实时 Linux 系统裁剪、定制化调度策略开发。
建议读者基于本文提供的源码和实操命令,自行编译内核、创建多组 Deadline 任务,结合 ftrace 观察dl_nr_running数值变化,修改内核阈值观察负载均衡行为,真正从源码底层吃透 Linux Deadline 任务数量统计与调度运行机制。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)