
1. 从一次温控翻车说起thermal framework 到底管什么前阵子帮一个做嵌入式设备的朋友排查问题设备跑着跑着就降频跑分直接腰斩日志里翻来覆去就是几行thermal_zone相关的打印。他一开始怀疑是 CPU 调度器的问题折腾了两天没结果最后定位到是温控策略把频率压下去了。这件事让我意识到很多做 Linux 内核或者嵌入式开发的人对thermal framework这套东西其实是知道有这么个东西但说不清楚它怎么运转。thermal framework 是 Linux 内核功耗子系统里负责温度管理的核心框架。它要做的事情说起来简单监测芯片上各个发热点的温度当温度超过阈值时采取降温手段比如降频、关核、限制充电电流、调风扇转速。但真正落到内核代码里它是一套相当精巧的分层架构涉及 thermal zone 设备、governor 策略、cooling device、trip point、binding 关系等一堆概念。这套框架从早期的简单温控演进到现在支持多 zone、多 governor、设备树描述、用户空间干预复杂度已经不低了。这篇文章适合谁看如果你在做嵌入式 Linux 开发、Android 底层、服务器功耗调优或者单纯想搞明白内核功耗子系统是怎么组织的那 thermal framework 是你绕不开的一环。我会从整体架构入手把它的分层设计、核心数据结构、注册流程、governor 工作机制、设备树描述方式、以及实际调试中踩过的坑尽量讲透。不堆砌源码但关键的逻辑和参数会讲清楚为什么这么设计。需要先说明一点thermal framework 的代码在不同内核版本之间变化不小尤其是 4.15 之后引入了 thermal zone 参数、5.x 之后对 governor 做了重构。我下面讲的内容以主流 5.x/6.x 内核为基准如果你用的是更老的版本部分结构体字段和注册接口会有差异需要对照你手上的源码看。2. thermal framework 的整体分层设计2.1 为什么要把温控拆成三层理解 thermal framework最关键的是先理解它的分层思想。内核里很多子系统都是分层的但 thermal 的分层有它自己的逻辑因为温控这件事天然涉及三个不同角色谁在发热、哪里热这是温度传感器和 thermal zone 要回答的。热了之后怎么办这是 governor 策略要决策的。具体怎么降温这是 cooling device 要执行的。如果把这三种职责揉在一起代码会变成一团乱麻每加一种传感器或者每加一种降温手段都要改核心逻辑。所以 thermal framework 把它们拆成了三层中间通过标准接口通信。这个设计思路和输入子系统的 input handler、或者网络子系统的协议栈分层是一个道理变化的部分隔离稳定的部分复用。具体来说thermal framework 的分层是这样的层次角色典型代表职责感知层thermal zoneCPU 温度传感器、GPU 传感器、电池温度提供温度读数定义 trip point决策层governorstep_wise、power_allocator、fair_share根据温度和政策决定降温动作执行层cooling devicecpufreq cooling、风扇、充电限流实际执行降频、调速等操作这三层之间通过thermal zone 与 cooling device 的绑定关系连接起来。一个 thermal zone 可以绑定多个 cooling device一个 cooling device 也可以被多个 zone 引用。这种多对多关系是 thermal framework 灵活性的来源也是调试时容易搞混的地方。2.2 thermal zone温度的抽象容器thermal zone 是 thermal framework 里最核心的概念。你可以把它理解成一个需要被监控温度的区域。这个区域可能对应一颗 CPU、一个 GPU、一块电池、甚至整个 SoC。每个 thermal zone 在内核里对应一个struct thermal_zone_device实例。一个 thermal zone 包含几个关键要素温度读取方式通过ops-get_temp回调获取当前温度单位是毫摄氏度。trip point 列表定义了一系列温度阈值每个阈值触发不同的动作。绑定的 cooling device每个 trip point 可以关联一组 cooling device 和对应的降温状态。governor决定用哪种策略来管理这个 zone。trip point 是理解 thermal zone 的关键。它本质上是一个温度阈值加上一个类型。类型主要有几种THERMAL_TRIP_ACTIVE主动降温比如开风扇。THERMAL_TRIP_PASSIVE被动降温比如降频。THERMAL_TRIP_HOT过热通常触发系统级动作。THERMAL_TRIP_CRITICAL临界温度通常触发关机或重启。为什么要有这么多类型因为不同温度区间该做的事情不一样。比如 60 度的时候你可能只想让风扇转快点85 度的时候要开始降频105 度的时候就得考虑关机保护硬件了。trip point 就是把这些政策用数据描述出来而不是硬编码在逻辑里。2.3 governor降温策略的大脑governor 是 thermal framework 的决策层。它拿到当前温度和 trip point 信息后决定要不要降温、降多少。内核里内置了几种 governor各有适用场景step_wise最常用的 governor。温度每超过一个 trip point就增加一级降温状态逐步加码。它的逻辑简单直接适合大多数场景。power_allocator基于功耗预算的 governor会综合考虑 CPU 的功耗模型来分配降温额度。适合对性能敏感的移动设备。fair_share把降温需求平均分配给所有绑定的 cooling device。适合多个设备需要协同降温的场景。bang_bang最简单的开关式控制温度超过阈值就全速降温低于就停止。适合风扇这种只有开关两档的设备。user_space把决策权交给用户空间内核只负责上报温度。适合需要复杂策略的场景。governor 的选择不是随便挑的。step_wise 通用但不够精细power_allocator 精细但需要准确的功耗模型bang_bang 简单但容易震荡。实际项目里移动设备常用 power_allocator服务器和嵌入式设备常用 step_wise风扇控制常用 bang_bang。2.4 cooling device真正干活的执行者cooling device 是 thermal framework 的执行层。它代表一种可以被用来降温的手段。内核里常见的 cooling device 有cpufreq cooling通过限制 CPU 频率来降温是最常见的降温手段。cpuidle cooling通过限制 CPU 进入深度 idle 状态来降温。devfreq cooling通过限制 GPU、内存等设备的频率来降温。风扇 cooling控制风扇转速。充电 cooling限制充电电流降低电池发热。每个 cooling device 有一个降温状态cooling state的概念。状态值越大降温越狠。比如 cpufreq cooling 的状态 0 表示不限制状态 1 表示限制到某个频率状态 2 表示限制到更低的频率以此类推。governor 决定把状态设成多少cooling device 负责执行。这里有个容易踩的坑cooling state 的具体含义是由 cooling device 自己定义的不同设备的 state 不能直接比较。比如 CPU 的 state 3 和风扇的 state 3 完全是两回事。governor 在分配降温额度时需要理解每个 cooling device 的能力这也是 power_allocator 比 step_wise 复杂的原因之一。3. 核心数据结构与注册流程拆解3.1 thermal_zone_device 结构体关键字段要读懂 thermal framework 的代码struct thermal_zone_device是必须啃下来的。这个结构体字段很多我挑几个最关键的讲struct thermal_zone_device { int id; char type[THERMAL_NAME_LENGTH]; struct device device; struct thermal_attr *trip_temp_attrs; struct thermal_attr *trip_type_attrs; struct thermal_attr *trip_hyst_attrs; void *devdata; int trips; int passive_delay; int polling_delay; int temperature; int last_temperature; int emul_temperature; int passive; unsigned int forced_passive; struct thermal_governor *governor; struct list_head thermal_instances; struct list_head node; struct delayed_work poll_queue; ... };几个字段值得单独说typezone 的名字比如cpu-thermal、gpu-thermal。这个名字会出现在 sysfs 里调试时靠它区分不同 zone。tripstrip point 的数量。polling_delay轮询间隔单位毫秒。如果传感器不支持中断上报温度内核就得定期轮询。passive_delay被动降温时的轮询间隔通常比 polling_delay 短因为被动降温时温度变化快需要更密集的监控。thermal_instances这个链表挂的是 zone 和 cooling device 的绑定关系是理解 binding 机制的关键。poll_queue延迟工作队列负责定期读取温度并触发 governor。polling_delay的设置是个经验活。设太大温度响应不及时可能已经过热了才反应过来设太小频繁读取传感器会增加功耗和 CPU 占用。一般传感器能中断上报的polling_delay 设成 0走中断不能中断的根据热惯性设 100ms 到 1000ms 不等。CPU 这种热惯性小的设 100ms 左右电池这种热惯性大的设 1000ms 也够。3.2 thermal zone 的注册流程注册一个 thermal zone核心是调用thermal_zone_device_register()或者它的带参数版本thermal_zone_device_register_with_trips()。以带 trips 的版本为例流程大致是struct thermal_zone_device * thermal_zone_device_register_with_trips(const char *type, void *devdata, struct thermal_zone_device_ops *ops, struct thermal_zone_params *tzp, int trips, int mask, int polling_delay, int passive_delay);参数里几个关键点typezone 名字必须唯一。ops操作回调集合至少要有get_temp。tripstrip point 数量。mask哪些 trip point 是可写的用于 sysfs 权限控制。polling_delay/passive_delay轮询间隔。注册过程中内核会做几件事分配 zone 结构体、初始化 sysfs 属性、注册设备、启动轮询工作队列、绑定默认 governor。如果注册失败常见原因是 type 重名、ops 里缺 get_temp、或者 trips 数量超限。这里有个细节trip point 的初始化方式在不同版本差异很大。老版本通过ops-get_trip_temp等回调动态获取新版本倾向于在注册时直接传入 trip 数组。新方式的好处是 trip 信息静态化便于 sysfs 展示和用户空间读取也简化了 governor 的逻辑。如果你在移植老代码这块要重点对照。3.3 cooling device 的注册cooling device 的注册走的是另一套接口struct thermal_cooling_device * thermal_cooling_device_register(const char *type, void *devdata, const struct thermal_cooling_device_ops *ops);ops里必须实现几个回调get_max_state返回最大降温状态。get_cur_state返回当前状态。set_cur_state设置状态。cpufreq cooling 是个特殊的存在它不走thermal_cooling_device_register而是通过cpufreq_cooling_register注册内部会创建对应的 cooling device。这是因为 cpufreq cooling 需要和 cpufreq 子系统深度交互获取频率表、计算功耗等。注册完 cooling device 后还需要把它和 thermal zone 绑定。绑定通过设备树或者显式调用thermal_zone_bind_cooling_device()完成。绑定时要指定 trip point 的索引和 cooling device 的状态范围这个范围决定了 governor 能把状态调到多大。3.4 设备树里的 thermal 描述现代内核里thermal zone 和 cooling device 的绑定关系大多写在设备树里。一个典型的描述长这样thermal-zones { cpu_thermal: cpu-thermal { polling-delay-passive 100; polling-delay 1000; thermal-sensors tsadc 0; trips { cpu_alert0: cpu-alert0 { temperature 70000; hysteresis 2000; type passive; }; cpu_crit: cpu-crit { temperature 105000; hysteresis 2000; type critical; }; }; cooling-maps { map0 { trip cpu_alert0; cooling-device cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT; }; }; }; };几个关键点polling-delay和polling-delay-passive单位是毫秒。temperature和hysteresis单位是毫摄氏度。70000 就是 70 度。hysteresis是迟滞防止温度在阈值附近抖动导致频繁触发。比如阈值 70 度迟滞 2 度那么温度要降到 68 度以下才认为退出该 trip。cooling-device里的两个THERMAL_NO_LIMIT分别表示 cooling state 的最小值和最大值用 NO_LIMIT 表示不限制。迟滞这个参数很多人会忽略但它很重要。没有迟滞温度在阈值附近波动时governor 会反复触发和解除降温导致频率来回跳用户体验很差。迟滞设多少要看热惯性和控制精度一般 1 到 5 度。4. governor 工作机制与降温策略实现4.1 step_wise governor 的决策逻辑step_wise 是最经典的 governor逻辑不复杂但很实用。它的核心思路是每次温度更新时根据当前温度和 trip point 的关系决定是增加还是减少降温状态。具体逻辑可以概括成找到当前温度所处的 trip point 区间。如果温度在上升且超过了某个 trip增加对应 cooling device 的状态。如果温度在下降且低于某个 trip 减去迟滞减少状态。每次只调整一步避免过冲。为什么每次只调一步因为温度变化有惯性一次调太多容易过冲导致温度降得太低然后又得升回来形成震荡。逐步调整虽然响应慢一点但更稳定。这是控制理论里很常见的做法类似 PID 控制里的积分项逐步累积。step_wise 的一个局限是它不考虑 cooling device 的实际降温能力。它假设每个 state 的降温效果差不多但实际上 CPU 从 2GHz 降到 1.8GHz 和从 1GHz 降到 800MHz 的降温效果可能差很多。这就是 power_allocator 要解决的问题。4.2 power_allocator 的功耗预算模型power_allocator 是给移动设备设计的 governor它的核心思想是给 thermal zone 分配一个功耗预算然后把这个预算按需分配给各个 cooling device。它的工作流程大致是根据当前温度和目标温度计算需要的降温幅度。根据 CPU 的功耗模型在设备树里通过dynamic-power-coefficient等参数描述把降温幅度转换成功耗预算。把功耗预算分配给各个 cooling device通常是按比例分配。每个 cooling device 根据自己的功耗模型把预算转换成具体的频率或状态。这套机制比 step_wise 精细得多但代价是需要准确的功耗模型。如果模型不准可能出现降温不足或者过度降温。功耗模型通常来自芯片厂商的实测数据写在设备树或者驱动里。power_allocator 还有个特点是它支持 PID 控制。通过sustainable-power、k_po、k_pu、k_i等参数可以调节控制的激进程度。这些参数调起来有门槛一般用默认值特殊场景才需要动。4.3 governor 的注册与切换governor 的注册通过thermal_governor_register()完成。每个 governor 实现一组回调struct thermal_governor { char name[THERMAL_NAME_LENGTH]; int (*bind_to_tz)(struct thermal_zone_device *tz); void (*unbind_from_tz)(struct thermal_zone_device *tz); int (*throttle)(struct thermal_zone_device *tz, int trip); void (*update_tz)(struct thermal_zone_device *tz, enum thermal_notify_event event); struct list_head governor_list; };其中throttle是核心每次温度更新触发 governor 时都会调用它。bind_to_tz在 governor 绑定到 zone 时调用用于初始化。切换 governor 可以通过 sysfs 的policy属性echo power_allocator /sys/class/thermal/thermal_zone0/policy但要注意不是所有 governor 都能绑定到所有 zone。比如 power_allocator 需要 zone 有功耗模型信息没有的话绑定会失败。切换前最好先看下/sys/class/thermal/thermal_zone0/available_policies里有哪些可用。4.4 温度更新与 governor 触发链路温度更新的触发链路是理解 thermal framework 运行时的关键。整个链路大致是轮询或中断触发poll_queue延迟工作到期或者传感器中断上报。读取温度调用ops-get_temp获取当前温度。更新 zone 状态更新temperature、last_temperature等字段。检查 trip point遍历所有 trip看是否跨越了阈值。调用 governor如果跨越了 trip调用governor-throttle()。governor 决策governor 决定调整哪些 cooling device 的状态。执行降温调用 cooling device 的set_cur_state。通知用户空间通过 netlink 或 sysfs 通知温度变化事件。这条链路里第 4 步的 trip 检查有个细节不是每次温度更新都会触发 governor。只有当温度跨越了某个 trip 的阈值考虑迟滞时才会触发。这样避免了温度小幅波动时频繁调用 governor减少开销。5. 实操调试与常见问题排查5.1 sysfs 接口速查调试 thermal 问题sysfs 是最直接的入口。常用的路径和含义路径含义/sys/class/thermal/thermal_zone*/typezone 名字/sys/class/thermal/thermal_zone*/temp当前温度毫摄氏度/sys/class/thermal/thermal_zone*/mode工作模式enabled/disabled/sys/class/thermal/thermal_zone*/policy当前 governor/sys/class/thermal/thermal_zone*/trip_point_*_temp各 trip 的温度阈值/sys/class/thermal/thermal_zone*/trip_point_*_type各 trip 的类型/sys/class/thermal/cooling_device*/typecooling device 类型/sys/class/thermal/cooling_device*/cur_state当前降温状态/sys/class/thermal/cooling_device*/max_state最大降温状态看温度直接cat temp单位是毫摄氏度比如 45000 就是 45 度。看 cooling device 当前状态cat cur_state如果一直是 0说明没触发降温如果频繁变化说明温控在反复调整。5.2 常见问题与排查思路问题一温度读不到temp 显示 0 或者报错。先确认 zone 是否 enabledcat mode看是不是 disabled。然后确认传感器驱动是否正常加载dmesg | grep thermal看有没有注册失败的日志。如果传感器是 I2C 或 SPI 接口的还要确认总线通信正常。问题二温度到了阈值但不降温。按链路排查先看 trip point 配置对不对cat trip_point_0_temp和cat trip_point_0_type。然后看 cooling device 有没有绑定ls /sys/class/thermal/thermal_zone0/下有没有cdev*目录。再看 governor 是不是正常工作cat policy确认 governor 类型。最后看 cooling device 的cur_state有没有变化如果 governor 调了但 state 没变可能是 cooling device 的set_cur_state实现有问题。问题三降温太激进性能掉得厉害。这通常是 trip point 设得太低或者 governor 太激进。检查 trip 温度是不是合理比如 CPU 的 passive trip 一般设在 70 到 85 度之间设到 50 度就太低了。如果是 power_allocator检查sustainable-power是不是设得太小。问题四温度在阈值附近反复震荡。这是迟滞没设或者设得太小。检查 trip 的hysteresis值建议至少 10001 度热惯性小的设备建议 2000 到 5000。另外 polling_delay 太小也会加剧震荡适当增大轮询间隔。问题五多个 zone 互相干扰。比如 CPU zone 降频后GPU zone 温度也降了导致 GPU zone 解除降温然后 CPU 又热起来。这种耦合问题在多 zone 系统里很常见。解决办法是合理设置各 zone 的 trip point避免阈值太接近或者用 fair_share governor 做协同。5.3 调试实战一次降频问题的定位回到开头朋友那个问题。设备跑分腰斩日志里有 thermal 打印。排查步骤cat /sys/class/thermal/thermal_zone*/type找到 CPU 相关的 zone。cat temp看当前温度发现 75 度。cat trip_point_*_temp看 trip 配置发现 passive trip 设在 70 度。cat policy看 governor是 step_wise。cat /sys/class/thermal/cooling_device*/cur_state看 cooling device 状态发现 CPU cooling 的 state 是 3。结论温度超过 70 度触发 passive 降温step_wise 把 CPU 降频到 state 3导致性能下降。问题根源是 trip point 设得太低70 度对这颗芯片来说还没到需要大幅降频的程度。把 passive trip 调到 85 度问题解决。这个案例说明thermal 问题的定位关键是顺着 sysfs 把温度、trip、governor、cooling state 这条链路走一遍大部分问题都能定位到具体环节。5.4 几个容易忽略的坑坑一polling_delay 设成 0 但传感器不支持中断。有些驱动把 polling_delay 设成 0 表示走中断但传感器实际不支持中断结果温度永远不更新。这种情况要么改成非 0 走轮询要么在驱动里实现中断上报。坑二cooling device 的 state 范围没设对。绑定时如果 state 范围设错governor 可能调到超出实际能力的状态导致set_cur_state失败或者行为异常。绑定时要确认 cooling device 的max_state和绑定范围一致。坑三设备树里 trip 顺序和驱动里索引对不上。设备树里 trip 的顺序决定了索引驱动里引用 trip 时用的是索引。如果顺序改了但驱动没同步改就会绑错 trip。改设备树时一定要同步检查驱动。坑四governor 切换后没生效。切换 governor 后有些状态不会立即重置。比如从 step_wise 切到 power_allocator之前 step_wise 设置的 cooling state 可能还保留着。切换后最好手动确认一下 cooling state 是否合理。坑五多线程访问 sysfs 的竞态。用户空间程序频繁读写 sysfs 的 temp 和 cur_state 时可能和内核的轮询产生竞态。虽然内核有锁保护但高频读写仍可能影响性能。调试时避免写脚本疯狂轮询。6. 从架构到落地thermal framework 的扩展与演进6.1 如何新增一个 thermal zone实际项目里经常需要新增自定义的 thermal zone比如监控某个外设的温度。步骤大致是在设备树里定义 zone 节点指定传感器、trip point、cooling map。在驱动里实现thermal_zone_device_ops至少实现get_temp。调用devm_thermal_zone_of_sensor_register()注册 zone。确认 sysfs 里能看到新 zone温度能正常读取。用devm_前缀的接口好处是资源自动管理驱动卸载时自动清理不用手动 unregister。新代码建议都用 devm 版本。6.2 如何新增一个 cooling device新增 cooling device 的场景也不少比如控制一个自定义的散热风扇。步骤是实现thermal_cooling_device_ops包括get_max_state、get_cur_state、set_cur_state。调用devm_thermal_cooling_device_register()注册。在设备树的 cooling map 里引用这个 cooling device。确认 sysfs 里能看到且 governor 能正常调节。set_cur_state的实现要注意不能阻塞太久因为它在温度更新的上下文里被调用。如果控制风扇需要 I2C 通信要确保通信不会卡住。必要时可以用工作队列异步处理。6.3 thermal framework 的演进方向从近几个内核版本看thermal framework 的演进有几个趋势trip point 静态化从动态回调转向注册时传入静态数组简化逻辑便于 sysfs 展示。governor 重构governor 接口逐步统一update_tz回调的引入让 governor 能更细粒度地响应事件。netlink 通知增强用户空间能通过 netlink 订阅更丰富的 thermal 事件便于做精细化的功耗管理。和功耗管理框架融合thermal 和 EAS、cpufreq、devfreq 的交互越来越紧密不再是孤立的温控模块。这些演进对开发者的影响是接口在变但分层思想不变。理解了 zone、governor、cooling device 这三层的职责划分不管接口怎么改都能快速上手新版本。6.4 性能与功耗的平衡经验最后分享几点实际项目里的经验。thermal 调优本质是在性能和温度之间找平衡点没有万能参数只有适合具体场景的参数。第一trip point 的设置要参考芯片手册的热设计功耗和结温上限留足安全余量。一般 passive trip 设在结温上限往下 15 到 20 度critical trip 设在结温上限往下 5 度。第二governor 的选择要看场景。移动设备追求续航和温控精度用 power_allocator服务器追求稳定用 step_wise风扇控制用 bang_bang 就够。第三迟滞和轮询间隔要配合调。迟滞大、轮询慢温控稳但响应慢迟滞小、轮询快响应快但容易震荡。一般迟滞 2 度、轮询 100 到 500ms 是个不错的起点。第四多 zone 系统要注意耦合。CPU 和 GPU 的 trip point 不要设得太接近否则一个降频会连带另一个解除降温形成振荡。必要时用 fair_share 做协同。第五调试时善用 sysfs 和 trace。/sys/kernel/debug/thermal/下有更详细的调试信息ftrace 的 thermal 事件能追踪 governor 的每次决策。这些工具比看日志高效得多。thermal framework 这套东西刚接触时容易被一堆概念绕晕但把分层思想理清楚把 zone、governor、cooling device 三者的关系搞明白剩下的就是查接口、调参数、看日志。真正难的不是理解架构而是在具体硬件上把参数调到既不影响性能又能压住温度。这个平衡点每个项目都不一样只能靠实测和迭代。