1. 从一次温控翻车说起thermal framework 到底管什么前阵子帮朋友排查一块嵌入式板子现象很典型设备跑高负载任务不到三分钟CPU 频率就被死死压在低位性能直接腰斩但外壳摸上去并不烫。第一反应是散热没做好换了导热硅脂、加了风扇问题照旧。后来把/sys/class/thermal/目录下的信息拉出来一看才发现是 thermal zone 的温度读数被某个错误的 trip point 提前触发了降频跟物理散热压根没关系。这个案例几乎是我接触 Linux 功耗子系统以来反复遇到的同一类问题thermal framework 在后台默默工作一旦配置或理解有偏差表现出的症状却完全不像温度问题。它可能表现为性能骤降、设备莫名重启、充电变慢、风扇狂转甚至系统卡死。而大多数人排查时根本不会第一时间想到去翻 thermal 相关的节点。所以这篇想做的事情很明确把 Linux 内核里 thermal framework 的通用架构从头到尾梳理一遍。不是罗列 API而是讲清楚它由哪些部分组成、每部分承担什么职责、数据怎么流动、驱动开发者需要填哪些坑、系统调优时该盯哪些节点。关键词里的Linux 内核、thermal framework、功耗子系统、通用架构正好对应本文的四个核心视角内核视角、框架视角、功耗视角、架构视角。适合谁看如果你在做嵌入式 Linux 驱动、BSP 移植、功耗调优或者单纯想搞明白/sys/class/thermal下面那一堆文件到底代表什么这篇应该能帮你把散落的知识点串成一条线。如果你只是偶尔用sensors命令看看温度那也能从里面理解到读数背后的机制知道为什么有时候读数会骗人。需要提前说明的是thermal framework 本身是一个持续演进的子系统不同内核版本在细节上有差异。本文梳理的是通用架构层面的东西具体到某个版本的行为还是要以你手上的内核源码为准。我下面提到的结构、流程、节点都是基于常见实践和主流版本的共性总结遇到版本差异我会尽量点出来。2. thermal framework 的四层骨架从传感器到执行器要理解 thermal framework最有效的方式不是背 API而是先建立一张数据流地图。整个框架可以拆成四个层次从上到下依次是传感器层、thermal zone 抽象层、governor 决策层、cooling device 执行层。这四层之间通过内核内部的注册与回调机制连接形成一个闭环。2.1 传感器层温度是从哪里读出来的最底层是温度传感器。它可能来自 SoC 内部的温度传感模块比如很多 ARM 平台集成的 TSADC、外部的 I2C/SPI 温度芯片如常见的 LM75、TMP102 系列、或者通过其他子系统间接获取的温度源。这一层的核心任务是提供一个可以被内核调用的温度读取接口。在设备树里传感器通常以独立节点的形式描述然后被 thermal zone 引用。比如一个典型的 SoC 内部传感器节点会声明寄存器地址、时钟、校准参数等。驱动加载后会向 thermal framework 注册一个thermal_zone_device并绑定一个get_temp回调。框架每次需要温度时就调用这个回调去实际读取硬件。这里有个容易被忽略的点温度读取是有开销的。如果传感器挂在慢速总线上频繁读取会拖累系统。所以框架内部有轮询间隔polling delay的概念不是每次查询都真的去读硬件。理解这一点对后面分析为什么温度变化有延迟很关键。2.2 thermal zone 抽象层把物理区域变成内核对象thermal zone 是整个框架的核心抽象。一个 thermal zone 代表一个需要被热管理的物理区域比如 CPU 集群、GPU、电池、外壳表面等。它把传感器、触发点trip point、冷却设备cooling device三者绑定在一起形成一个完整的管理单元。每个 thermal zone 在内核里对应一个thermal_zone_device结构注册后会出现在/sys/class/thermal/thermal_zoneN/目录下。这个目录里的文件就是用户空间观察和控制 thermal 行为的主要入口。我列几个最关键的文件/节点含义典型用途typethermal zone 的类型名区分是 CPU、GPU 还是电池temp当前温度毫摄氏度实时监控mode工作模式enabled/disabled临时关闭热管理policy当前使用的 governor查看/切换策略trip_point_N_type第 N 个触发点类型查看触发条件trip_point_N_temp第 N 个触发点温度查看/调整阈值cdevN_cur_state第 N 个冷却设备当前状态查看降频档位trip point 是 thermal zone 里最需要理解的概念。它本质上是温度阈值 触发动作的组合。常见的 trip 类型有passive被动降频、active主动散热比如开风扇、critical临界通常触发关机、hot过热警告。当温度越过某个 trip point框架就会通知 governor 去执行对应动作。2.3 governor 决策层温度越线之后谁来拍板governor 是 thermal framework 的大脑。它决定当温度达到某个 trip point 时具体该怎么调整冷却设备。内核里常见的 governor 有几种各自适用场景不同step_wise最常用也最直观。温度每越过一个档位就把冷却设备状态调整一档逐步加码或逐步回退。适合大多数嵌入式场景。power_allocator基于功耗预算做分配把有限的散热能力按权重分给各个 cooling device。适合多核、多设备协同的复杂平台。fair_share在多个 cooling device 之间公平分配降温任务。bang_bang最简单的开关式控制温度高了就全开低了就全关。容易震荡一般用于风扇这类设备。user_space把决策权交给用户空间内核只负责上报。适合需要精细自定义策略的场景。governor 的选择通过policy节点体现也可以在设备树或内核配置里指定。选错 governor 是很多降频行为诡异问题的根源。比如一个本该用 step_wise 的场景用了 bang_bang就会出现温度在阈值附近反复横跳、频率忽高忽低的现象。2.4 cooling device 执行层降频、关核、开风扇都归它管cooling device 是实际执行降温动作的部件。它可以是CPU 频率调节器通过 cpufreq 降低频率或电压。CPU 热插拔直接关掉部分核心。GPU 频率调节降低 GPU 工作频率。风扇控制器调整风扇转速。充电电流限制降低充电功率减少发热。设备节流限制某些外设的工作强度。每个 cooling device 注册时会声明自己的状态数量max_state和每个状态对应的实际效果。框架通过set_cur_state回调来调整它。状态值越大通常代表降温力度越强。这四层的关系可以这样理解传感器提供体温thermal zone 定义发烧标准governor 决定怎么退烧cooling device 负责实际吃药。任何一环出问题最终表现都是温度控制失效或性能异常。3. 设备树里的 thermal 描述绑定关系是怎么建立的搞清楚了四层骨架接下来要回答一个实操中绕不开的问题这些组件是怎么被组装到一起的答案主要藏在设备树Device Tree里。对于 ARM 等平台thermal 相关的绑定关系几乎全靠设备树描述理解这部分是 BSP 移植的基本功。3.1 thermal zone 节点的标准写法一个典型的 thermal zone 节点长这样以某 ARM 平台为例具体属性名以你的内核绑定文档为准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 100000; hysteresis 0; type critical; }; }; cooling-maps { map0 { trip cpu_alert0; cooling-device cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT; }; }; }; };这里面几个关键属性值得逐个拆解polling-delay-passive进入被动降温状态后的轮询间隔毫秒。温度已经越线了就得盯紧点所以这个值通常比正常轮询小。polling-delay正常状态下的轮询间隔。设太大反应迟钝设太小浪费 CPU。thermal-sensors引用具体的温度传感器节点可以引用多个。trips定义所有触发点每个包含温度、迟滞hysteresis、类型。cooling-maps把 trip point 和 cooling device 关联起来这是越线后找谁降温的映射表。3.2 hysteresis 这个参数为什么不能随便填hysteresis迟滞是我见过最容易被忽视、又最容易引发诡异现象的参数。它的作用是当温度回落到 trip point 以下时不是立刻解除降温而是要再低一个 hysteresis 值才解除。举个例子trip point 设在 70°Chysteresis 设 2°C。温度升到 70°C 触发降频之后温度降到 69°C 时不会立刻恢复要降到 68°C 以下才恢复。这样做的目的是防止温度在阈值附近反复穿越导致降频/恢复频繁抖动。如果 hysteresis 设成 0或者设得太小就会出现温度在 70°C 上下波动频率跟着反复横跳的现象。用户感受到的就是性能忽好忽坏非常难受。我一般建议 hysteresis 至少留 2°C 到 5°C 的余量具体看散热系统的响应速度。3.3 cooling-maps 的匹配逻辑与常见错误cooling-maps 是把 trip 和 cooling device 绑定的地方。每个 map 条目包含一个trip引用和一个cooling-device引用。cooling-device后面的两个参数是状态范围最小状态和最大状态THERMAL_NO_LIMIT表示不限制。这里常见的错误有几类第一类是引用了不存在的 cooling device。比如设备树里写了cpu0但 cpufreq 驱动没加载或者没注册成 cooling device结果就是 trip 触发了却没人执行降温温度继续飙升直到 critical 关机。第二类是状态范围填反了。有的驱动状态值越大降温越强有的相反。填错范围会导致降温动作方向错误越降越热。第三类是多个 trip 映射到同一个 cooling device 但状态范围重叠。这会让 governor 的决策变得混乱不知道该用哪个档位。排查这类问题最直接的办法是看/sys/class/thermal/thermal_zoneN/cdevN_cur_state的值有没有随温度变化。如果温度越线了但 cur_state 一直是 0基本可以确定映射没生效。4. 温度越线之后一次完整的触发链路拆解前面讲了静态结构这一节讲动态过程。当温度真的越过一个 trip point内核里到底发生了什么把这条链路走通排查问题时就能准确定位卡在哪一环。4.1 从轮询到 governor 被唤醒thermal zone 注册后框架会启动一个延迟工作队列delayed work来周期性读取温度。每次读取后框架会把当前温度和所有 trip point 比对判断是否有 trip 被穿越。这里有个细节框架不是简单地温度大于阈值就触发而是会记录上一次的 trip 状态只有发生状态变化从没触发到触发或从触发到解除时才通知 governor。这就是为什么 governor 不会每轮都被调用也是 hysteresis 能起作用的原因。一旦检测到 trip 状态变化框架会调用当前 governor 的throttle回调把 thermal zone、trip 信息传进去。governor 拿到这些信息后开始决策。4.2 step_wise 的决策过程以最常用的 step_wise 为例它的决策逻辑大致是这样的判断当前 trip 是升温触发还是降温解除。如果是升温触发找到这个 trip 关联的所有 cooling device把每个设备的状态往上调一档不超过 max_state。如果是降温解除把状态往下调一档不低于 0。如果温度继续升高越过更高的 trip重复上述过程继续加档。这个逐步加码的设计很符合直觉温度越高降温力度越大。但它也有个特点——反应是渐进的。如果散热系统响应慢而温度上升快可能在 governor 还没加到足够档位时温度就已经冲到 critical 了。所以 trip point 的档位设计要留足缓冲。4.3 cooling device 状态改变后发生了什么governor 决定调整某个 cooling device 的状态后框架会调用该设备的set_cur_state回调。对于 CPU 频率冷却设备来说这个回调最终会作用到 cpufreq 子系统限制 CPU 的最大频率。这里要注意一个耦合关系thermal 和 cpufreq 是两个独立子系统通过 cooling device 接口连接。thermal 只负责说现在需要降到第 3 档具体降到多少频率是 cpufreq 根据 cooling device 的状态映射决定的。这个映射关系在 cpufreq 驱动里定义不同平台不一样。所以当你看到温度越线了频率确实降了但降得不够时问题可能不在 thermal而在 cpufreq 的 cooling device 状态映射上。反过来频率降了但温度没降下来那可能是散热设计或 trip 档位的问题。4.4 critical trip 的特殊处理critical 类型的 trip 比较特殊。它不经过 governor 的常规决策流程而是直接触发内核的热关机机制。当温度达到 critical 阈值框架会调用thermal_zone_device_critical最终走 orderly_poweroff 或直接触发硬件关机。这个机制是最后一道防线正常情况下不应该被触发。如果你的设备经常因为 critical 关机说明前面的 passive/active 降温措施完全不够用需要重新审视散热设计或 trip 配置。把 critical 阈值设得很高来避免关机是掩耳盗铃硬件该坏还是会坏。5. 用户空间能看到什么sysfs 节点实战解读对大多数开发和运维人员来说跟 thermal framework 打交道最多的入口就是 sysfs。这一节把常用节点和它们的实际含义讲透方便你排查问题时快速定位。5.1 读懂 /sys/class/thermal 目录结构进入/sys/class/thermal/通常会看到两类目录thermal_zoneN和cooling_deviceN。前者是热区后者是冷却设备。每个目录下都有若干属性文件。一个实用的排查顺序是先看thermal_zoneN/type确认这个 zone 管的是哪个区域。看thermal_zoneN/temp确认当前温度读数是否合理。看thermal_zoneN/policy确认用的哪个 governor。看所有trip_point_N_type和trip_point_N_temp确认阈值配置。看cdevN_cur_state确认降温设备当前档位。对照cooling_deviceN/type和cooling_deviceN/max_state确认设备能力。这一套走下来基本能判断出 thermal 系统是否在正常工作。5.2 温度读数的单位陷阱temp节点的单位是毫摄氏度不是摄氏度。也就是说读到 45000 表示 45°C。这个单位在脚本处理时特别容易出错我见过不止一次有人写监控脚本时忘了除以 1000结果阈值判断完全错乱。同样trip_point_N_temp也是毫摄氏度。写自动化脚本时统一按毫摄氏度处理最后展示时再转换能避免很多低级错误。5.3 临时关闭热管理的正确姿势调试性能问题时有时候需要临时关掉热管理排除降频干扰。方法是往mode节点写disabledecho disabled /sys/class/thermal/thermal_zone0/mode但这里必须强调这只是调试手段绝对不能作为长期方案。关掉热管理意味着硬件失去了保护高负载下可能真的烧毁。我一般只在实验室环境、短时间验证时用验证完立刻恢复echo enabled /sys/class/thermal/thermal_zone0/mode5.4 用脚本做温度趋势监控单次读温度意义不大看趋势才有价值。下面这个简单脚本可以周期性打印所有 thermal zone 的温度方便观察变化#!/bin/bash while true; do for zone in /sys/class/thermal/thermal_zone*; do type$(cat $zone/type 2/dev/null) temp$(cat $zone/temp 2/dev/null) if [ -n $temp ]; then printf %-20s %d.%03d C\n $type $((temp/1000)) $((temp%1000)) fi done echo --- sleep 2 done跑起来之后配合压力测试就能直观看到温度爬升和降频触发的对应关系。这比盯着单个数字有用得多。6. 移植和调优时最容易踩的几个坑前面偏原理和结构这一节讲实操。下面这些坑有的是我自己踩过的有的是帮别人排查时反复见到的都是文档里不太会写、但实际项目中很要命的东西。6.1 传感器读数和真实温度对不上最常见的问题是温度读数明显偏离实际。可能的原因有几个校准参数没配、传感器采样点离热源太远、或者读的是芯片结温而不是环境温度。芯片结温junction temperature通常比外壳温度高不少这是正常的。但如果结温读数高到离谱比如待机就 90°C那就要检查校准数据是否正确写入。很多 SoC 的传感器需要出厂校准值这些值存在 efuse 或 OTP 里驱动要负责读出来并参与计算。如果校准没做读数就是错的。排查方法在已知环境温度下比如室温 25°C让设备待机一段时间看读数是否接近合理范围。如果偏差超过 10°C基本可以确定校准有问题。6.2 trip point 档位设计不合理导致性能断崖有些平台的 trip 档位设计得很粗比如只有 70°C 和 100°C 两个点。结果就是温度一到 70°C降频力度直接拉满性能断崖式下跌。用户感受就是用一会儿就卡。合理的做法是设置多个渐进档位比如 65°C、75°C、85°C 各一档每档降温力度递增。这样温度上升时性能是平滑下降的而不是突然掉下去。当然档位也不是越多越好太多会增加 governor 的决策开销一般 3 到 5 档比较合适。6.3 多 thermal zone 之间的相互干扰复杂平台往往有多个 thermal zone比如 CPU、GPU、电池各一个。如果它们的 cooling device 有重叠比如都控制 CPU 频率就可能出现相互干扰CPU zone 降了频GPU zone 觉得温度降了又恢复结果 CPU 温度又上去。处理这类问题要么在设计上让各 zone 的 cooling device 尽量不重叠要么用 power_allocator 这类能统一协调的 governor。用 step_wise 管多个重叠 zone很容易出现按下葫芦浮起瓢的情况。6.4 轮询间隔设置的两难polling-delay设大了温度变化反应慢可能错过最佳降温时机设小了频繁读传感器增加系统开销尤其在慢速总线上更明显。我的经验值是正常轮询 500ms 到 1000ms被动降温状态下 100ms 到 200ms。这个范围在大多数平台上能兼顾响应速度和开销。如果传感器读取特别慢比如 I2C 上挂了很多设备可以适当放宽但要相应地把 trip 档位设计得更保守留足缓冲。6.5 内核版本差异带来的行为变化thermal framework 在不同内核版本间有过不少调整比如 governor 的默认选择、sysfs 节点的命名、设备树绑定的属性名等。从老版本内核迁移到新版本时这些差异可能导致原本工作的配置失效。我的建议是移植 thermal 配置时一定要对照目标内核版本的绑定文档Documentation/devicetree/bindings/thermal/重新核对一遍不要直接照搬老版本的设备树。尤其是 trip 类型和 cooling device 的引用方式改动比较频繁。7. 把 thermal 放进整个功耗子系统里看最后想跳出 thermal 本身聊聊它在整个 Linux 功耗子系统里的位置。这样能帮你建立更完整的认知排查问题时也知道该往哪个方向找。Linux 的功耗管理是个多子系统协作的体系thermal 只是其中一环。跟它关系最密切的有这么几个cpufreq负责 CPU 频率和电压调节是 thermal 最主要的降温执行者。cpuidle负责 CPU 空闲状态管理影响待机功耗和发热。devfreq负责其他设备GPU、内存控制器等的频率调节也可能作为 cooling device。regulator负责电压调节跟 cpufreq 配合实现 DVFS。PM QoS提供性能约束接口thermal 的降频决策最终会体现为对性能的约束。它们之间的关系是thermal 感知温度通过 cooling device 接口去影响 cpufreq、devfreq 等执行者执行者再通过 regulator 等底层机制真正改变硬件状态。任何一环的配置或驱动有问题最终都会表现为热管理失效。理解了这层关系排查问题时思路就清晰了温度越线但没降频先查 cooling device 映射降频了但温度不降查散热和 trip 档位温度读数本身不对查传感器和校准。每一层都有对应的排查入口不用盲目乱试。thermal framework 的通用架构说到底就是感知—决策—执行这个闭环在内核里的具体实现。把这四层骨架、设备树绑定、触发链路、sysfs 节点这几块吃透绝大多数热管理相关的问题都能自己定位。剩下的就是具体平台的细节差异那部分只能靠读源码和实测来补。