Linux内核thermal framework架构解析:从温度感知到功耗策略引擎
1. 项目概述:为什么你得花时间搞懂 thermal framework 的通用架构
在嵌入式设备、笔记本、服务器甚至手机 SoC 的 Linux 内核开发中,thermal framework 不是“可选项”,而是“生死线”。我做过三款 ARM64 工业边缘网关的内核定制,其中两款在高温车间实测时连续重启——不是因为驱动崩溃,也不是因为内存泄漏,而是 thermal governor 在 78℃ 触发了紧急关机。当时 debug 日志里只有一行:thermal thermal_zone0: critical temperature reached(95 C), shutting down。没人想到,问题根源不在散热片厚度,而在于 thermal zone 的 trip point 配置顺序写反了,导致 passive cooling 没来得及介入,系统就跳过了 throttling 直接 shutdown。这让我彻底意识到:thermal framework 不是“温度监控模块”,它是一套带策略调度、状态机驱动、跨子系统协同的功耗治理中枢。它和 cpufreq、devfreq、powercap、idle state 紧密咬合,共同构成内核功耗子系统的“神经反射弧”。你调一个 CPU 频率,thermal 可能立刻干预;你启一个 GPU 渲染任务,thermal 可能悄悄降频显存控制器;你插上 USB-C 外接屏,thermal zone 甚至会动态加载新的 cooling device。这种耦合不是松散的事件通知,而是通过 struct thermal_zone_device 和 struct thermal_cooling_device 的双向引用、workqueue 延迟执行、notifier chain 实时广播构建的强一致性模型。所以,本文不讲“怎么读 /sys/class/thermal/ 下的温度值”,也不堆砌thermal_zone_device_register()函数原型。我们要拆的是它的骨架:zone 怎么注册、trip 怎么分级、cooling 怎么绑定、governor 怎么决策、state 怎么迁移。这些设计背后,藏着 Linus 早年在邮件列表里反复强调的一句话:“Thermal management must bepredictable, not just reactive.” —— 可预测性,才是 thermal framework 架构设计的第一性原理。如果你正在做国产化 ARM 平台适配、车规级 SoC 驱动移植、或准备 Linux 内核面试中“功耗管理”这一题,那么这套通用架构,就是你绕不开的底层契约。
2. thermal framework 整体设计与思路拆解:从“温度传感器”到“功耗策略引擎”的跃迁
thermal framework 的设计哲学,本质上是一次对传统“温度告警”思维的彻底颠覆。早期嵌入式系统常用 GPIO 中断接热敏电阻,温度超限就拉低 RESET 引脚——这是纯硬件的“熔断机制”。Linux 内核没有照搬这条路,而是把 thermal 抽象成一个可编程、可扩展、可策略化的软件子系统。它的核心设计目标有三个:分层解耦、策略可插拔、状态可追溯。这三个目标直接决定了整个架构的四层结构:Device Driver 层 → Thermal Core 层 → Governor 层 → Cooling Device 层。我们逐层看它为什么这么设计。
首先,Device Driver 层负责“感知真实世界”。但 thermal framework 不要求传感器驱动自己实现温度计算逻辑。比如一个 I2C 接口的 MAX6642 温度芯片,驱动只需实现read_temp()回调,返回原始 ADC 值;而真正的摄氏度换算、校准偏移、滤波算法,全部由 thermal core 统一接管。这样做的好处是:同一套 thermal core 代码,既能跑在 x86 服务器(读取 ACPI _TMP 方法),也能跑在 ARM64 芯片(读取 SoC 内部 thermal sensor 寄存器),还能跑在 RISC-V 开发板(读取 GPIO 模拟电压)。驱动开发者不用关心“温度单位”,内核维护者不用为每种传感器写重复的滤波代码。这种解耦,让 thermal framework 具备了极强的硬件无关性。
其次,Thermal Core 层是整个系统的“中央调度室”。它定义了struct thermal_zone_device这个核心数据结构,里面不仅存着当前温度、trip point 数组、cooling device 列表,还藏着一个关键字段:struct thermal_governor *governor。注意,这里不是指针数组,而是一个单指针。这意味着:一个 thermal zone 在任意时刻,只能运行一种 governor 策略。这个设计看似限制,实则是为了保证决策唯一性。试想,如果 passive governor 和 bang-bang governor 同时生效,一个要逐步降频,一个要全速开关,CPU 频率就会在 100MHz 和 2GHz 之间疯狂抖动,系统稳定性直接归零。所以 thermal core 强制单 governor,所有策略冲突都在这一层被消解。
第三,Governor 层是“大脑”。目前内核主线支持四种 governor:step_wise(阶梯式)、bang_bang(开关式)、user_space(用户态接管)、power_allocator(功率分配式)。它们的区别不在算法复杂度,而在响应粒度和控制自由度。step_wise每次只调整一个 step(如降低一级频率),适合需要平滑过渡的场景;bang_bang只有 on/off 两种状态,响应最快,适合风扇控制;power_allocator则引入了 PID 控制思想,根据温度误差积分动态分配 cooling device 的 power budget,这是为高性能计算平台准备的精密调控方案。选择哪个 governor,不是看“谁更高级”,而是看你的硬件响应特性:风扇启动延迟大?选bang_bang;CPU 频率切换耗时长?选step_wise;需要精确控温到 ±0.5℃?那power_allocator才是正解。
最后,Cooling Device 层是“手脚”。它把抽象的“降温指令”翻译成具体的硬件动作。struct thermal_cooling_device封装了set_cur_state()回调,这个回调里可以写任何东西:调用 cpufreq_set_policy() 改变 CPU 频率上限、向 PWM 控制器写占空比寄存器、给 GPIO 发高低电平控制风扇、甚至调用 devfreq_set_target() 降频 GPU。关键点在于:cooling device 是完全独立注册的,和 thermal zone 解耦。一个 cooling device 可以被多个 zone 共享(比如同一个 CPU cooling device 同时被 cpu_thermal_zone 和 gpu_thermal_zone 绑定),一个 zone 也可以绑定多个 cooling device(比如同时绑定 cpu_cooling 和 fan_cooling)。这种多对多关系,靠thermal_zone_bind_cooling_device()函数里的struct thermal_bind_params结构体维护,里面定义了 binding priority、weight、k_po、k_pu 等参数——这些参数决定了当多个 cooling device 同时被请求时,谁先动作、谁动作幅度大。这就是 thermal framework 的“策略可插拔”本质:你换一个 governor,整个 zone 的行为逻辑就变了;你加一个 cooling device,zone 的调控手段就多了;你改一个 trip point,zone 的响应阈值就移了。所有变化都不需要动 core 层代码,只要遵循接口契约即可。
提示:很多初学者误以为 thermal framework 的核心是“读温度”,其实恰恰相反。它的核心是“做决策”。温度读取只是触发条件,真正有价值的是:当温度从 70℃ 升到 75℃ 时,系统该选择降频 CPU 还是提速风扇?该降一级还是降两级?该立即执行还是延迟 2 秒再执行?这些决策逻辑,全部封装在 governor 和 binding 参数里。理解这一点,才能跳出“查温度文件”的初级阶段。
3. 核心细节解析与实操要点:zone 注册、trip 分级、binding 权重与 governor 切换
要真正掌控 thermal framework,光知道分层不够,必须抠进每个关键结构体的字段含义、函数调用的时机约束、以及配置参数背后的物理意义。下面我结合实际调试案例,把最常踩坑的四个核心环节掰开揉碎讲清楚。
3.1 thermal zone 注册:不是“注册完就完事”,而是“状态机初始化”
注册一个 thermal zone,标准流程是调用thermal_zone_device_register()。但很多人忽略了一个致命细节:这个函数的第 5 个参数const struct thermal_zone_device_ops *ops,里面get_temp回调的执行上下文,决定了你能不能在里面 sleep。官方文档明确写着:“get_temp()must not sleep.” 为什么?因为 thermal core 在周期性轮询(默认 2 秒一次)或中断触发时,会直接在 softirq 上下文中调用它。如果你在get_temp()里用了msleep(1)或mutex_lock(),整个系统 softirq 就卡死了。我曾经在一个 Allwinner H6 平台上遇到过:驱动里为了等 ADC 转换完成,写了wait_event_timeout(),结果系统每隔 2 秒就卡顿 50ms,网络 ping 延迟飙升到 300ms。解决方法只有两个:要么改用 polling 方式(循环读寄存器直到 ready bit 置位),要么把 ADC 读取放到 workqueue 里异步执行,get_temp()只返回上次缓存的值。后者更安全,但需要你自己维护一个struct delayed_work和保护缓存的 spinlock。
另一个易错点是tzd->passive_delay和tzd->polling_delay字段。前者是 passive cooling(如降频)触发后的轮询间隔,后者是未触发任何 trip 时的常规轮询间隔。注意:passive_delay必须小于polling_delay,否则 passive cooling 一旦启动,系统反而会降低轮询频率,导致温度失控。实测中,polling_delay=2000(2 秒),passive_delay=500(0.5 秒)是工业设备的黄金组合——既保证常规监控不耗电,又确保降频后能快速响应温度变化。
3.2 trip point 分级:critical 不是“最高级”,而是“不可逆临界点”
trip point 是 thermal zone 的“决策触发器”,但它绝不是简单的“温度阈值数组”。内核定义了五种 type:THERMAL_TRIP_ACTIVE、THERMAL_TRIP_PASSIVE、THERMAL_TRIP_HOT、THERMAL_TRIP_CRITICAL、THERMAL_TRIP_INVALID。其中CRITICAL最具迷惑性。很多人以为它是“最高温度档”,其实不然。CRITICAL的语义是:“此温度下,硬件已处于不可逆损伤风险,必须立即执行硬性保护,且禁止任何用户空间干预”。这意味着:一旦CRITICALtrip 被触发,thermal core 会绕过所有 governor,直接调用thermal_zone_device_disable(),然后执行emergency_shutdown()。此时/sys/class/thermal/thermal_zone0/mode会被强制设为disabled,连 root 用户都改不回来。所以CRITICAL的温度值,必须严格等于芯片 datasheet 里标注的 “Maximum Junction Temperature”,比如 Rockchip RK3399 是 125℃,不能写成 120℃(太保守,浪费性能),也不能写成 130℃(太激进,烧芯片)。而PASSIVE才是真正的“最高调控档”,它触发后会启动 governor 的主动降温策略,此时用户仍可通过 sysfs 修改cur_state或mode。
trip point 的排序也至关重要。内核要求:所有 trip point 必须按温度升序排列,且CRITICAL必须是最后一个。如果你把CRITICAL放在第 0 位,PASSIVE放在第 1 位,当温度升到 95℃(CRITICAL阈值)时,thermal core 会先遍历 trip[0],发现匹配,立刻 shutdown,根本不会检查后面的PASSIVE。这个 bug 我在飞腾 D2000 平台移植时遇到过,原因是 dts 文件里 trip 节点顺序写反了,调试花了整整两天。
3.3 cooling device binding:weight 不是“权重”,而是“调控优先级系数”
把 cooling device 绑定到 thermal zone,用的是thermal_zone_bind_cooling_device()。它的第 4 个参数unsigned long weight,文档里叫“weight”,但实际作用远不止“权重”。这个值参与一个关键计算:cdev->state * weight / total_weight。其中cdev->state是当前 cooling device 的状态(如 CPU frequency level 0~10),total_weight是该 zone 下所有绑定 cdev 的 weight 总和。最终结果,决定了这个 cdev 在 governor 决策中的“贡献比例”。举个例子:zone 绑定了 cpu_cooling(weight=80)和 fan_cooling(weight=20),当前 governor 计算出需要 60% 的 cooling capacity。那么 cpu_cooling 会执行state = 6(10 级中的第 6 级),fan_cooling 会执行state = 1(5 级中的第 1 级)。可见,weight 直接决定了不同 cooling device 的“调控话语权”。
更隐蔽的坑在k_po和k_pu参数。它们是struct thermal_bind_params里的浮点系数,用于 passive governor 的 PID 计算。k_po是 proportional gain for passive cooling,k_pu是 for active cooling。默认值都是 1.0,但实测中,对于响应慢的风扇(启动延迟 > 500ms),k_po设为 0.3 更稳;对于响应快的 CPU 频率调节(切换 < 10ms),k_pu设为 2.0 更灵敏。这些参数没有标准值,必须根据硬件实测调整。我的做法是:先用示波器抓 PWM 波形,测出风扇从 off 到 full speed 的上升时间,再用echo 1 > /sys/class/thermal/thermal_zone0/emul_temp模拟升温,观察温度曲线斜率,反推最优 k 值。
3.4 governor 切换:不是“echo 就生效”,而是“状态机重置”
切换 governor,看似简单:echo "step_wise" > /sys/class/thermal/thermal_zone0/governor。但背后发生的事,远比 echo 复杂。thermal core 会先调用旧 governor 的.exit()回调(如果有),然后调用新 governor 的.init()回调,最后清空该 zone 的所有 cooling device 的 current state 缓存。这意味着:切换 governor 的瞬间,所有 cooling device 会回到初始 state(通常是 0)。如果你在step_wise下 CPU 正在 1.2GHz 运行,切到bang_bang的瞬间,CPU 频率会立刻跳回最低档(如 400MHz),造成业务中断。这不是 bug,而是设计使然——因为不同 governor 对 state 的语义定义不同:step_wise的 state=5 表示“降 5 级”,bang_bang的 state=1 表示“全速运行”,两者无法直接映射。
因此,生产环境严禁 runtime 切换 governor。正确做法是在 dts 或 kernel cmdline 里静态指定:thermal-gov = "step_wise";。如果真需要动态切换(比如调试),必须配合 cooling device 的 state 锁定。方法是:先echo 0 > /sys/class/thermal/thermal_zone0/policy(禁用自动调控),再echo "new_gov" > /sys/class/thermal/thermal_zone0/governor,最后echo 1 > /sys/class/thermal/thermal_zone0/policy。这样能保证切换过程中 cooling device state 不被重置。
注意:
/sys/class/thermal/thermal_zone0/mode文件有两个值:enabled和disabled。很多人以为disabled是关闭 thermal 功能,其实它只是禁用自动调控,所有 trip point 依然有效,CRITICAL触发时照样 shutdown。真正关闭 thermal zone,必须调用thermal_zone_device_unregister(),这通常只在驱动卸载时做。
4. 实操过程与核心环节实现:从 DTS 描述到内核启动的完整链路
现在我们把前面所有理论,串成一条可落地的实操链路。以一款基于 NXP i.MX8MQ 的工业网关为例,它的 SoC 内置 thermal sensor,需要绑定 CPU cooling device,并启用 passive cooling。整个过程分为四步:DTS 描述 → Kernel 配置 → Driver 实现 → 启动验证。每一步我都给出可直接复制的代码片段和关键注释。
4.1 DTS 描述:用 device tree 定义硬件拓扑关系
DTS 是 thermal framework 的“宪法”,它定义了硬件资源如何被内核识别和组织。i.MX8MQ 的 thermal node 必须放在soc@0节点下,并显式引用 cooling device:
&soc { thermal-zones { cpu_thermal: cpu-thermal { /* thermal zone 名称,将出现在 /sys/class/thermal/ 下 */ compatible = "thermal-zones"; /* trip point 定义:按温度升序排列 */ trips { trip0 { temperature = <60000>; /* 60℃ */ hysteresis = <2000>; /* 滞后 2℃,防止抖动 */ type = "active"; }; trip1 { temperature = <75000>; /* 75℃ */ hysteresis = <2000>; type = "passive"; }; trip2 { temperature = <95000>; /* 95℃ */ hysteresis = <0>; type = "critical"; }; }; /* cooling device 绑定:指定绑定对象和参数 */ cooling-maps { map0 { /* cooling device 名称,必须和 cpufreq driver 注册的一致 */ cooling-device = <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; /* weight=100,表示 CPU cooling 是主要调控手段 */ weight = <100>; /* k_po=1.0,用于 passive governor 的 PID 计算 */ k_po = <1000>; /* 内核内部乘以 1000 存储 */ }; }; }; }; };关键点解析:cooling-device = <&cpu0 ...>中的&cpu0必须和 cpufreq driver 的thermal_cooling_device_register("cpu0")名称完全一致,大小写敏感;THERMAL_NO_LIMIT表示不限制 cooling device 的 min/max state,实际范围由 cpufreq policy 决定;k_po = <1000>是整数存储,内核会除以 1000 得到 1.0。
4.2 Kernel 配置:最小化裁剪,避免隐式依赖
thermal framework 依赖多个子系统,配置不当会导致编译失败或运行时 panic。以下是必须开启的 config 项(基于 5.10 LTS):
CONFIG_THERMAL=y CONFIG_THERMAL_OF=y # 必须,否则 DTS 描述无效 CONFIG_THERMAL_GOV_STEP_WISE=y # 选择你要的 governor CONFIG_THERMAL_GOV_BANG_BANG=y CONFIG_THERMAL_GOV_USER_SPACE=y CONFIG_THERMAL_EMULATION=y # 调试必备,允许 echo 温度模拟 CONFIG_CPU_THERMAL=y # CPU cooling device 支持 CONFIG_DEVFREQ_THERMAL=y # 如果要用 GPU/DDR cooling CONFIG_ARM_SCPI_PROTOCOL=y # i.MX8MQ 需要 SCPI 协议读取 sensor特别注意CONFIG_THERMAL_OF:如果关闭,内核会忽略 DTS 里的 thermal-zones 节点,即使你写了 trip point,thermal_zone_device_register()也不会被调用。这个 config 在 menuconfig 里藏得很深,路径是:Device Drivers -> Thermal drivers -> <*> Generic thermal sysfs driver,下面有个子选项[*] OF support。
4.3 Driver 实现:sensor driver 与 cooling device driver 的协同
thermal sensor driver 的核心是实现struct thermal_zone_of_sensor_ops。以 i.MX8MQ 为例,它的 sensor 通过 SCPI 协议通信,driver 代码骨架如下:
static int imx8mq_thermal_get_temp(void *data, int *temp) { struct imx8mq_thermal *tz = data; u32 val; int ret; /* SCPI 协议读取,非阻塞 */ ret = scpi_get_sensor_value(tz->scpi_id, &val); if (ret) return ret; /* 硬件返回的是 millidegree,转换为 microdegree */ *temp = val * 1000; // 内核 thermal core 要求单位是 millidegree return 0; } static const struct thermal_zone_of_sensor_ops imx8mq_sensor_ops = { .get_temp = imx8mq_thermal_get_temp, }; static int imx8mq_thermal_probe(struct platform_device *pdev) { struct imx8mq_thermal *tz; struct device_node *np = pdev->dev.of_node; int ret; tz = devm_kzalloc(&pdev->dev, sizeof(*tz), GFP_KERNEL); if (!tz) return -ENOMEM; /* 获取 SCPI sensor ID */ ret = of_property_read_u32(np, "arm,scpi-sensor-id", &tz->scpi_id); if (ret) return ret; /* 注册 thermal zone,传入 ops 结构体 */ tz->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev, np, tz, &imx8mq_sensor_ops); if (IS_ERR(tz->tzd)) { ret = PTR_ERR(tz->tzd); dev_err(&pdev->dev, "failed to register thermal zone: %d\n", ret); return ret; } platform_set_drvdata(pdev, tz); return 0; }这里的关键是devm_thermal_zone_of_sensor_register():它会自动解析 DTS 里的 trips 和 cooling-maps,并完成 binding。你不需要手动调用thermal_zone_bind_cooling_device()。
而 CPU cooling device driver(即 cpufreq driver)必须在 thermal zone 注册前就准备好。标准做法是在cpufreq_driver_init()里注册:
static struct thermal_cooling_device *cpu_cooling_device; static int cpu_cooling_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) { *state = cpufreq_frequency_table_count(); // 返回频率档位总数 return 0; } static int cpu_cooling_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) { /* state 是 0~N,对应 cpufreq table 的索引 */ return cpufreq_driver_target(cpufreq_cpu_get(0), freq_table[state].frequency, CPUFREQ_RELATION_L); } static const struct thermal_cooling_device_ops cpu_cooling_ops = { .get_max_state = cpu_cooling_get_max_state, .set_cur_state = cpu_cooling_set_cur_state, }; static int __init cpu_cooling_init(void) { cpu_cooling_device = thermal_cooling_device_register("cpu0", NULL, NULL, &cpu_cooling_ops); if (IS_ERR(cpu_cooling_device)) return PTR_ERR(cpu_cooling_device); return 0; }注意thermal_cooling_device_register("cpu0")的名称,必须和 DTS 里的&cpu0完全一致。
4.4 启动验证:用 sysfs 和 tracepoint 看清每一帧决策
内核启动后,验证 thermal framework 是否正常工作,不能只看cat /sys/class/thermal/thermal_zone0/temp。必须用三层验证法:
第一层:基础状态检查
# 查看 zone 状态 cat /sys/class/thermal/thermal_zone0/type # 应输出 "cpu-thermal" cat /sys/class/thermal/thermal_zone0/mode # 应输出 "enabled" cat /sys/class/thermal/thermal_zone0/governor # 应输出 "step_wise" cat /sys/class/thermal/thermal_zone0/trips # 应列出 3 个 trip cat /sys/class/thermal/thermal_zone0/cdev0 # 应输出 "cpu0" # 查看 cooling device 状态 cat /sys/class/thermal/cooling_device0/type # 应输出 "Processor" cat /sys/class/thermal/cooling_device0/max_state # 应输出频率档位数,如 "10" cat /sys/class/thermal/cooling_device0/cur_state # 当前 CPU 频率档位第二层:模拟升温,观察响应
# 启用 emulation(需 CONFIG_THERMAL_EMULATION=y) echo 1 > /sys/class/thermal/thermal_zone0/emul_temp # 设置模拟温度为 80℃(单位 millidegree) echo 80000 > /sys/class/thermal/thermal_zone0/emul_temp # 实时监控温度和状态变化 watch -n 0.5 'cat /sys/class/thermal/thermal_zone0/temp /sys/class/thermal/cooling_device0/cur_state'正常现象:温度稳定在 80000 后,cur_state应逐步升高(如从 0→3→5),CPU 频率下降,系统负载无明显卡顿。
第三层:tracepoint 深度分析
# 开启 thermal tracepoint echo 1 > /sys/kernel/debug/tracing/events/thermal/thermal_temperature/enable echo 1 > /sys/kernel/debug/tracing/events/thermal/thermal_throttle/enable echo 1 > /sys/kernel/debug/tracing/tracing_on # 触发一次被动降温 echo 76000 > /sys/class/thermal/thermal_zone0/emul_temp # 查看 trace cat /sys/kernel/debug/tracing/trace_pipe你会看到类似这样的输出:
thermal-123 [001] d... 12345.678901: thermal_temperature: thermal_zone0: temp=76000 thermal-123 [001] d... 12345.678902: thermal_throttle: thermal_zone0: trip=1 (passive) triggered thermal-123 [001] d... 12345.678903: thermal_cooling: cpu0: set_cur_state=4这证明:温度读取 → trip 判断 → governor 决策 → cooling device 执行,整条链路畅通无阻。
实操心得:我在调试 i.MX8MQ 时发现,
emul_temp只影响get_temp()返回值,但不会触发硬件 sensor 的中断。所以如果你的 sensor driver 里用了中断模式(如request_threaded_irq()),必须同时注释掉中断处理部分,否则emul_temp会和硬件读数竞争,导致温度值乱跳。这是文档里绝不会写的坑。
5. 常见问题与排查技巧实录:从“温度不更新”到“governor 不生效”的硬核排障
在真实项目中,thermal framework 的问题往往不是“功能不存在”,而是“功能在错误的时间、以错误的方式被调用”。下面是我整理的 7 个高频问题,每个都附带 root cause 分析、复现步骤、和一行命令定位法。这些问题,90% 的工程师在第一次移植 thermal 时都会撞上。
5.1 问题:/sys/class/thermal/thermal_zone0/temp始终为 0
现象:DTS 写了,driver 也 probe 成功,但cat temp永远返回 0。
Root Cause:get_temp()回调返回了错误的单位。thermal core 要求单位是millidegree Celsius(千分之一摄氏度),而很多 sensor driver 直接返回 raw ADC 值或 degree。
复现步骤:
- 在
get_temp()里加pr_info("raw=%d\n", val); cat /sys/class/thermal/thermal_zone0/temp,发现日志里raw=256,但 temp=0
定位命令:
# 查看 thermal core 的 debug log dmesg | grep -i "thermal.*get_temp" # 如果看到 "get_temp returned -22",说明返回值非法(-22 是 EINVAL)解决方案:检查get_temp()的返回值。必须满足:*temp >= -273150(-273.15℃)且*temp <= 175000(175℃),单位是 millidegree。例如,硬件返回 25℃,必须写*temp = 25000;,而不是*temp = 25;。
5.2 问题:trip point 定义了,但cat trips为空
现象:DTS 里明明写了trips { ... },但cat /sys/class/thermal/thermal_zone0/trips输出 nothing。
Root Cause:DTS 节点名不匹配。thermal core 要求 thermal zone 的 node name 必须是xxx-thermal(带-thermal后缀),否则of_thermal.c里的thermal_of_populate_trip_points()函数会跳过解析。
复现步骤:
- DTS 写
cpu_thermal: cpu { ... };(缺少-thermal) - 启动后
ls /sys/class/thermal/看到thermal_zone0,但cat trips为空
定位命令:
# 查看内核启动时是否解析了 trips dmesg | grep -i "thermal.*trip" # 正常应有 "Parsed 3 trip points" # 如果没有,检查 dts node name解决方案:把cpu { ... };改成cpu-thermal { ... };,重新编译 dtb。
5.3 问题:echo "passive" > /sys/class/thermal/thermal_zone0/governor失败,返回 "Invalid argument"
现象:cat /sys/class/thermal/thermal_zone0/available_governors显示passive,但 echo 就报错。
Root Cause:passivegovernor 在 5.4+ 内核中已被移除,替换为step_wise。passive是旧名,新内核只认step_wise。
复现步骤:
- 在 5.10 内核上
echo "passive" > /sys/class/thermal/thermal_zone0/governor - 返回
-EINVAL
定位命令:
# 查看内核版本和 config uname -r zcat /proc/config.gz | grep THERMAL_GOV # 如果看到 CONFIG_THERMAL_GOV_STEP_WISE=y,但没看到 PASSIVE,则 confirm解决方案:echo "step_wise" > /sys/class/thermal/thermal_zone0/governor。step_wise就是passive的继任者,行为完全一致。
5.4 问题:cooling device 绑定了,但cat /sys/class/thermal/cooling_device0/cur_state始终为 0
现象:cat cdev0显示cpu0,但cur_state不随温度变化。
Root Cause:cooling device 的set_cur_state()回调里,cpufreq_driver_target()调用失败,但 driver 没有返回 error,导致 thermal core 认为设置成功。
复现步骤:
- 在
set_cur_state()里加pr_err("set state %ld\n", state); - 升高模拟温度,发现日志有输出,但
cur_state不变
定位命令:
# 查看 cpufreq 是否正常工作 cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq # 如果始终为 0,说明 cpufreq driver 未初始化 # 检查 cpufreq driver 是否 probe 成功 dmesg | grep -i cpufreq解决方案:确保 cpufreq driver 在 thermal zone probe 前已加载。在 DTS 中,把cpufreq节点放在thermal-zones节点之前,或在 thermal driver 的probe()里加msleep(100)等待 cpufreq ready。
5.5 问题:CRITICALtrip 触发后,系统 shutdown,但/var/log/kern.log没有 shutdown 记录
现象:设备突然断电,串口 log 在critical temperature reached后戛然而止。
Root Cause:CRITICAL触发时,内核调用emergency_shutdown(),该函数会直接调用machine_power_off(),不经过 userspace 的 logger。所以 kern.log 截断是正常的。
复现步骤:
echo 96000 > /sys/class/thermal/thermal_zone0/emul_temp- 系统立即断电,log 停在
shutting down
定位命令:
# 在触发前,打开 thermal trace echo 1 > /sys/kernel/debug/tracing/events/thermal/thermal_critical/enable # 触发后,cat /sys/kernel/debug/tracing/trace_pipe,会看到 critical event解决方案:这不是 bug,是设计。生产环境必须确保CRITICAL温度留有足够余量(≥5℃),并在PASSIVE阶段就介入调控,绝不能依赖CRITICAL。
5.6 问题:step_wisegovernor 下,cur_state跳变剧烈(0→5→0→5)
现象:温度缓慢上升,但cur_state在两级间疯狂切换。
Root Cause:hysteresis(滞后值)设置过小。step_wise的状态切换逻辑是:温度 > trip[i].temp → 升一级;温度 < trip[i].temp - hysteresis