
刚开始接触 thermal framework 那阵子我也一度被thermal_zone、cooling_device、trip_point、governor这些词绕得头晕。后来啃完整套源码、又亲手调过几块板子的散热策略才意识到这个框架其实没有想象中复杂它本质上是把“温度采集、阈值判断、降温执行”这三件事标准化然后由内核统一调度。这篇文章就顺着这条主线把 Linux 内核功耗子系统里的 thermal framework 通用架构梳理一遍既是给这个系列做一次阶段总结也方便后来的人少走点弯路。如果你正在做嵌入式、ARM SoC 或者服务器端的功耗调优平时需要面对“发热-降频-风扇-关机保护”这类问题这篇内容会帮你把整个框架的骨架搭起来知道代码该从哪看、参数该往哪调、异常该怎么查。1. 先搞清楚 thermal framework 到底要解决什么问题1.1 从一场“过热事故”说起我印象很深的一次经历一块四核 ARM 开发板CPU 满载压测不到三分钟表面温度直线上升到接近 90 度然后整板突然重启。第一反应自然是“散热没做好”但真正的问题并不是风扇该不该加而是系统里没有任何机制能在到达硬件极限之前采取动作。《Linux 内核功耗子系统九thermal framework 通用架构梳理》——其实整个 thermal 框架要解决的核心问题就是让系统在“硬件烧坏之前”有一个可预测、可配置、可分级的响应链条。这里想强调一个容易被忽略的观念热管理的目标不是“不死机”而是在允许的功耗/温度范围内尽可能维持性能和用户体验。只靠硬件层面的过温断电保护那已经是最后的兜底手段了相当于房子起火之后才报警。而框架要做的事是提前干预温度高了先限频还高再降到更低档位再不行才考虑关机保护。这一套动作必须是有序的、可回退的否则就会引发性能抖动的连锁反应。很多人会问既然各家 SoC 的传感器、风扇、调频策略都不一样为什么一定要搞一个通用框架直接让每个模块各自处理温度不行吗答案很简单没有统一框架驱动各写各的阈值、延迟、降温方式全是散装的系统行为就会变成一团乱麻——某个驱动认为 85 度就该降频另一个驱动在 80 度就开始疯狂拉风扇两者互相干扰出了问题还很难复现。1.2 通用框架带来的三个直接好处第一个是解耦。温度传感器只负责报数不管你背后是 NTC 热敏电阻、芯片内部数字传感器还是 PMIC 上报冷却设备只负责执行不管是调 CPU 频率、调 GPU 频率、开关风扇还是控制加热丝。传感器和冷却设备之间不需要互相知道对方的存在由框架中间的策略层来牵线。第二个是可配置。trip point温度阈值和 cooling map阈值到冷却设备的映射可以从设备树、ACPI 或 sysfs 中配置改策略的时候不需要重新编译驱动。我做过的项目里量产阶段和开发阶段用的散热策略往往不一样就是因为阈值和滞后参数都可以动态调。第三个是生态复用。新来的芯片工程师只需要实现get_temp和几个回调剩下所有通用逻辑——轮询、趋势判断、与 cpufreq/devfreq 的联动——框架已经铺好路了。与其叫“通用架构”不如说它是一个行业标准模板大家照着填空就行。这些好处合在一起就是 thermal framework 存在的意义。但光知道“为什么要统一”还不够紧接着的问题就是框架内部到底怎么组织这些实体2. 通用架构拆解zone、trip、cooling device 三要素2.1 三个核心概念把复杂系统拆成积木整个 thermal framework 的底层模型非常简单只有三类角色thermal zone热区、trip point触发点、cooling device冷却设备。thermal zone 可以理解为一个“温度观察区域”。一个 SoC 上可能有 CPU 热区、GPU 热区、电池热区各区域的传感器位置不同、热特性不同、降温手段也不同。每个 thermal zone 都对应内核里一个thermal_zone_device结构体它负责维护当前温度、轮询周期、trip 点列表以及绑定了哪些冷却设备。trip point 则是“温度阈值 行为类型”的组合。阈值就是多少度触发动作行为类型有active、passive、hot、critical四类。通俗点说active 一般对应主动散热比如开风扇passive 对应被动降频比如限制 CPU 最大频率hot 通常做加强散热或准备进入安全状态critical 则是救命阈值到了就必须触发系统停机或者硬件急停保护。每个 trip 还可以配置 hysteresis滞后量防止温度在阈值附近反复横跳。cooling device 是实际的降温执行器。它对外暴露一组状态比如 0 到 N 档cur_state代表当前档位max_state代表最大档位。框架只需要把 trip 和冷却设备绑定在一个 cooling map 里温度越线时按策略上调档位即可。2.2 从“读到温度”到“执行降温”的完整链路忽略驱动层的细节一次热管理动作的完整链路大致是这样的内核定时器按polling_delay触发一次 thermal zone 更新或者硬件中断/传感器阈值中断提前唤醒。框架调用该 zone 的get_temp回调拿到以毫摄氏度为单位的当前温度。核心代码把当前温度与 zone 内的所有 trip 点逐一比较找出需要处理的 trip。通过设备树/ACPI 解析好的 cooling map找到这个 trip 关联的冷却设备集合。调用 thermal governor温度治理策略决定这些冷却设备该跳到哪一档。冷却设备的set_cur_state被调用实际执行降频、开风扇等动作。这个流程看起来简单但里面有一个关键设计框架并不仅是“超过阈值就无脑拉满”。以step_wise策略为例它会同时参考温度趋势——如果温度还在上升一级一级往上调如果温度开始回落就推迟一步再往下降避免“冲过头”。这个细节决定了你的系统是平滑还是来回震荡。2.3 governor 选型别默认用到底要按场景挑governor 是 thermal framework 的决策中心内核默认提供了几种最常见的四类区别如下策略名核心思路典型场景注意事项step_wise按温度趋势逐级升降档位CPU/GPU 被动降频反应平滑但降温速度可能偏慢bang_bang过阈值开、低阈值关只有两态风扇、加热器滞后要设好否则容易振荡power_allocator基于 PID 的功率分配控制带功率模型的 SoC 功耗限制需要调参k_p/k_i 不合适会失控user_space把决策权交给用户态进程策略复杂的场景如笔记本依赖用户态守护进程内核侧基本不干预选型逻辑其实不复杂如果你的冷却设备只有开关两档比如风扇用bang_bang最直接如果是 CPU 频率这种连续可分级的场景step_wise是常用起点如果你对功耗有精确预算、愿意投入时间调 PID 参数power_allocator能榨出更好的性能代价是系统复杂度明显上来。还有一点实操经验别在项目里反复切换 governor 而逃避调参。实际中发现很多人一上来就换成power_allocator结果 K 值没调好温度不降反升。先跑默认step_wise把 trip 和 cooling map 确认好再考虑更精细的策略是更稳的顺序。3. 关键实现路径从注册流程到实际控制3.1 热区注册传感器驱动的“入场券”在内核源码里你只需要关注drivers/thermal/目录。一个 sensor driver 要把自己暴露成 thermal zone核心动作是调用thermal_zone_device_register或者带设备树支持的变体传入一组操作函数。这些操作函数是框架和硬件之间的薄接口最重要的就是get_temp。get_temp的语义很直接调用它就能得到这个 zone 的当前温度。但真正可靠的驱动通常不会只把寄存器值直接读出来就完事因为芯片内部传感器的返回值可能有非线性偏差。常见做法是会结合 SoC 出厂校准参数做一次 headroom 修正。比如某款芯片手册上说传感器读值在高温段会偏低 8 度你没修正那么你配置的 95 度 critical trip 在真实 103 度时才会触发风险很大。注册时还要指定polling_delay主动轮询间隔和passive_delay被动模式下的轮询间隔。这两个值决定了系统对温度变化的敏感度但也要匹配传感器的采样速度。我见过有驱动把 polling_delay 设成 50ms但传感器自己 200ms 才能完成一次转换等于白忙活反而拉高 CPU 占用。3.2 冷却设备注册把“降温手段”标准化冷却设备的结构和 thermal zone 是对称的。驱动实现get_max_state、get_cur_state、set_cur_state这三个回调然后调用注册接口把自身挂到框架上。对 CPU 调频来说这个冷却设备往往就是 cpufreq 驱动的封装最高档对应最高频率某个中间档对应降频后的频率。实际项目里最常用到的两类冷却设备是cpufreqCPU 调频和devfreq设备频率比如 GPU/DDR 调频另外还有风扇驱动通过 pwm 实现多档位。每个冷却设备会暴露一个state框架对它只有一个请求把档位调到 target state。至于这个状态具体对应多少频率、多少占空比框架不关心。这里值得注意一个容易出错的地方多核 CPU 的冷却设备在现代内核里通常已经细化为 per-policy 粒度而不是整个 CPU cluster 一刀切。绑定冷却设备到 trip 时最好先确认你要限制的是哪一个 policy、哪一个 cluster。曾在某 ARM 平台见过冷却设备绑错了 CPU 的 cpufreq policy结果温度过高的核心没降频旁边凉快的核心反而被限制了完全起不到保护作用。3.3 设备树里的“合同”trips、cooling map、hysteresis对嵌入式平台来说thermal 关系主要靠设备树描述。看一段最常见的模板你就知道整体长什么样thermal-zones { cpu0-thermal { polling-delay-passive 250; polling-delay 1000; trips { cpu_alert0: cpu-alert0 { temperature 85000; /* 毫摄氏度85度 */ hysteresis 5000; /* 5度滞后 */ type passive; }; cpu_crit0: cpu-crit0 { temperature 105000; type critical; }; }; cooling-maps { map0 { trip cpu_alert0; cooling-device cpu0 0 2; /* 最小档0最大档2 */ }; }; }; };cooling-device里的cpu0 0 2是新手最容易看晕的地方它表示把 CPU 这个冷却设备绑定到当前 trip允许使用的最小档位是 0、最大档位是 2。为什么要限定范围因为同一个冷却设备可能被多个 trip 共享比如 85 度的 trip 只允许限制到轻降频档95 度的 trip 才允许压到更低档。在 DT 里这么写就等于把每个 trip 的“降温权力”边界划清楚了。滞后量hysteresis常被忽略但它直接关系到系统稳不稳定。设想一个 85 度触发的 trip降温到 84.5 度就解除了限制结果 CPU 马上又升回 85 度再触发再解除形成振荡。设置 5 度的滞后意味着要降到 80 度以下才解除限制给系统留出缓冲。3.4 sysfs 出口用户态怎么观察和控制如果你只想验证框架是否正常工作完全可以不看源码直接翻 sysfs。每个 thermal zone 在/sys/class/thermal/thermal_zoneN/下会暴露一批节点temp当前温度单位毫摄氏度typezone 类型名policy当前使用的 governor可写trip_point_X_temp第 X 个 trip 的阈值trip_point_X_type第 X 个 trip 的类型modeenabled/disabled可以整体关闭该 zone 的热管理冷却设备则在/sys/class/thermal/cooling_deviceN/下type冷却设备类型max_state最大档位cur_state当前档位可写实操中最常用的验证动作就是一边用stress或mprime压 CPU一边循环查看temp与各个cooling_device/cur_state的变化。你会在温度跨过 trip 后看到cur_state自动从 0 往上跳这就是整个框架在工作的最直接证据。如果温度到了阈值但cur_state纹丝不动优先排查 cooling map 有没有写错其次再看 governor 是否正确生效。4. 我踩过的坑与排查实录4.1 trip 数量与注册掩码不匹配这是我最常见到的低级 bug但排查起来也会让人挠头。早期版本的thermal_zone_device_register需要传入三个参数trips 数量、温度掩码和冷却掩码。你说“这个 zone 有 3 个 trip”但实际只注册了 1 个后面的 trip 索引会越界或直接静默失效。更隐蔽的问题是掩码设置错误导致某个 trip 永远不被处理。框架用位掩码表示哪些 trip 点支持温度上报、哪些 trip 点会主动触发冷却动作如果你漏掉了对应位温度变化到了阈值也白搭框架压根不认为这个 trip 有效。排查办法很简单读一遍 sysfs 里trip_point_*节点是否齐全如果缺失基本上就是注册参数写错了。我的经验是视觉检查注册参数之外一定用 sysfs 把所有 trip 点列出来和预期对比一遍不要假设代码写对了就真的对。4.2 传感器读数异常为 0、跳变、滞后严重传感器驱动最容易出问题的三个症状是读数为 0、读数跳变、读数响应明显滞后。读数为 0 通常意味着寄存器位域解析错误或者传感器没上电跳变往往是采样通道切换噪声需要驱动里做滤波滞后严重则多半是传感器位于导热路径远端和真正发热源的温差被低估了。遇到传感器明显不准的板子优先怀疑的其实是校准参数。很多 sensor driver 在热量给定时直接返回原始值由 device tree 里的属性加入 offset 或 slope 修正。你要用实际测得的外部热电偶温度去反推修正系数而不是从芯片手册生搬硬套。软件层还有个补救手段set_trips回调如果可用框架会为传感器设置硬件中断上下限让传感器在温度越界时主动上报而不是干等轮询。这样既能提高响应速度也能降低轮询频率。不过要注意有些 SoC 的传感器中断在低功耗模式下不可用deep sleep 唤醒后重新注册一套 trip 是常见的坑点。4.3 冷却设备“打架”与循环依赖真实的多热区系统里同一个冷却设备往往被多个 thermal zone 共享。典型例子CPU 热区和电池热区都想控制 CPU 频率或者 CPU 热区和外壳温控都想控制风扇。框架本身允许这种共享但协调逻辑必须想清楚——如果两个 zone 都调同一个风扇A 说升档、B 说降档最终以谁为准这种情况框架并没有智能仲裁本质上是最后更新冷却设备状态的人获胜。所以架构设计初期就要约定优先级哪些 zone 对某冷却设备拥有“硬控制权”哪些 zone 只能施加范围限制。实用做法是在 cooling map 中给不同 zone 限定不同的冷却档位范围比如电池热区最多把风扇拉到 3 档CPU 热区可以拉到 5 档。还有一个很多人忽略的是 GPIO/时钟依赖。曾见过一个风扇驱动它的电源使能脚复用自某个 GPIO 控制器而那个控制器又被另一个 thermal zone 的降频策略关掉了——结果系统一过热不但没降温风扇反而断电温度直接冲顶。这就是典型的循环依赖排查起来极其痛苦。建议做架构审查时把所有冷却设备的依赖树画出来确认没有互相牵制的关系存在。4.4 调试三板斧仿真温度、sysfs 监控、trace 事件调 thermal 策略最怕的就是“复现不出过热场景”。有些板子传感器位置设计不合理跑满载也到不了 trip 阈值这时候就轮到仿真温度上场。内核开启CONFIG_THERMAL_EMULATION后sysfs 会出现emul_temp节点往里面写一个温度值框架就会把它当作实际温度来走完整流程。这是测试 governor 和 cooling map 最强的工具可以让你不依赖真实负载直接验证 60 度、85 度、105 度下的系统行为。仿真有个细节需要注意写入仿真温度后别忘了测完清掉并恢复真实传感器读数否则会掩盖真实过热问题。我自己就吃过亏测试完遗忘了emul_temp之后怎么压测都不触发保护排查半天才发现是仿真值一直压在 30 度。看重实时链路的话可以直接用 ftrace 里的 thermal 事件比如 thermal temperature 更新、thermal zone 进入 trip 等事件点。抓事件比看 dmesg 靠谱因为热管理更新频率远高于打印频率而且打印本身会引入额外耗时。配合cur_state曲线基本能做到分钟级定位问题。5. 落地建议源码入口与上线 checklist5.1 源码该从哪几行看起如果你是第一次准备深入 thermal 源码建议按下面的顺序读思路会顺畅很多include/linux/thermal.h先看结构体定义理解thermal_zone_device、thermal_cooling_device、thermal_trip这些核心类型。drivers/thermal/thermal_core.c看注册、注销、更新主流程理解 zone 是怎么被框架驱动的。drivers/thermal/thermal_sysfs.c看用户态节点是怎么映射到内部数据的这能帮你理解 sysfs 里每个文件的行为。drivers/thermal/gov_step_wise.c挑一个典型 governor 细读理解框架如何把决策下发给冷却设备。再回到某个真实 SoC 驱动比如imx_thermal、rcar_thermal或exynos_thermal看硬件驱动如何实现这些接口。这个阅读顺序的精髓在于先看抽象层再看具体实现最后回头看接口定义避免一上来就陷进某个 SoC 的寄存器细节里出不来。5.2 上线前我一般会过一遍这六项检查传感器校准get_temp读出的值与外部实测比对确认 offset/slope 正确。polling_delay 匹配轮询周期不短于传感器单次采样周期。trip 点覆盖每个 zone 都至少配了 passive 和 critical 两级不要把 critical 当成唯一防线。滞后没有遗漏触发阈值和恢复阈值之间留了合理缓冲。cooling map 范围收敛同一个冷却设备被多 zone 共享时优先级和档位范围都已明确。仿真验证过所有阈值用emul_temp把每个 trip 都实际触发了一遍确认冷却设备档位变化符合预期。这六项看起来基础但几乎覆盖了我接触到的绝大多数线上事故。一次完整的热管理调优不是简单看几个 sysfs 数字而是把传感器可信度、策略合理性和执行链路可靠性全部验证到位。thermal framework 的通用架构之所以能成为 Linux 功耗子系统的基石就是因为它把混乱的散热问题收敛成了清晰的三要素模型剩下的一切都是在跟真实的物理世界打交道。