Linux thermal framework深度解析:从trip判定到governor决策的温控链路

发布时间:2026/10/31 6:23:52
Linux thermal framework深度解析:从trip判定到governor决策的温控链路 写到这里已经是这个 Linux 内核功耗子系统系列的第九篇了。前面几篇我们聊了 cpufreq、cpuidle、PM domain 这些功耗管理的核心组件这次终于轮到 thermal framework。说实话thermal 这块在功耗子系统中属于兜底的角色——前面的组件都在想办法省电thermal 是在省电手段失效、芯片真的热起来的时候用降频、限流这些粗暴手段把温度摁回去。但恰恰因为它是最后一道防线它的架构设计反而非常值得仔细看一遍。这篇文章我打算从框架的整体分层讲起把 thermal_zone_device、thermal_cooling_device、thermal_instance 这几个核心对象之间的关系理清楚然后沿着温度采集 - trip 判定 - governor 决策 - cooling device 执行这条链路走一遍最后结合 device tree 配置和 sysfs 调试接口聊几个我在实际项目中踩过的坑。适合已经对 Linux 驱动模型有基础、想系统梳理 thermal 框架的读者。1. 为什么需要一套通用温控框架从散热策略到内核抽象1.1 没有框架的年代各家 SoC 各写各的在 thermal framework 成熟之前芯片厂商的温控逻辑基本是各自为政。高通、三星、海思各自在自家 SoC 的底层驱动里塞了一套温度监控和降频逻辑有的放在 msm_thermal 里有的放在 exynos_thermal 里有的是在 cpufreq 驱动里偷偷改频率表。这样做的问题很明显物料清单上换一颗传感器就要重写一遍策略同一个降频算法在三个平台上被复制粘贴了三遍而且系统工程师想从 user space 看一眼当前温度得先搞清楚这个平台用的是哪一套私有接口。内核社区最后给出的答案就是把这套东西抽象成三个角色温度传感器/温区thermal zone、降温手段cooling device、控制策略governor。 sensor 负责报温度cooling device 负责执行降温动作governor 负责在温度超过阈值了该降多少这件事上做决策。三者的关系通过 thermal_instance 绑定起来形成一张可配置的网。1.2 thermal framework 解决的三个核心问题第一个问题是温度数据的统一。不管底层是 NTC 热敏电阻、芯片内部的 TSENS 传感器还是 PMIC 上报的温度框架层面都收敛成一个 thermal_zone_device暴露统一的 ops 接口通过 get_temp() 回调拿到温度单位统一为毫摄氏度millicelsius。上层不需要关心传感器是 I2C 的还是 SPI 的还是寄存器直读的。第二个问题是策略与硬件解耦。governor 是纯软件层面的东西不关心你用的是哪颗 SoC。同样是 trip 点触发step_wise 会一步一步往下调power_allocator 会算一个精确的功耗预算fair_share 按权重分。SoC 厂商只需要把 zone、trip、cooling device 在 device tree 里描述清楚选哪个策略是运行时的事。第三个问题是执行器标准化。cpufreq 可以降频cpuidle 可以限制进入深层 idledevfreq 可以限带宽风扇可以调速这些完全不同的执行方式统一抽象成 cooling device 的 set_cur_state/get_cur_state 接口。cooling device 的状态是一个 0 到 max_state 的整数0 表示不干预数值越大降温越激进。至于状态 3 对应的到底是 1.2GHz 还是 1.0GHz那是各 cooling device 驱动自己翻译的事。1.3 整体分层传感器、策略、执行器把整个框架从下往上分大致是这么四层底层传感器驱动和 cooling device 驱动对应 thermal_zone 的 ops 和 cooling device 的 ops它们通过注册接口挂进框架。核心层thermal core包含 thermal_zone_device、thermal_cooling_device、thermal_instance 的生命周期管理以及 thermal_zone_device_update() 这个核心入口。策略层governor注册到核心层的 governor_list 里每个 thermal zone 在运行时指定使用哪个 governor。上层sysfs 接口、netlink 事件、device tree 解析以及用户空间的 thermal daemon。这四层之间接口都很薄核心层只有几十个导出函数。我个人的理解是thermal framework 本质上就是一个注册表 事件循环 策略插槽的组合体它本身不包含任何具体的温控智慧智慧都在 governor 和驱动里。2. 三个核心对象与一张关系网thermal_zone、cooling_device、instance2.1 thermal_zone_device温区的大脑每个 thermal_zone_device 在 struct 里都有一堆字段但理解它最关键的是抓住三类信息struct thermal_zone_device { struct device *device; const struct thermal_zone_params *tzp; const struct thermal_zone_ops *ops; struct list_head thermal_instances; struct thermal_governor *governor; struct thermal_trip trips[]; // 或者通过 ops 动态获取 int temperature; int prev_temperature; int passive_delay; int polling_delay; ... };ops 是驱动侧需要实现的回调最重要的三个是 get_temp()、get_trip_temp()/get_trip_type()在 DT 支持的平台上通常不用驱动实现直接从 DT 解析、set_mode()。一个温区的名字如 cpu-thermal、gpu-thermal、battery-thermal就是 sysfs 里的目录名同一个 SoC 上可以有多个 zone每个 zone 独立跑自己的策略循环。需要注意的一个细节是thermal zone 的温度上报有两种模式轮询模式和中断模式。轮询模式下内核会启动一个延迟工作项每隔 polling_delay被动调温时切换成 passive_delay毫秒调一次 get_temp()中断模式下传感器驱动在温度跨越阈值时主动调用 thermal_zone_device_update() 触发一次处理。现在的 SoC 基本都有硬件中断但 framework 里轮询这条路径依然保留着很多平台为了省事仍然在用。2.2 thermal_cooling_device降温手段的抽象cooling device 的抽象非常简洁核心就是三个回调struct thermal_cooling_device_ops { int (*get_max_state)(struct thermal_cooling_device *cdev, unsigned long *state); int (*get_cur_state)(struct thermal_cooling_device *cdev, unsigned long *state); int (*set_cur_state)(struct thermal_cooling_device *cdev, unsigned long state); };get_max_state 返回最大档位比如 cpufreq cooling 可能支持 16 档set_cur_state 是 governor 调度的结果值越大冷却越强。在 cpufreq cooling 里set_cur_state 内部会把 state 换算成频率上限然后调用 cpufreq 接口限制最大频率在 PWM 风扇里state 直接对应占空比。这里有个经常被误解的点cooling device 的 state 和 CPU 频率不是一一对应的线性关系。cpufreq cooling 的实现里有一个频率表state 是频率表的下标state 越大对应的频率越低。也就是说降温档位 0 是不限制频率最大档位是锁到最低频率。中间档位的频率分布取决于 OPP 表可能不是均匀的。2.3 thermal_instance把温度和手段绑在一起的路由表说实话我第一次看 thermal_instance 这个结构时觉得它是多余的——不就是把 zone 和 cdev 绑在一起吗实际上它才是整个框架的路由表。一个 zone 可以绑定多个 cdev一个 cdev 也可以服务多个 zone比如 GPU zone 和 CPU zone 共享一个风扇。如果不加 instance 这层中转多对多的关系根本无法表达。struct thermal_instance { struct thermal_zone_device *tz; struct thermal_cooling_device *cdev; struct thermal_trip trip; // 权重、类型是该 instance 自己的 unsigned long upper; // 该 instance 允许设置的上限档位 unsigned long lower; // 下限档位 unsigned long target; // governor 最近一次算出来的目标档位 u32 weight; // 用于 fair_share / power_allocator ... };每个 instance 还有自己的 upper/lower 限位。比如同一个 cpufreq cooling device在 CPU zone 里的 upper 可能是 16完全放开但在 battery zone 里的 upper 可能是 8最多只允许降到 8 档因为电池过热时要限制 CPU但不希望把性能压得太狠。weight 则用于多个 instance 共享同一个 cdev 时按权重分配降温幅度。2.4 thermal_trip 与 trip 的四种类型trip 是温控逻辑里最核心的阈值点。每个 thermal zone 可以有一组 trip每个 trip 有自己的温度阈值和类型active主动散热设备介入点通常是风扇开启的阈值。passive被动散热介入点通常是开始降频的阈值。对应 devicetree 里的 passive-delay。hot达到这个点说明被动散热已经压不住了可能会采取更激进的手段比如通知用户空间。critical最后防线到这个点系统要执行紧急关机orderly_poweroff防止硬件损坏。内核在 __thermal_zone_device_update() 里逐个检查 trip把当前温度与每个 trip 阈值做比较然后调用 handle_thermal_trip() 通知 governor 做决策。每个 trip 还有一个 hysteresis迟滞参数这个参数在后面讲工作循环时单独展开。3. 温度上报与 trip 命中thermal_zone 的工作循环3.1 温度从哪来get_temp 回调与轮询机制thermal_zone_device_register() 之后框架会根据 mode 字段决定是轮询还是中断驱动。轮询模式下注册时如果 polling_delay 大于 0就会 schedule 一个 delayed workthermal_zone_device_check这个 work 执行时调用 thermal_zone_device_update() 做一次完整巡检然后根据当前状态重新调度自己。拿到温度的核心路径是 thermal_zone_get_temp()它套了一层 mutex 后调用 ops-get_temp()。驱动侧需要把裸的 ADC 值或者寄存器值换算成毫摄氏度填到参数指针里。这一步有个约定get_temp() 返回 0 表示成功非零会让框架认为温度读取失败。失败时thermal_zone_device_update() 是不会继续往下跑 trip 判定和 governor 决策的这一点是我后面要说的坑之一。3.2 一次完整的 thermal_zone_device_updatethermal_zone_device_update() 是框架内部最重要的入口函数逻辑大概是调 thermal_zone_get_temp() 拿当前温度失败则跳过错峰。更新 zone 的 temperature 字段把旧值存到 prev_temperature。对于每个 trip调 thermal_zone_trip_up() / thermal_zone_trip_down() 判断温度是否跨过 trip 阈值。如果发生了 trip 状态变化或者处于持续降频状态则调用 governor 的 throttle() 回调传入当前温度和各 trip 状态。根据 zone 状态更新下一次 poll 的 delaypassive 时用 passive_delay平时用 polling_delay。这段逻辑在 5.15 之后的内核里经历过一次重构原来在 thermal_zone_device_update() 里直接铺开的 for_each_trip 循环和 governor 调用被拆得更细但总体流程没变。理解这个入口函数基本就理解了 thermal 框架的运行时主干。3.3 hysteresis迟滞为什么重要hysteresis 这个概念在温度控制里非常关键但很多第一次接触的人会忽略它。它的作用是防止温度在阈值附近反复横跳导致风扇频繁启停或者频率频繁抖动。举个例子trip 阈值是 85 度没有 hysteresis 时温度到 85.1 度触发降温风扇开始转温度降到 84.9 度就解除风扇停然后温度又升回 85.1 度……风扇就在启停之间疯狂振荡。给这个 trip 配一个 5000 毫摄氏度的 hysteresis意味着从未触发到触发要温度升到 85 度但从触发回到未触发要温度降到 80 度以下。这 5 度的回差区间给系统留了稳定的工作窗口。内核里 trip 的 hysteresis 值可以从 DT 的 hystersis 属性读出来这个拼写错误一直保留到了现在官方文档也这么写也可以在 sysfs 里直接改。4. governor 决策机制step_wise、fair_share、power_allocator4.1 governor 在内核里到底干什么governor 的核心就是一个 throttle() 回调由核心层的 update 流程在 trip 状态变化时调用。governor 需要根据当前温度是多少、哪些 trip 已经被越过、上次给每个 instance 定的 target 是多少算出新的 target 值然后通过 thermal_instance_set_trips() 之类的接口把 target 写回 instance最终由核心层调 cdev-ops-set_cur_state() 落到执行器上。每个 governor 注册时会声明自己支持的特性比如 power_allocator 需要 zone 有足够的 power 相关参数如果硬件描述里缺了这些参数即使你在 policy 属性里指定了它框架也可能会拒绝绑定或者退回到默认 governor。内核里 policy 的优先级是DT / sysfs 里显式指定的 governor 优先其次按 governor_list 里的优先级排序default 是 step_wise。4.2 step_wise最常用也最好理解step_wise 的思路非常朴素维护一个 trend温度变化趋势温度和 trip 的关系无非是低于 trip、在 trip 附近徘徊、刚越过 trip、远高于 trip这几种每种状态下对 instance 的 target 做一步调整。具体来说当温度刚越过一个 trip 时step_wise 会把该 trip 绑定的所有 instance 的 target 从上一个状态逐步上调target 越大降温越强一次只调一档当温度回落到 hysteresis 以下时再逐步下调。它的名字里这个 step 就是一档一档来的意思。好处是不会一次性把频率拉到最低造成性能悬崖坏处是响应速度偏慢对于发热极快的场景可能压不住。绝大多数嵌入式平台的默认温控策略都是它。4.3 power_allocator功耗预算的思路power_allocator 是目前内核里最高级的 governor源自 ARM 的 IPAIntelligent Power Allocation方案。它的思路不是温度高了就降一档而是反过来先根据当前温度和 target 温度之间的偏差通过 PID 控制器算出一个允许的总功耗预算然后把这个预算按权重分配给各个 cooling device每个 cdev 再把功耗预算映射成 state。这套机制需要 DT 里提供充分的功率参数包括 sustainable-power可持续功耗、power model 里的动态/静态功率系数等。如果这些参数缺失power_allocator 就跑不起来。它最大的优势是能在多热源共享散热资源时做精细分配比如 CPU 和 GPU 同时发热谁的热量贡献大就多限制谁。缺点是参数整定PID 系数、预测系数很有门槛调不好效果反而不如 step_wise。4.4 fair_share 与 user_spacefair_share 是按权分配的简单策略每个 instance 有 weight 属性温度超过 trip 后每个 cdev 被压低的比例与权重成正比。它比 step_wise 更平滑但缺乏反馈闭环实际平台里用的人不多。user_space 则完全不做内核侧决策只在 trip 触发时向用户空间发送 netlink 事件也有老的 uevent 方式由 thermal daemon 或者用户自定义的脚本决定做什么。Android 的 thermal hal 早期就大量依赖这类机制。选 user_space 意味着你要保证用户空间进程可靠运行如果 daemon 挂了温控就完全失效所以关键平台一般不会把它作为唯一防线。5. cooling device 的落地频率、电压、风扇到底怎么被驱动5.1 cpufreq cooling最典型的降温执行器cpufreq cooling 是所有 cooling device 里最常打交道的。注册入口是 cpufreq_cooling_register()它会根据 CPU 的 freq 表和 OPP 的 dynamic power coefficient 算出每个档位对应的功耗然后内部实例化一个 thermal_cooling_device。set_cur_state 的实现路径大致是state 换算成对应的最大频率 - 调 cpufreq 接口设置 policy 的 max - cpufreq 驱动在下次频率切换时把频率限制到这个上限以内。这实际上就是通过限制最高频率来降功耗所以你会发现降温后 CPU 跑在低频高占用的状态而不是直接 idle。这也是为什么单纯靠降频能效往往不如 cpuidle 那种跑完赶紧睡的策略——但 thermal 场景下我们追求的是快速压低发热降频依然是最直接的手段。5.10 内核之后引入了 DTPMDynamic Thermal Power Management框架把 cpufreq cooling 的功耗建模做得更细可以对 CPU 的功耗做更精确的预算控制配合 power_allocator 使用。如果你想深挖功耗建模可以从 drivers/thermal/cpufreq_cooling.c 里的 power 相关实现看起。5.2 devfreq cooling 与 device cooling除了 CPUGPU、DDR、NPU 这些设备也有自己的频率控制。它们大多走 devfreq 框架所以对应的 cooling device 就是 devfreq_cooling。注册方式类似用 devfreq 设备的频率表和功率模型注册成 cooling deviceset_cur_state 时限制 devfreq 的最大频率。比如 Mali GPU 的调频就是通过 devfreq cooling 接入 thermal 的。另外还有 device cooling 这种更通用的封装直接对某个 device 调 thermal_device_ops 的 set_cur_state适用于那些没有标准 freq 管理框架的硬件。总的来说cooling device 的 universe 就是一切能通过限制资源来降温的手段的统称。5.3 fanPWM与 clock cooling风扇类 cooling device 一般由 pwm-fan 驱动注册state 直接映射到 PWM 占空比。这种 cdev 的特点是 max_state 通常很小比如 4 到 8 档而且它不会像 CPU 降频那样有立竿见影的温度回落所以绑定它的 trip 往往要用 active 类型并且 hysteresis 要设大一点避免风扇频繁换挡产生噪音。clock cooling 则用于那些没有 cpufreq/devfreq 管理、但可以切时钟源的模块。它的 set_cur_state 里会对某个 clock 的速率做限制。这类 cdev 在低端 SoC 上比较常见核心逻辑其实和 cpufreq cooling 一脉相承只是操作对象从 CPU policy 换成了 clk 框架。6. 从配置到落地device tree 描述与 sysfs 调试接口6.1 device tree 里的 thermal-zones现代 ARM 平台基本都在 DT 里描述整个温控拓扑。一个典型的 thermal-zones 节点长这样thermal-zones { cpu_thermal: cpu-thermal { polling-delay-passive 250; polling-delay 1000; thermal-sensors tsens0 0; trips { cpu_alert0: cpu-alert0 { temperature 85000; hysteresis 5000; type passive; }; cpu_crit: cpu-crit { temperature 105000; hysteresis 0; type critical; }; }; cooling-maps { map0 { trip cpu_alert0; cooling-device cpu0 0 4; // 允许的 state 区间 [0, 4] contribution 1024; }; }; }; };每个 zone 节点通过 thermal-sensors 属性引用温度传感器trips 子节点定义阈值cooling-maps 定义哪个 trip 触发时约束哪个 cdev、允许在什么 state 区间内活动。cooling-device 里的两个数字是该 cdev 在本 map 内允许的 lower 和 upper 档位contribution 则是给 fair_share 和 power_allocator 用的权重。这套语法在 Devicetree 的 thermal binding 文档里有完整说明配置前建议仔细读一遍尤其是 cooling-device 的 state 范围写错了会导致降温根本不起作用。另外注意 sensors 描述里可以带传感器编号如 tsens0 0具体含义由传感器驱动解释比如 tsens 的通道 0 可能对应 CPU 附近的温度点通道 1 对应 GPU。6.2 sysfs 接口速查系统起来后/sys/class/thermal/ 下面会有大量目录thermal_zone0、thermal_zone1... 每个温区一个目录里面最重要的是 type名称、temp当前温度毫摄氏度、policy当前 governor、modeenabled/disabled、trip_point_X_temp、trip_point_X_type。cooling_device0、cooling_device1... 每个 cdev 一个目录里面有 type、max_state、cur_state。如果存在 cooling device 和 zone 的绑定还会出现 cooling_deviceX 的 stats 目录或者 thermal_zone 下的 cdev 关联信息不同内核版本暴露形式不同但 cur_state 和 max_state 一直都有。调试时最常用的三板斧就是cat temp 看温度是否在跳cat policy 看用的是哪个 governorcat cooling_deviceN/cur_state 看 governor 有没有真的把挡位推上去。这三个值串起来就能判断温控链路断在哪一环。6.3 调试方法论从 /sys/class/thermal 看问题假设你发现设备温度已经超过 trip 阈值但频率没降按下面顺序排查先确认 zone 的 temp 是不是真的超过 trip_point_X_temp。很多时候是传感器位置不对读到的温度根本没到阈值。确认 zone 的 mode 是 enabled这个文件可以手动写 enabled/disabled跑测试时经常有人误关了。确认 policy 是你预期的 governor。如果 platform 的 default governor 和 DT 指定的不一致行为会很奇怪。看对应 cdev 的 cur_state。如果 cur_state 一直是 0说明 governor 决策没下发如果 cur_state 变了但频率没变问题在 cdev 驱动自身。这套排查流程看起来简单但实际项目里 80% 的温控问题都能被它定位到。7. 排查温控不生效的实践我踩过的几个坑7.1 get_temp 返回错误码导致温区直接停摆我有一次调试平台发现 thermal_zone 的 temp 永远是上一次的正确值不再更新。查了半天最后发现是传感器驱动在某个异常状态下 get_temp() 返回了 -EIO。框架这边拿到非零返回值后直接跳过了后续所有逻辑连 delayed work 都没重新调度等于整个温区进入了僵尸状态。当时我们用的内核版本在这个错误路径上既没有重试机制也没有告警只能靠 log 里偶尔出现的一条 thermal thermal_zone0: failed to read out thermal zone 消息发现。这个问题在较新的内核里有所改善但依然建议驱动实现 get_temp() 时务必保证失败路径最终返回 0 或提供临时有效值不要让错误码冒泡到框架的调度循环里。你自己的驱动里对这种瞬时错误最好是重试一次 返回上次缓存值而不是直接返回错误。7.2 trip 阈值与 hysteresis 配错另一个高频问题是把 trip 的 temperature 和 hysteresis 配成绝对温度。DT 的 temperature 是毫摄氏度85000 表示 85 度。hysteresis 是相对值5000 表示 5 度的回差不是 5 毫摄氏度也不是回到 5000 度。这个单位问题看似低级但真的是团队里新老同事都踩过的坑。配错之后现象很典型温度到 85 度触发降频降到 84.99 度就立刻恢复然后立刻又触发频率在两边疯狂振荡功耗反而比不限制还差。7.3 cooling device 的 state 一直不变还有一个隐蔽问题cooling-maps 里写的 upper 区间太小。比如你要允许 CPU 最多降到 4 档但实际 max_state 是 16你在 map 里写 cooling-device cpu0 0 4那 governor 算出来的 target 会被 clamp 在 4 以内怎么看 cur_state 都最多到 4。这本身是配置语义不算 bug但如果你误以为 state 是绝对频率档位就会懵。建议在调试早期直接给 map 写 0 max_state把区间限制彻底放开确认链路通了再逐步收紧。7.4 governor 没选对的典型表现最后一个坑是 governor 选择。很多平台在 DT 里没写 thermal-governor 属性默认走 step_wise这没问题。但如果你在内核配置里把多个 governor 编进去而且某个 zone 的 DT 里写了 governor power_allocator 却缺少 sustainable-power 属性绑定会失败最终 fallback 到默认 governor而你从 sysfs 里看到的 policy 可能是 step_wise跟预期完全不一样。这种安静失效最坑人因为代码看着没问题配置也好像写了实际走的却是另一套逻辑。排查这类问题最直接的办法是打开 thermal 相关的 ftrace 事件。内核在 handle_thermal_trip() 和 thermal_zone_device_update() 等路径上有 tracepoint可以清楚看到每次巡检时温度、trip 状态和 governor 的调用关系。配合 trace-cmd 用起来比瞎猜高效太多。这系列写到现在我自己最大的体会是thermal framework 的代码量不大但是抽象层次非常讲究传感器、策略、执行器三个维度切得很干净。真正难的不是读代码而是把 device tree 里的拓扑描述、governor 的计算逻辑、cdev 驱动的实际行为这三层在脑子里串成一条完整的因果链。每次遇到温控问题只要沿着温度准不准 - trip 有没有触发 - governor 选对没 - cdev 动没动这条线走基本都能收口。最后再建议一句手里有开发板的话把一个风扇或者可调电源接上自己写个 sensor 驱动模拟温度变化把 step_wise 和 power_allocator 各跑一遍比看十篇源码分析都管用。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询