1. 这不是“温度监控”那么简单:thermal framework 是内核里最被低估的功耗协同中枢
你翻过 Linux 内核文档,可能见过drivers/thermal/目录下密密麻麻的.c文件;你在嵌入式板子上跑sensors命令,看到 CPU 温度跳动,以为这就是 thermal 的全部;甚至有些面试官问“thermal 怎么用”,候选人答“调sysfs接口改 trip point”,就以为通关了。但我要说:这就像只盯着汽车仪表盘上的水温表,却完全不知道冷却液泵、节温器、散热风扇、ECU 控制逻辑之间是怎么咬合运转的——thermal framework 不是温度传感器的搬运工,它是整个内核功耗管理的神经中枢,是 CPU 频率调节器(cpufreq)、GPU 动态调压模块(regulator)、内存带宽控制器(devfreq)和系统级电源策略(powercap)之间唯一能坐下来开协调会的实体。
我从 2013 年开始在 ARM SoC 厂商做 BSP 支持,亲手把 thermal 框架移植进四代不同架构的芯片平台(从 Cortex-A9 到 Cortex-X3),调试过上百个 thermal zone 的绑定逻辑,踩过 trip point 触发延迟导致 CPU 热关机、thermal governor 切换卡死、被动 cooling device 无法响应等所有典型坑。今天这篇,不讲泛泛而谈的“框架图”,也不堆砌代码行号,而是带你一层层剥开 thermal framework 的通用骨架:它为什么必须设计成“zone + trip + cooling device + governor”四层解耦?为什么thermal_zone_device_ops里只有 5 个回调函数却撑起了整个热管理生态?为什么thermal_cooling_device_register()注册一个 cooling device 后,内核就能自动把它和任意 zone 关联起来?这些设计背后,全是十多年一线工程师在真实芯片、真实散热模组、真实功耗墙约束下反复博弈出来的工程智慧。
如果你正在做嵌入式 Linux 产品开发,手头有 Rockchip、Allwinner、NXP i.MX 或高通骁龙平台,正为 CPU 高温降频、GPU 热 throttling、SoC 整体功耗超标发愁;或者你是内核学习者,已经啃完进程调度、内存管理,想真正理解“功耗”这个维度如何融入内核主干;又或者你刚在dmesg里看到thermal thermal_zone0: critical temperature reached, shutting down却找不到根因——那么这篇就是为你写的。它不教你“怎么查温度”,而是告诉你:当温度成为系统瓶颈时,内核到底在后台做了哪些决策、谁在执行、依据什么规则、哪里可以干预。全文所有结论,都来自我实测过的 6.1~6.6 内核主线代码,以及在 RK3588、i.MX8MP、STM32MP157 等平台上反复验证的配置方案。
2. 架构设计的底层逻辑:为什么 thermal 必须是“可插拔”的四层模型
2.1 四层解耦不是为了炫技,而是应对硬件碎片化的生存法则
thermal framework 的核心抽象是四层结构:Thermal Zone(热区)→ Trip Point(触发点)→ Cooling Device(冷却设备)→ Governor(调控策略)。初看像教科书式的分层,但它的每一层都直指一个残酷现实:Linux 要跑在从树莓派到服务器、从智能手表到自动驾驶域控制器的千种硬件上,而这些硬件的热特性天差地别。
Thermal Zone:不是物理概念,而是软件定义的“热感知单元”。一个 zone 可以是一个 CPU cluster(如 big.LITTLE 中的 big core group),也可以是 GPU+DDR controller 组合,甚至是一整块 PCB 板。关键在于,它封装了“在哪里测温、怎么读数、谁负责上报”这三件事。比如在 RK3588 上,
thermal_zone0对应 CPU cluster,其tz->ops->get_temp回调最终调用的是rockchip_thermal_get_temp(),而该函数内部要通过 I2C 读取 ADC 值再查表转换;但在 STM32MP157 上,同一套 thermal zone 框架,get_temp却直接读取 SOC 内部的TSR寄存器。如果框架不抽象出 zone 层,每个 SoC 都得重写一整套热管理逻辑,内核维护成本将指数级爆炸。Trip Point:这是 thermal 的“决策开关”。它不关心温度值本身,只定义“当温度达到 X℃ 时,该触发什么动作”。注意,trip point 分为
THERMAL_TRIP_ACTIVE(主动降温)、THERMAL_TRIP_PASSIVE(被动限频)、THERMAL_TRIP_CRITICAL(紧急关机)三类。为什么必须区分?因为硬件响应能力不同:CPU 频率可毫秒级调整(passive),风扇转速需几百毫秒建立风压(active),而关机是最后手段(critical)。我在调试某款工业网关时发现,客户把PASSIVE和ACTIVEtrip point 设成同一温度,结果风扇还没转起来,CPU 就先被 cpufreq governor 降频到最低,系统卡死——这就是没理解 trip 类型语义的代价。Cooling Device:这是 thermal 的“执行器”。它抽象了所有能影响温度的硬件:CPU 频率调节器(
cpufreq_cooling_device)、GPU 电压调节器(regulator_cooling_device)、PWM 风扇(pwm_fan_cooling_device)、甚至 USB Type-C 接口的散热风扇(usb_cooling_device)。关键设计是:cooling device不与任何 zone 绑定,它只暴露一个统一接口cooling_device_ops,提供set_cur_state()设置当前冷却强度(0~max_state)。这样,同一个 PWM 风扇设备,既能被 CPU zone 调用,也能被 GPU zone 调用,还能被主板 zone 共享——彻底解耦硬件资源与热源逻辑。Governor:这是 thermal 的“大脑”。它监听 zone 温度变化,根据 trip point 触发条件,决定调用哪些 cooling device、设置多大强度。内核默认提供
step_wise(阶梯式)、power_allocator(功率分配式)、bang_bang(开关式)三种 governor。step_wise最常用,但它不是简单地“温度超了就加一级风扇”,而是基于历史温度趋势预测下一步动作;power_allocator则更激进,它把整个 SoC 的功耗预算(如 15W)按比例分配给各 cooling device,确保总功耗不越界——这正是现代移动 SoC 实现“性能释放最大化”的核心技术。
提示:不要试图在驱动里硬编码 trip point 温度值。正确做法是在设备树中声明:
&cpu_thermal { trips { cpu_alert0: trip@0 { temperature = <70000>; // 70℃ hysteresis = <2000>; // 滞后 2℃ type = "passive"; }; cpu_alert1: trip@1 { temperature = <85000>; hysteresis = <5000>; type = "active"; }; }; };这样,同一份内核镜像可适配不同散热设计的硬件,无需重新编译。
2.2 thermal_sys.c:整个框架的“中央调度室”
所有 thermal 逻辑的入口都在drivers/thermal/thermal_sys.c。它不处理具体温度读取或风扇控制,而是扮演“注册中心+事件分发器”的角色。当你调用thermal_zone_device_register()注册一个 zone 时,它做的三件事决定了整个框架的健壮性:
初始化 zone 的 sysfs 接口:自动创建
/sys/class/thermal/thermal_zone0/下的temp、trip_point_0_temp、mode等文件。这里有个关键细节:temp文件的读取会触发tz->ops->get_temp(),但内核会强制加锁并限制调用频率(默认 250ms 间隔),防止高频读取拖垮 ADC 或 I2C 总线。我在调试某款车机芯片时,客户用watch -n 0.1 cat temp导致 I2C bus lockup,根源就是绕过了这个保护机制。建立 cooling device 关联表:当
thermal_cooling_device_register()被调用时,thermal_sys 会遍历所有已注册的 zone,检查其设备树中是否声明了对该 cooling device 的引用(如cooling-device = <&cpu0_cooling 0 4>)。这种“松耦合关联”让 cooling device 可以动态增减——比如热插拔一个 USB 风扇,内核能自动将其纳入 thermal 管理,无需重启。启动 governor 工作队列:每个 zone 对应一个
thermal_zone_device,其governor字段指向具体策略。thermal_zone_device_enable()会启动一个 per-zone 的 workqueue,周期性(默认 2 秒)调用governor->throttle()。注意:这个周期不是固定死的,step_wisegovernor 会根据温度变化斜率动态调整采样间隔——温度飙升时缩短到 500ms,稳定时拉长到 5 秒,这是省电的关键。
2.3 为什么没有“thermal driver”?——所有驱动都是 thermal 的“插件”
这是新手最大的认知误区:以为 thermal 框架需要一个专门的“thermal driver”。实际上,thermal framework 本身不包含任何硬件驱动,它只是一个“驱动容器”。真正的温度传感器驱动(如rockchip_thermal.c、imx_thermal.c)和 cooling device 驱动(如cpufreq_cooling.c、pwm_fan.c)都是独立模块,它们通过标准接口接入 thermal 框架:
- 温度传感器驱动 → 实现
struct thermal_zone_device_ops→ 注册为thermal_zone_device - cooling device 驱动 → 实现
struct thermal_cooling_device_ops→ 注册为thermal_cooling_device - governor → 实现
struct thermal_governor→ 注册为thermal_governor
这种设计带来两个核心优势:
- 零侵入式扩展:你想支持新型温度传感器?只需实现那 5 个 ops 函数,调用
thermal_zone_device_register()即可,不用改 thermal_sys.c 一行代码。 - 跨平台复用:
cpufreq_cooling.c在 x86、ARM、RISC-V 上完全通用,因为它只调用cpufreq_update_policy()这个架构无关接口;pwm_fan.c也只需适配pwm_config()和pwm_enable(),与底层 PWM controller 无关。
我在为某国产 RISC-V SoC 移植 thermal 时,直接复用了主线cpufreq_cooling.c和step_wise.c,只写了 300 行kendryte_thermal.c驱动,三天就跑通——这就是框架解耦的价值。
3. 核心组件深度拆解:从设备树到内核调用链的完整路径
3.1 Thermal Zone 的设备树绑定:如何让内核“认出”你的热区
设备树(DTS)是 thermal 框架的“配置语言”。一个典型的 CPU thermal zone 定义如下(以 RK3588 为例):
&cpu_thermal { thermal-sensors = <&tsadc>; #thermal-sensor-cells = <1>; trips { cpu_alert0: trip@0 { temperature = <70000>; hysteresis = <2000>; type = "passive"; }; cpu_alert1: trip@1 { temperature = <85000>; hysteresis = <5000>; type = "active"; }; }; cooling-maps { map0 { trip = <&cpu_alert0>; cooling-device = <&cpu0_cooling 0 4>; }; map1 { trip = <&cpu_alert1>; cooling-device = <&fan0 0 15>; }; }; };这段 DTS 描述了三层关系:
- sensor 绑定:
thermal-sensors = <&tsadc>告诉内核,这个 zone 的温度数据来自tsadc节点(即 Rockchip 的 Thermal Sensor ADC 控制器)。#thermal-sensor-cells = <1>表示后续引用时需传一个参数(通常是 channel ID)。 - trip point 定义:两个 trip,分别对应 70℃(被动限频)和 85℃(主动风扇)。
hysteresis是滞后值,防止温度在阈值附近抖动导致频繁触发。 - cooling map 绑定:
map0表示当cpu_alert0触发时,调用cpu0_cooling设备,且目标 state 为 0~4(即 CPU 频率档位 0 到 4);map1表示cpu_alert1触发时,调用fan0设备,state 0~15(PWM 占空比 0%~100%)。
关键细节:cooling-device属性中的<&cpu0_cooling 0 4>三个参数含义是:
&cpu0_cooling:指向 cooling device 的 phandle0:min state(最低冷却强度)4:max state(最高冷却强度)
这个范围不是随意定的。对于 cpufreq cooling device,state 0 表示最低频率(如 400MHz),state 4 表示最高频率(如 2.4GHz);但 thermal 框架会反向使用:state 越大,冷却强度越小(因为频率越高越热),所以 governor 实际调用set_cur_state(4)时,会把 CPU 锁在最高频——这看似矛盾,实则是 thermal 框架的统一设计:所有 cooling device 的 state 都遵循“数值越大,冷却效果越弱”的约定,这样 governor 逻辑才能统一。
3.2 Temperature 读取的全链路:从 ADC 到 sysfs 的 7 步调用
当你执行cat /sys/class/thermal/thermal_zone0/temp时,内核经历了以下调用链(以 RK3588 为例):
sysfs层触发thermal_zone_show_temp()- → 调用
tz->ops->get_temp(tz, &temp) - → 进入
rockchip_thermal_get_temp() - → 调用
rockchip_tsadc_get_temp() - → 通过
regmap_read()读取 TSADC 寄存器TSADCCON和TSADCDAT - → 查
rockchip_thermal_table[]温度补偿表(该表由芯片厂提供,校准 ADC 非线性) - → 返回
temp(单位为 m℃,如 70000 表示 70℃)
这 7 步中,第 5 步和第 6 步最易出错:
- 寄存器读取失败:TSADC controller 可能未使能时钟或未 reset。
dmesg中若出现rockchip_thermal: failed to get temperature,首先要检查clk_tsadc和rst_tsadc是否在设备树中正确声明。 - 温度表校准偏差:同一颗芯片,不同批次的 ADC 偏差可达 ±5℃。我曾遇到客户量产板在 60℃ 时
temp读数为 65℃,根源是温度表未按实际芯片校准。解决方案:在rockchip_thermal_table[]中插入实测点,用线性插值法生成新表。
注意:
get_temp()必须是原子操作,不能 sleep。因此所有 ADC 读取必须用 polling 方式(而非中断),否则在 atomic context 中调用msleep()会导致 kernel panic。这也是为什么 thermal driver 通常不处理 ADC 初始化——那是rockchip_tsadc.c的职责。
3.3 Cooling Device 的注册与状态映射:从“设频率”到“调风扇”的统一接口
cooling device 的注册是 thermal 框架最精妙的设计之一。以cpufreq_cooling.c为例,其核心是cpufreq_power_coefficient——一个将 CPU 频率映射为“热功率贡献值”的系数表:
static const struct cpufreq_power_coefficient coeffs[] = { { .freq = 400000, .power = 100 }, // 400MHz -> 100mW { .freq = 800000, .power = 300 }, { .freq = 1200000, .power = 600 }, { .freq = 1600000, .power = 1000 }, { .freq = 2400000, .power = 1800 }, // 2.4GHz -> 1800mW };当 governor 调用cdev->ops->set_cur_state(cdev, state)时:
- 若
state=0,则查找coeffs[0],调用cpufreq_update_policy()将 CPU 锁在 400MHz - 若
state=4,则锁在 2.4GHz
但注意:state值本身不直接对应频率,而是通过cpufreq_cooling_get_max_state()获取最大 state 数(此处为 5),再用state作为索引查表。这种设计允许你在不改 governor 的前提下,通过修改coeffs[]表来调整功耗-频率关系——比如为低功耗场景增加 200MHz 档位,只需扩表,无需动 thermal 核心。
对于 PWM 风扇,pwm_fan.c的set_cur_state()更简单:state直接映射为 PWM 占空比百分比。但有一个隐藏陷阱:pwm_config()的 period 参数必须与硬件匹配。某次我调试一款 12V 风扇,period=1000000ns(1ms)时风扇嗡嗡响,改为period=50000ns(50us)后静音——这是因为风扇电机的 LC 时间常数要求 PWM 频率 >20kHz 才能消除人耳可闻噪声。
3.4 Governor 的决策逻辑:step_wise 如何避免“温度震荡”
step_wise是最常用的 governor,但它绝非“温度超了就升一级,降了就降一级”的简单逻辑。其核心算法在step_wise_throttle()中,包含三个关键阶段:
Trip 状态检测:遍历所有 trip point,找出当前激活的最高优先级 trip(
CRITICAL > ACTIVE > PASSIVE)。例如温度 78℃ 时,cpu_alert0(70℃)和cpu_alert1(85℃)都满足,但cpu_alert0优先级更高,故选择它。Target State 计算:不是直接设
state+1,而是计算“目标冷却强度”:target_state = (tz->temperature - trip->temperature) * (cdev->max_state - cdev->min_state) / (trip->hysteresis);这个公式意味着:温度离 trip 阈值越近,target_state 越大(冷却越强);离得越远,target_state 越小。比如
hysteresis=2000(2℃),当前温度 71.5℃,则target_state ≈ (1500/2000)*4 = 3,即设为 state 3。平滑过渡:引入
last_target_state缓存上一次目标值,每次只允许变化 ±1。这避免了温度在阈值附近小幅波动时,风扇或 CPU 频率频繁切换导致的机械磨损和功耗尖峰。
我在某款 NAS 设备上实测:启用step_wise后,CPU 温度稳定在 72±0.5℃,风扇转速无明显波动;而换成bang_bang(开关式),温度在 70~75℃ 间锯齿震荡,风扇启停声每分钟达 12 次。
4. 实操全流程:从零构建一个可工作的 thermal zone(RK3588 示例)
4.1 硬件准备与设备树修改
假设你有一块 RK3588 开发板,已知其 TSADC controller 地址为0xff2a0000,支持 4 个 thermal sensor channel,CPU cluster 使用 channel 0。首先在arch/arm64/boot/dts/rockchip/rk3588.dtsi中添加 TSADC 节点:
&tsadc { compatible = "rockchip,rk3588-tsadc"; reg = <0x0 0xff2a0000 0x0 0x1000>; interrupts = <GIC_SPI 42 IRQ_TYPE_LEVEL_HIGH>; clocks = <&cru SCLK_TSADC>, <&cru PCLK_TSADC>; clock-names = "tsadc", "pclk"; #thermal-sensor-cells = <1>; status = "okay"; };然后在板级 DTS(如rk3588-evb.dts)中定义 thermal zone:
&cpu_thermal { status = "okay"; thermal-sensors = <&tsadc 0>; // channel 0 for CPU trips { cpu_alert0: trip@0 { temperature = <70000>; hysteresis = <2000>; type = "passive"; }; cpu_alert1: trip@1 { temperature = <85000>; hysteresis = <5000>; type = "active"; }; }; cooling-maps { map0 { trip = <&cpu_alert0>; cooling-device = <&cpu0_cooling 0 4>; }; map1 { trip = <&cpu_alert1>; cooling-device = <&fan0 0 15>; }; }; }; &fan0 { compatible = "pwm-fan"; pwms = <&pwm0 0 50000 0>; // 20kHz PWM cooling-levels = <0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15>; #cooling-cells = <2>; status = "okay"; };注意cooling-levels属性:它定义了 16 个 PWM 占空比档位(0%~100%),#cooling-cells = <2>表示cooling-device引用时需传两个参数(min/max state)。
4.2 内核配置与编译
确保以下配置项启用(.config):
CONFIG_THERMAL=y CONFIG_THERMAL_OF=y CONFIG_THERMAL_GOV_STEP_WISE=y CONFIG_THERMAL_GOV_BANG_BANG=y CONFIG_THERMAL_GOV_POWER_ALLOCATOR=y CONFIG_THERMAL_WRITABLE_TRIPS=y CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE=y CONFIG_THERMAL_EMULATION=y # 调试用,可模拟温度 CONFIG_ROCKCHIP_THERMAL=y CONFIG_PWM_FAN=y CONFIG_CPU_FREQ=y CONFIG_CPU_FREQ_DT=y编译并烧写内核镜像后,启动时dmesg应看到:
rockchip_thermal rockchip_thermal: tsadc initialized thermal thermal_zone0: registered as thermal_zone0 pwm-fan fan0: Registered as cooling device thermal thermal_zone0: binding with cpu0_cooling thermal thermal_zone0: binding with fan04.3 验证与调试:五步定位 thermal 是否真工作
确认 zone 注册成功:
ls /sys/class/thermal/ # 应看到 thermal_zone0 cat /sys/class/thermal/thermal_zone0/temp # 应输出类似 65000(65℃)检查 trip point 设置:
cat /sys/class/thermal/thermal_zone0/trip_point_0_temp # 应为 70000 cat /sys/class/thermal/thermal_zone0/trip_point_0_type # 应为 passive验证 cooling device 绑定:
ls /sys/class/thermal/thermal_zone0/cdev0 # 应为 cpu0_cooling ls /sys/class/thermal/thermal_zone0/cdev1 # 应为 fan0手动触发 cooling(调试用):
# 强制设 CPU 为最低频(state 0) echo 0 > /sys/class/thermal/thermal_zone0/cdev0/cur_state # 强制设风扇为最高转速(state 15) echo 15 > /sys/class/thermal/thermal_zone0/cdev1/cur_state观察 governor 日志(需开启 debug):
echo 1 > /sys/module/thermal/parameters/debug dmesg | grep -i "thermal.*throttle" # 应看到 step_wise 的决策日志
实操心得:如果
temp读数始终为 0,90% 是 TSADC 时钟未 enable。检查dmesg中是否有failed to get clk tsadc。解决方案:在&tsadc节点中添加clocks = <&cru SCLK_TSADC>, <&cru PCLK_TSADC>,并在rockchip,rk3588-cru.h中确认SCLK_TSADC定义正确。
4.4 性能调优:三组关键参数的实测经验
thermal 的效果不取决于“是否启用”,而在于参数调优。我在 RK3588 上总结出三组黄金参数:
| 参数 | 推荐值 | 依据 | 实测效果 |
|---|---|---|---|
trip_point_0_temp(passive) | 70℃ | CPU 持续负载下安全结温上限 | 温度 >70℃ 时开始降频,避免长期高温老化 |
hysteresis(滞后值) | 3000(3℃) | 小于温度传感器精度(±2℃) | 避免在 69.5~70.5℃ 区间频繁触发 |
polling_delay(采样间隔) | 2000ms(默认)→ 1000ms | 高性能场景需更快响应 | 温度上升速率 >5℃/s 时,1s 采样比 2s 降低峰值温度 4℃ |
调整方法:
# 临时修改(重启失效) echo 1000 > /sys/class/thermal/thermal_zone0/polling_delay # 永久修改:在设备树中添加 &cpu_thermal { polling-delay-passive = <1000>; polling-delay-active = <500>; };特别提醒:polling_delay_active应小于passive,因为 active cooling(风扇)响应慢,需更早干预。
5. 常见问题与排查技巧实录:那些官方文档不会告诉你的坑
5.1 “温度读数不准”的 5 个根源与诊断树
温度不准是 thermal 最常见问题,但原因千差万别。我整理了一个快速诊断树:
温度读数异常? ├─ 读数为 0 或负数 → 检查 TSADC controller 是否 enabled(dmesg 搜 "clk"、"reset") ├─ 读数恒定不变 → 检查 `get_temp()` 是否被阻塞(用 `perf record -e sched:sched_switch` 看是否卡在 thermal ops) ├─ 读数偏高 5~10℃ → 校准表未适配(用红外测温枪实测 CPU 表面,对比 `temp` 值,修正 `rockchip_thermal_table[]`) ├─ 读数跳变剧烈(±10℃) → ADC 电源噪声(检查 VDD_TSADC 是否有足够滤波电容,实测纹波 <10mVpp) └─ 读数随 CPU 负载突变 → sensor placement 错误(TSADC 应靠近 CPU core,而非远离的 PCB 区域)典型案例:某款工控机temp显示 120℃,但红外枪测 CPU 仅 65℃。dmesg发现rockchip_thermal: invalid temperature from TSADC。根源是 TSADC 的参考电压VREF被错误连接到 3.3V 而非 1.8V,导致 ADC 量程压缩。更换VREF连线后恢复正常。
5.2 “cooling device 不响应”的链路排查法
当echo 15 > cdev1/cur_state但风扇不转,按此顺序排查:
确认 cooling device 注册成功:
ls /sys/class/thermal/cooling_device* # 应看到 cooling_device0(fan0) cat /sys/class/thermal/cooling_device0/cur_state # 应随 echo 改变检查 PWM 输出(用示波器测
pwm0引脚):- 若无 PWM 波形 →
pwm-fan.c驱动未加载,或pwms属性地址错误 - 若有波形但占空比不对 →
cooling-levels数组长度与cur_state范围不匹配(如cooling-levels只有 8 个值,却设cur_state=15)
- 若无 PWM 波形 →
验证风扇供电:
# 测量风扇接口电压,应为标称值(如 12V) # 用万用表短接 PWM 信号线与 GND,风扇应全速转(排除风扇本体故障)检查 thermal zone 绑定:
# 查看 cooling device 是否被 zone 引用 ls /sys/class/thermal/thermal_zone0/cdev* # 应有 cdev1 指向 fan0 # 若无,检查设备树中 `cooling-maps` 的 phandle 是否正确
注意:
cur_state值超出cooling-levels数组长度时,内核会静默截断,不会报错。这是最隐蔽的 bug 来源。
5.3 Governor 切换失败的三大陷阱
echo "power_allocator" > /sys/class/thermal/thermal_zone0/policy失败?常见原因:
- 依赖未启用:
power_allocator需要CONFIG_THERMAL_POWERCAP和CONFIG_ENERGY_MODEL。检查.config是否启用。 - energy model 缺失:
power_allocator需要每个 CPU 的功耗模型(EM)。在drivers/base/power/energy_model.c中,EM 数据来自cpufreq-dt或arm_big_little驱动。若dmesg出现Failed to initialize energy model for cpu0,说明 EM 未注册。 - cooling device 不支持 power:
power_allocator要求 cooling device 实现get_requested_power()回调。cpufreq_cooling.c支持,但pwm_fan.c不支持——这意味着你不能用power_allocator同时管理 CPU 和风扇,只能选其一。
解决方案:对纯 CPU 管理场景,power_allocator效果极佳;对 CPU+风扇混合场景,坚持用step_wise,并通过设备树精细控制cooling-maps的权重。
5.4 热关机(Critical Shutdown)的预防与审计
critical temperature reached, shutting down是最严重的 thermal 事件。预防措施:
设置合理的 critical trip:必须低于芯片 datasheet 的 Tjmax(结温)。RK3588 Tjmax=105℃,故
criticaltrip 应设 ≤100℃,留 5℃ 安全裕量。启用 thermal emergency shutdown log:
# 在内核命令行添加 thermal.emergency_shutdown=1 # 启动后,/sys/firmware/devicetree/base/thermal/emergency_shutdown 会显示 last shutdown reason审计 shutdown 前 10 秒行为:
# 开启 ftrace 记录 thermal 事件 echo 1 > /sys/kernel/debug/tracing/events/thermal/thermal_zone_trip/enable # 复现问题后,dump trace cat /sys/kernel/debug/tracing/trace_pipe | grep "trip.*critical"
我曾处理过一个案例:客户设备在 85℃ 时关机,但criticaltrip 设为 100℃。trace_pipe显示thermal_zone0: critical temperature reached,进一步发现是rockchip_thermal驱动中一个if (temp > 85000)硬编码判断——这是厂商 BSP