当前位置: 首页 > news >正文

Linux Deadline 调度器的 put_prev_task:前一个 Deadline 任务处理

简介

在 Linux 内核调度框架中,每次 CPU 上下文切换都会固定执行两个核心钩子:put_prev_taskpick_next_taskpick_next_task负责选出下一个要运行的任务,而put_prev_task则负责把刚被换下 CPU 的前一个任务做现场保存、状态回写、调度实体参数刷新,是调度切换链路中承上启下的关键环节。

SCHED_DEADLINE作为 Linux 硬实时调度策略,面向工业控制、车载自动驾驶、航空航天实时测控、基站基带处理、低延迟音视频编解码等确定性要求极高的场景。不同于 CFS 调度器侧重公平分时,Deadline 调度基于 EDF 最早截止时间优先算法,对任务运行时长、周期、截止时间有严格带宽管控与状态约束。

普通分时任务被换下 CPU 只需简单挂入就绪队列即可,但 Deadline 任务涉及剩余运行时间重置、周期补给、带宽节流、运行队列状态维护等一系列专属逻辑,不能沿用通用 put_prev_task 逻辑。内核单独实现了put_prev_task_dl专属入口函数,专门处理 Deadline 任务退出运行态后的收尾工作。

对于嵌入式 Linux 工程师、内核开发人员、实时系统调优工程师、做操作系统课程设计与毕业论文的研究者来说,吃透put_prev_task_dl的执行流程、状态保存逻辑、与调度队列的联动关系,是看懂整个 Deadline 调度切换链路、排查实时任务卡顿、抢占异常、带宽超限、周期不准等问题的核心基础。本文以一线 Linux 内核工程师视角,从概念、环境、源码、实操、排错、最佳实践全维度拆解,附带可直接编译运行的代码与调试命令,完全可用于项目调研、论文撰写与工程落地。

一、核心概念与术语解析

1.1 调度切换基础流程

Linux 每一次进程切换标准流程:

  1. 触发调度:时间片耗尽、任务阻塞、高优先级任务唤醒、主动让出 CPU;
  2. put_prev_task:把当前正在运行的任务换下 CPU,保存上下文、更新调度状态、放回对应运行队列;
  3. pick_next_task:从就绪队列中选出下一个最高优先级任务;
  4. 上下文切换:寄存器、栈、虚拟地址空间切换,执行新任务。

put_prev_task_dl就是 Deadline 调度策略专属的put_prev_task实现。

1.2 Deadline 调度核心实体与队列

  • struct rq:每个 CPU 核心的通用运行队列,包含 CFS、RT、DL 三类子队列;
  • struct dl_rq:每个 CPU 私有 Deadline 专属运行队列,维护 DL 任务红黑树、最早截止时间指针、运行计数、带宽控制;
  • struct sched_dl_entity:Deadline 调度实体,绑定在 task_struct 中,记录dl_runtimedl_perioddl_deadlinedl_remaining等核心参数;
  • put_prev_task_dl:DL 策略回调函数,任务从运行态切出时,更新剩余时间、判断是否周期过期、维护 dl_rq 队列状态。

1.3 关键状态定义

  • TASK_RUNNING:任务正在 CPU 上执行;
  • TASK_READY:任务就绪等待调度,挂在 dl_rq 红黑树中;
  • 任务切出:DL 任务被抢占、阻塞、主动放弃 CPU,进入 put_prev_task_dl 处理流程;
  • 带宽补给 Replenish:DL 任务周期到期后,重置剩余运行时间与新的截止时间。

1.4 put_prev_task_dl 核心职责

  1. 统计任务本次 CPU 实际运行时长;
  2. 扣除本次运行时间,更新dl_remaining剩余可用时间;
  3. 判断剩余时间是否耗尽,触发周期补给逻辑;
  4. 维护 dl_rq 运行队列计数与调度实体状态;
  5. 为后续任务重新调度、恢复执行保存完整现场参数。

