☰
Linux内核thermal framework深度解析:热区、触发点与冷却设备协同机制
2026/10/8 18:14:19 网站建设 项目流程

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 时,它做的三件事决定了整个框架的健壮性:

  1. 初始化 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,根源就是绕过了这个保护机制。

  2. 建立 cooling device 关联表:当thermal_cooling_device_register()被调用时,thermal_sys 会遍历所有已注册的 zone,检查其设备树中是否声明了对该 cooling device 的引用(如cooling-device = <&cpu0_cooling 0 4>)。这种“松耦合关联”让 cooling device 可以动态增减——比如热插拔一个 USB 风扇,内核能自动将其纳入 thermal 管理,无需重启。

  3. 启动 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 的 phandle
  • 0: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 为例):

  1. sysfs层触发thermal_zone_show_temp()
  2. → 调用tz->ops->get_temp(tz, &temp)
  3. → 进入rockchip_thermal_get_temp()
  4. → 调用rockchip_tsadc_get_temp()
  5. → 通过regmap_read()读取 TSADC 寄存器TSADCCON和TSADCDAT
  6. → 查rockchip_thermal_table[]温度补偿表(该表由芯片厂提供,校准 ADC 非线性)
  7. → 返回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()中,包含三个关键阶段:

  1. Trip 状态检测:遍历所有 trip point,找出当前激活的最高优先级 trip(CRITICAL > ACTIVE > PASSIVE)。例如温度 78℃ 时,cpu_alert0(70℃)和cpu_alert1(85℃)都满足,但cpu_alert0优先级更高,故选择它。

  2. 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。

  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 fan0

4.3 验证与调试:五步定位 thermal 是否真工作

  1. 确认 zone 注册成功:

    ls /sys/class/thermal/ # 应看到 thermal_zone0 cat /sys/class/thermal/thermal_zone0/temp # 应输出类似 65000(65℃)
  2. 检查 trip point 设置:

    cat /sys/class/thermal/thermal_zone0/trip_point_0_temp # 应为 70000 cat /sys/class/thermal/thermal_zone0/trip_point_0_type # 应为 passive
  3. 验证 cooling device 绑定:

    ls /sys/class/thermal/thermal_zone0/cdev0 # 应为 cpu0_cooling ls /sys/class/thermal/thermal_zone0/cdev1 # 应为 fan0
  4. 手动触发 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
  5. 观察 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但风扇不转,按此顺序排查:

  1. 确认 cooling device 注册成功:

    ls /sys/class/thermal/cooling_device* # 应看到 cooling_device0(fan0) cat /sys/class/thermal/cooling_device0/cur_state # 应随 echo 改变
  2. 检查 PWM 输出(用示波器测pwm0引脚):

    • 若无 PWM 波形 →pwm-fan.c驱动未加载,或pwms属性地址错误
    • 若有波形但占空比不对 →cooling-levels数组长度与cur_state范围不匹配(如cooling-levels只有 8 个值,却设cur_state=15)
  3. 验证风扇供电:

    # 测量风扇接口电压,应为标称值(如 12V) # 用万用表短接 PWM 信号线与 GND,风扇应全速转(排除风扇本体故障)
  4. 检查 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 事件。预防措施:

  1. 设置合理的 critical trip:必须低于芯片 datasheet 的 Tjmax(结温)。RK3588 Tjmax=105℃,故criticaltrip 应设 ≤100℃,留 5℃ 安全裕量。

  2. 启用 thermal emergency shutdown log:

    # 在内核命令行添加 thermal.emergency_shutdown=1 # 启动后,/sys/firmware/devicetree/base/thermal/emergency_shutdown 会显示 last shutdown reason
  3. 审计 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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询