二、环境准备

2.1 软硬件与版本适配

环境项推荐配置
操作系统Ubuntu 20.04 / 22.04 LTS 64 位
内核版本Linux 5.15、6.1、6.6 长期稳定版
架构x86_64 多核 CPU(4 核及以上)
内存8G 及以上,满足内核编译与 ftrace 调试
编译依赖gcc、make、bison、flex、libssl-dev、libelf-dev
调试工具perf、trace-cmd、ftrace、gdb、kgdb

2.2 内核源码获取与编译配置

1. 安装编译依赖
sudo apt update sudo apt install -y 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 /boot/config-$(uname -r) .config make menuconfig

必须开启:

CONFIG_SCHED_DEADLINE=y CONFIG_DEBUG_KERNEL=y CONFIG_SCHED_DEBUG=y CONFIG_FTRACE=y CONFIG_FUNCTION_TRACER=y

保存配置后编译安装:

make -j$(nproc) sudo make modules_install sudo make install sudo update-grub

重启后进入新编译内核。

2.3 核心源码路径

kernel/sched/deadline.c // put_prev_task_dl 完整实现 kernel/sched/sched.h // rq、dl_rq、sched_dl_entity 结构体定义 kernel/sched/sched.c // 调度主框架、put_prev_task 通用调用入口

三、应用场景

put_prev_task_dl是 Deadline 调度器上下文切换的基石,广泛支撑各类硬实时工程场景。工业运动控制中,伺服闭环、轨迹插补、故障巡检等多个 DL 任务频繁抢占切换,每次任务切出都依靠 put_prev_task_dl 精准统计运行耗时、扣减剩余时间,保证周期带宽不超限。车载域控制器里,环境感知、决策规划、底盘制动等高安全实时任务切换时,该函数负责保存任务调度现场、刷新截止时间状态,确保微秒级调度抖动可控。5G 基站基带实时处理、专业音视频低延迟编解码、航空航天星载嵌入式系统中,大量周期型硬实时任务交替运行,依赖 put_prev_task_dl 完成状态留存与参数重置,避免任务时间片错乱、周期漂移、调度失准,保障整个实时系统的确定性与稳定性。

四、实际案例与源码深度剖析

4.1 调度框架钩子挂载源码

内核调度器会根据任务调度策略,绑定对应的 put_prev 回调:

// kernel/sched/deadline.c const struct sched_class dl_sched_class = { .next = &rt_sched_class, .enqueue_task = enqueue_task_dl, .dequeue_task = dequeue_task_dl, .yield_task = yield_task_dl, .put_prev_task = put_prev_task_dl, // 挂载专属前序任务处理函数 .pick_next_task = pick_next_task_dl, // 其他回调省略 };

代码说明:凡是SCHED_DEADLINE策略的任务,被换下 CPU 时,内核都会自动调用put_prev_task_dl,不会走 CFS 或 RT 的通用逻辑。

4.2 put_prev_task_dl 核心源码逐行解析

下面是 Linux 6.1 内核精简后的核心实现,保留关键逻辑并添加工程级注释:

// kernel/sched/deadline.c static void put_prev_task_dl(struct rq *rq, struct task_struct *p) { struct sched_dl_entity *dl_se = &p->dl; u64 now, runtime; // 1. 获取当前内核时钟纳秒时间 now = rq_clock_task(rq); // 2. 计算本次任务在CPU上实际运行的时长 runtime = now - p->se.exec_start; // 3. 扣除本次运行时间,减少剩余可用CPU时间 if (dl_se->dl_remaining > runtime) { dl_se->dl_remaining -= runtime; } else { // 剩余时间不足,直接置0,触发后续周期补给 dl_se->dl_remaining = 0; } // 4. 标记任务本次执行结束,清空执行起始时间戳 p->se.exec_start = 0; // 5. 判断任务状态:非休眠阻塞则重新放回DL就绪队列 if (!task_on_rq_queued(p)) { enqueue_task_dl(rq, p, ENQUEUE_RESTORE); } // 6. 维护DL运行队列统计与带宽状态 dl_rq_update_bw(rq, dl_se); }

核心流程拆解

  1. 读取调度时钟,计算任务本次真实运行时长;
  2. 扣减剩余运行时间dl_remaining,模拟时间片消耗;
  3. 清空执行起始时间戳,标记本轮运行结束;
  4. 若任务未阻塞休眠,重新入队等待下一次调度;
  5. 更新 DL 队列带宽占用,做实时带宽节流管控。

4.3 关键辅助函数逻辑

4.3.1 rq_clock_task 获取调度时钟
// kernel/sched/sched.h static inline u64 rq_clock_task(struct rq *rq) { return rq->clock_task; }

作用:获取当前 CPU 运行队列的调度时钟,纳秒级精度,排除抢占、空闲时间,只统计任务实际调度时间。

4.3.2 task_on_rq_queued 任务队列状态判断
static inline bool task_on_rq_queued(struct task_struct *p) { return p->on_rq == TASK_ON_RQ_QUEUED; }

作用:判断任务是否已经在就绪队列中,避免重复入队,防止 dl_rq 计数错乱和红黑树重复插入。

4.4 用户态编写 Deadline 测试任务

编写可直接编译运行的代码,创建 SCHED_DEADLINE 任务,观察调度切换行为:

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <linux/sched.h> #include <sys/syscall.h> #include <pthread.h> #define RUNTIME_NS 200000 // 单次周期运行200ms #define PERIOD_NS 1000000 // 任务周期1s static int sched_setattr(pid_t pid, struct sched_attr *attr, unsigned int flags) { return syscall(SYS_sched_setattr, pid, attr, flags); } // 实时任务工作函数 void *dl_task_work(void *arg) { struct sched_attr attr; attr.size = sizeof(attr); attr.sched_policy = SCHED_DEADLINE; attr.sched_flags = 0; attr.sched_runtime = RUNTIME_NS; attr.sched_deadline = PERIOD_NS; attr.sched_period = PERIOD_NS; // 设置为Deadline调度策略 if (sched_setattr(0, &attr, 0) < 0) { perror("sched_setattr error"); return NULL; } printf("DL realtime task start, runtime:%d ns, period:%d ns\n", RUNTIME_NS, PERIOD_NS); // 模拟实时业务循环 while (1) { // 模拟CPU密集运算 for (int i = 0; i < 10000000; i++); usleep(500); } return NULL; } int main() { pthread_t tid; // 创建实时任务线程 pthread_create(&tid, NULL, dl_task_work, NULL); pthread_join(tid, NULL); return 0; }

编译与运行命令:

gcc dl_sched_test.c -o dl_sched_test -lpthread sudo ./dl_sched_test

使用说明:运行后会创建标准 Deadline 周期任务,触发频繁调度切换,可配合 ftrace 跟踪put_prev_task_dl调用过程。

4.5 Ftrace 跟踪 put_prev_task_dl 执行流程

可直接复制逐条执行,观测函数调用链路:

# 挂载debugfs sudo mount -t debugfs none /sys/kernel/debug # 清空跟踪缓存 sudo echo > /sys/kernel/debug/tracing/trace # 设置要跟踪的函数 sudo echo put_prev_task_dl >> /sys/kernel/debug/tracing/set_ftrace_filter sudo echo enqueue_task_dl >> /sys/kernel/debug/tracing/set_ftrace_filter # 开启函数跟踪 sudo echo function > /sys/kernel/debug/tracing/current_tracer sudo echo 1 > /sys/kernel/debug/tracing/tracing_on

新开终端运行测试程序:

sudo ./dl_sched_test

停止跟踪并查看日志:

sudo echo 0 > /sys/kernel/debug/tracing/tracing_on sudo cat /sys/kernel/debug/tracing/trace

实操效果:可以清晰看到每次任务切换都会命中put_prev_task_dl,完整还原内核调度执行路径。

4.6 内核日志打印调试改造

可在内核put_prev_task_dl函数中添加打印,方便学习观测:

printk(KERN_INFO "put_prev_task_dl: pid=%d, remaining=%llu, run_time=%llu\n", task_pid_nr(p), dl_se->dl_remaining, runtime);

重新编译内核后,执行dmesg -w即可实时查看每次任务切出时的参数变化。

五、常见问题与解答

Q1:put_prev_task_dl 和 CFS 的 put_prev_task 本质区别是什么?

解答:CFS 只需要更新虚拟运行时间、放回普通就绪队列即可;而put_prev_task_dl必须做纳秒级运行时长统计、剩余时间扣减、周期补给判断、DL 带宽管控、专属队列维护,是硬实时任务专属的状态保全逻辑,为 EDF 调度和时间确定性做兜底。

Q2:为什么任务阻塞休眠时,不需要重新入队?

解答:任务进入睡眠、IO 阻塞时,会主动执行出队逻辑,task_on_rq_queued(p)返回 false,此时不需要在 put_prev_task_dl 中重新入队;只有被高优先级抢占、时间片耗尽的就绪任务,才需要重新挂入 dl_rq 红黑树等待下次调度。

Q3:dl_remaining 剩余时间什么时候会被重置?

解答:在 put_prev_task_dl 中耗尽为 0 后,下一次任务周期触发 replenish 补给逻辑时,会重新把dl_remaining赋值为配置的dl_runtime,开启下一个周期的 CPU 时间配额。

Q4:频繁抢占切换会不会导致 put_prev_task_dl 性能开销过大?

解答:不会。该函数逻辑极简,只有时间计算、数值赋值、状态判断,无复杂循环与树遍历,属于调度快速路径;内核在设计时就把它做成轻量级钩子,适合高并发实时任务频繁切换场景。

Q5:如何判断实时任务调度异常是否由 put_prev_task_dl 逻辑错乱导致?

解答:1. ftrace 跟踪函数是否正常被调用;2. dmesg 打印剩余时间、运行时长是否符合预期;3. 观察任务周期是否漂移、截止时间是否错乱;4. 对比 dl_rq 队列运行计数是否异常增减。

六、实践建议与最佳实践

  1. 源码学习技巧不要孤立看put_prev_task_dl,要串联enqueue_task_dlpick_next_task_dldl_rq_update_earliest_dl一起看,才能完整理解一次调度切换的全链路。配合 ftrace 动态跟踪,比静态读源码更容易吃透逻辑。

  2. 实时任务开发规范尽量固定 DL 任务的 runtime、period 参数,不要在运行中频繁动态修改;频繁修改会反复触发 put_prev_task_dl 刷新状态、重入红黑树,增加调度抖动。

  3. 性能调优建议高实时场景下,将 DL 任务绑定独占 CPU 核心,隔离普通后台任务,减少不必要的抢占与 put_prev_task_dl 调用次数,进一步压低调度时延。

  4. 内核调试排错技巧遇到实时任务卡顿、周期不准、超时丢帧时,优先用 ftrace 抓put_prev_task_dl调用日志,核对剩余时间扣减是否正常、任务是否重复入队,快速定位是调度逻辑问题还是业务负载问题。

  5. 定制化调度开发建议若要基于 Deadline 调度器做二次开发、自定义 EDF 变种,必须保留put_prev_task_dl的状态统计与时间扣减基础逻辑,否则会导致任务时间片错乱、带宽管控失效。

七、总结与应用延伸

本文系统性讲解了 Linux Deadline 调度器put_prev_task_dl的工作原理、核心职责、源码实现、实操调试与工程最佳实践。它作为调度切换的前置核心函数,承担着任务运行时长统计、剩余时间更新、调度状态保存、就绪队列维护、带宽刷新五大核心作用,是整个 Deadline 调度体系不可或缺的关键环节。

从底层原理看,它实现了硬实时任务切换时的现场保全与参数刷新,为 EDF 最早截止时间优先调度、周期带宽管控、任务精准周期运行提供了基础支撑;从工程应用看,它是工业控制、自动驾驶、5G 基站、航空航天、低延迟多媒体系统实时确定性的底层保障。

掌握put_prev_task_dl不仅能看懂 Linux 实时调度切换完整流程,还能支撑内核源码研读、课程论文写作、实时 Linux 系统裁剪、调度策略定制开发。建议读者利用本文提供的内核源码、测试代码、ftrace 调试命令,自行编译内核复现实验,修改函数内部逻辑观察调度行为变化,真正做到从理论源码到工程实战完全吃透。

http://www.jsqmd.com/news/795176/

相关文章:

  • 终极Zotero Style插件:三步打造你的智能文献管理神器
  • [理论篇-14]大模型评估与可观测性——如何知道你的 AI 到底行不行
  • AI写专著解决方案:AI专著写作工具,高效产出20万字专业专著!
  • 添加公众号附件链接的工具软件(政企云文档小程序)终身免费使用. - 政企云文档
  • Excel智能革命:用自然语言对话实现数据处理自动化
  • 太原高端水漆定制认准客来福 十年三千户业主口碑之选 - 速递信息
  • NI PXI-5922数字化仪:高精度动态信号采集技术解析
  • 2026企业级CRM综合实力榜单:5大标杆产品驱动行业数字化升级 - Blue_dou
  • 深岩银河存档编辑器终极指南:快速掌握DRG游戏存档修改技巧 [特殊字符]
  • 模拟文件打开写入关闭的过程
  • 2026年中山GEO优化服务商推荐:五家实力机构综合选型分析参考 - 产业观察网
  • 银泰百货卡回收技巧分享:常见回收问题解答! - 团团收购物卡回收
  • 免费LLM API集成实战:从选型到构建高可用AI服务
  • 华为光猫配置解密工具终极技术指南:深度解析AES加密与XML/CFG文件处理
  • 2026年中山五金配件定制厂家怎么选?工程装修采购避坑指南与靠谱供应商对标 - 优质企业观察收录
  • 百度网盘秒传技术终极指南:打破文件分享的时间限制
  • 如何高效使用Equalizer APO:从音频优化到专业级声学校准的完整指南
  • 打造你的专属桌面伙伴:DyberPet开源桌面宠物框架完全指南
  • 终极指南:3分钟实现GitHub下载速度50倍提升
  • Fast-GitHub:GitHub访问加速的技术解决方案与实现原理
  • 终极音频解密指南:3分钟解锁QQ音乐加密格式
  • 2026年广州白蚁防治公司哪家好?越秀区/天河区/荔湾区/海珠区/白云区/番禺区各区专业上门灭白蚁推荐 - 品牌推荐大师
  • 大语言模型评估指南:从ChatGPT评测看LLM能力边界与挑战
  • OpenAI 工程师翁家翌实验:AI 可“自主改代码”变强,Heuristic Learning 产业应用前景几何?
  • 3步掌握B站视频下载神器:解锁4K大会员画质的终极方案
  • 告别人力搬运数据,采用TurboEx邮件数据摆渡系统 - 拓波TurboEx邮件系统
  • Anthropic、OpenClaw让AI“做梦”“记忆”,SubQ模型拓展上下文,模糊人机边界!
  • 《求教:用阿里云处理Ozon图片的具体参数怎么设置,搜索匹配度最高?》
  • OpenAI做了一次豪赌:不给任何指令,让模型自己学会所有任务
  • 2026 江苏淮安彩钢瓦金属屋面外墙防水补漏防腐翻新公司哪家好?TOP5 权威推荐 + 避坑指南 - 速递信息