ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux内核thermal framework架构解析:温度控制与功耗管理的闭环

Linux内核thermal framework架构解析:温度控制与功耗管理的闭环 做功耗相关的事情做得越久越发现 thermal 这块根本绕不过去。你可以把 CPU 频率调得很激进可以把调度策略调到极致但热量一旦散不出去一切性能调优都会被温度墙一把按住。Linux 内核的 thermal framework 就是温度控制与功耗管理之间的那层胶水——它管理着 CPU/GPU 频率节制、风扇转速、充电电流限制是整个功耗子系统的闭环反馈环节。这是 Linux 内核功耗子系统系列的第九篇我打算把 thermal framework 的通用架构彻底梳理清楚适合那些已经大概知道 cpufreq、PM QoS 是怎么回事但还没把 drivers/thermal 下面代码啃透的朋友。读完这篇文章你会搞明白 thermal zone、thermal sensor、thermal governor、cooling device 这四类角色怎么配合也能直接上手排查温度过高却不降频风扇乱转这类实际问题。1. 先弄清楚 thermal framework 在功耗子系统里扮演什么角色1.1 thermal 是功耗闭环的反馈环节任何一个电子设备的功耗管理本质上都是算一笔账可用功率预算怎么分性能给多少发热怎么控。这里有一个物理现实——功耗最终都会以热能形式耗散而温度升高又会反过来改变芯片的特性漏电流指数级上升金属电阻变大不但性能变差甚至可能触发不可逆的损坏。所以功耗管理不可能只做向下发指令这一件事它必须有一只盯着温度的眼睛把热反馈回来再根据热状态反向调节之前的功率分配。thermal framework 在 Linux 内核里承担的就是这个反馈角色。我经常拿水壶来类比cpufreq 相当于调节燃气灶的火力PM QoS 相当于给各种用水需求排优先级thermal framework 就是那个温控器——它盯着水温水温到 90 度自动调小火力到 100 度直接关火。没有温控器的厨房要么煮不熟饭要么糊锅。1.2 热管理的三根支柱thermal framework 的架构用一段话概括并不难一个 thermal zone 通过 sensor 获取温度温度越过某个 trip point阈值触发点后zone 绑定的 governor 做出决策驱动 cooling device 执行降温动作。这句话说起来简单但里面暗含了三个完全不同的问题域。第一根支柱是温度感知解决现在热不热的问题第二根是策略决策解决热到什么程度该怎么办的问题第三根是执行降温解决怎么把温度压下去的问题。内核里这三根支柱被拆成了三个独立抽象thermal sensor 融合进 thermal zone 的 ops 回调里governor 以单独的模块存在cooling device 又是完全独立的驱动。这种拆分最大的好处是每一环都可以单独替换——同一个 sensor 可以换不同的 governor同一个 governor 可以作用在完全不同的 cooling device 上。这也是为什么它叫 framework 而不是一个单纯的驱动它搭的是骨架具体策略和策略的执行者都是插件化的。1.3 一个最典型的场景玩游戏为什么先降频再掉帧把抽象拉回现实。你在手机上跑大型游戏SoC 的 CPU/GPU 全速冲刺功耗飙到七八瓦机身温度开始爬升。这时温度 sensor 上报温度超过 65 度的阈值governor 判定升温趋势不减于是给 CPU cooling devicecpufreq cooling下发把最大频率从 2.8GHz 压到 1.8GHz的指令给 GPU cooling devicedevfreq cooling下发降频指令甚至给充电模块下发限制充电电流的指令。这一套动作做完温度慢慢回落游戏画面随后才能恢复流畅。所以你会看到掉帧通常不是性能不够而是温控策略在主动限制性能。这个过程涉及 zone 怎么知道温度、trip 阈值怎么设、冷却设备怎么响应等一系列问题。接下来我把这套角色一个个拆开看。2. 四个核心角色到底是谁zone、sensor、governor、cooling device2.1 thermal zone温度域的容器thermal zone 是整个框架里最核心的一个容器概念。一个 zone 通常对应一个物理温度域比如 CPU 所在的裸片区域、电池区域、相机模组区域。手机上常见的 zone 有 cpu0-thermal、gpu-thermal、battery-thermal、charger-thermal 等。在内核代码中zone 由struct thermal_zone_device描述它上面挂着一组 trip point、一个 sensor 的操作函数集合、一个当前生效的 governor 指针以及一堆运行时状态——当前温度、当前轮询间隔、被动降温标志位等等。zone 自己并不产生热量它只是把哪块地方热、热到什么程度、该怎么办这个逻辑域定义出来。这里要强调一点zone 和物理硬件不一定是严格的一对一。你可以让好几个物理 sensor 参与同一个 zone 的温度计算取平均或是取最高取决于 get_temp 的实现也可以在同一个芯片上定义多个互相重叠的 zone比如一个管 CPU 大核、一个管整个 SoC。怎么划分 zone 是产品策略问题framework 本身不做限制。2.2 thermal sensor温度的最终来源sensor 不是一个独立的内核对象它其实是给 thermal zone 提供温度数据的驱动实现。zone 的 ops 里有一个get_temp回调sensor 驱动负责实现它——内核 thermal 框架自己带了一些通用 sensor 驱动比如读取 Intel MSR 温度、通过设备树描述的温度探头等。在设备树体系下推荐做法是zone 在设备树里声明sensor 驱动通过 thermal_zone_of_sensor_register 把自己绑定到 zone 上之后框架在需要的时候回调 sensor 驱动读温度。改 sensor 驱动时最常翻车的点get_temp返回的温度单位必须是毫摄氏度m°C不是摄氏度。有同事写驱动时习惯性地把温度除以 1000 再给框架导致 zone 里看到的温度永远不对——框架以为你给的是毫摄氏度结果你把 45 度写成了 45 毫度0.045 度zone 以为你掉进冰窟了永远触发不了温控。2.3 governor策略大脑governor 是 thermal framework 里看起来最智能的部分但代码量往往最少。它本质上是一个模块提供一组回调在thermal_zone_device_update被调用时决定当前温度下每个绑定的 cooling device 应该把冷却等级state调到多少。内核自带的 governor 有 step_wise、bang_bang、fair_share、user_space、power_allocator。民用设备里绝大多数用的是 step_wise 或 power_allocator。step_wise 的思路非常朴素温度越过哪个 trip就把对应绑定的冷却设备往上调一档温度回落过 hysteresis滞后量后再往下降。power_allocator 则复杂一些它基于 PID 控制思想把温度约束换算成功率预算再分配到各个冷却设备上——这个后面专门开一节细讲。governor 本身的注册接口很简单核心就是注册一个struct thermal_governor填上名字和回调即可。内核默认选中哪个 governor由 CONFIG_THERMAL_DEFAULT_GOV_* 编译配置决定运行时也可以通过 sysfs 里的 policy 文件切换。2.4 cooling device执行降温的四肢cooling device 是真正动手降温的部件比如无级调速的风扇、cpufreq 频率限制器、devfreq 频率限制器、控制充电电流的充电 IC甚至某些平台的 VRM 限流模块。它们抽象成一个统一接口state 从 0 到 max_statestate 越大降温能力越强通常也意味着功耗越低、性能越差。cooling device 底层驱动通过thermal_cooling_device_ops提供三个回调get_max_state获取档位上限、get_cur_state获取当前档位、set_cur_state设置目标档位。内核里已经提供了 cpufreq_cooling 和 devfreq_cooling 模块帮你把频率范围映射成冷却档位很多时候不用自己写。我自己的体会是cooling device 这层是整个框架里最容易出现假降温的地方。很多驱动把 state 写进去后只改了一个变量实际硬件频率纹丝不动。排查问题时一定要先确认 set_cur_state 真的执行了频率调整而不是只改了个内存标志。2.5 角色关系一图流文字版把四个角色串起来thermal sensor 往 zone 里填温度zone 维护 trip 阈值和运行状态温度刷新时zone 把当前温度交给 governorgovernor 拿着温度和每个冷却设备的当前 state 做运算得出每个实例的目标 statezone 再通过绑定的 thermal_instance 把目标 state 写给 cooling devicecooling device 执行实际的限频、调速、限流动作降低发热源功耗最终让 sensor 读到的温度回降形成闭环。这个流程从下到上、从上到下都不算绕难的是真实产品里把每一环的数据对齐——温度单位、绑定关系、状态范围有一处对不上整个闭环就断了。3. 从温度采集到冷却动作一次完整温控循环的内部数据流3.1 轮询与中断温度采样的两条路径温度变化相对慢所以 thermal framework 默认采用轮询机制。zone 有一个 polling_delay 参数单位毫秒内核工作队列 thermal_zone_device_check 按照这个间隔刷新温度。在高温场景下框架会自动把 zone 切到 passive 模式用更短的 passive_delay 间隔高频巡检。设备树里对应的就是两个属性polling-delay非 passive 状态下的巡检间隔。polling-delay-passive触发被动降温后的巡检间隔通常要短得多。除了轮询一部分 sensor 支持硬件中断温度超过阈值时由硬件直接产生中断驱动在中断上下文或中断下半部里调用thermal_zone_device_update立即触发一次温控评估。这种路径响应快、省电不用每 100ms 就去读一次温度。一个实操经验轮询间隔别设太小。有平台把 polling-delay 设成 10ms整机功耗被 thermal 轮询白白吃掉了二十多毫瓦还引发频繁的锁竞争。正常产品里 500ms 到 2000ms 的巡检节奏就够了passive 状态下 100ms 到 250ms 足够应对大多数热冲击。3.2 trip point 是温控的锚点trip point 就是温度阈值但内核里它不只是一个数字。一个 trip 由三要素构成温度值、滞后量hysteresis和类型type。类型有四种active、passive、hot、critical。active对应主动散热设备比如风扇。越过了 active trip开风扇。passive对应被动降频即通过降低性能来降温。越过了 passive tripgovernor 开始限频。hot作为强提醒驱动可以做比如启动更强散热、通知用户空间的紧急动作。critical最后防线。温度到达 critical trip内核会调用 ops 里的 critical 回调通常就是 orderly_poweroff 直接关机。hysteresis 值得单独说。它是同一个 trip 上进入和退出之间的温差防止温度在阈值附近抖动造成冷却设备反复开关。比如 trip 设在 65 度、hysteresis 2 度那么温度超过 65 度触发后要等温度跌到 63 度以下才解除。没有这个滞后风扇会像疯了似的在开和关之间来回切换。3.3 governor 决策以 step_wise 为例走一遍代码逻辑step_wise 是理解整个温控决策最好的入门样本。它的核心逻辑是对于每个绑定了冷却设备的 trip比较当前温度和该 trip 温度的关系。温度超过 trip 温度再看温度变化趋势。温度还在上升直接把冷却 state 拉到最大值或上限趋势平稳保持当前 state温度在下降保持当前 state。温度跌回 trip 温度以下但仍在 hysteresis 滞回带内state 保持不动防止抖动。温度跌出 hysteresis 范围逐步降低 state。这样设计的好处是响应极快温度超过阈值且仍在上升时step_wise 会立刻把设备拉到最高冷却档而不是一级一级升。这决定了它在低频小热量冲击下表现很好但在需要精细控温的大散热场景下容易震荡于是就有了 power_allocator 这类更平滑的替代方案。3.4 cooling device 回写与精确闭环governor 计算出每个冷却实例的目标 state 后zone 会通过 thermal_instance 把目标值交给 cooling device 驱动。这一步在代码里对应cdev-ops-set_cur_state(cdev, instance-target)。这里有一个新手很容易忽略的点governor 算出来的 target 是请求值实际能不能达到取决于 cooling device 的 max_state 和硬件物理能力。框架在绑定的时候会对 lower/upper 做夹取clamping把范围限制在 0 到 max_state 之间但运行时 set_cur_state 仍可能被驱动拒绝比如硬件正忙。所以温控闭环不是指令一下达就完成的真实的降温效果要通过下一轮 get_temp 才能确认。这也是为什么轮询机制不可缺少它既是温度感知的手段也是决策效果的验证方式。4. 代码骨架关键数据结构与核心 API4.1 thermal_zone_device 的字段地图阅读 drivers/thermal 下面代码最核心的 struct 是thermal_zone_device。不同内核版本字段略有差异但骨架类似struct thermal_zone_device { char type[THERMAL_NAME_LENGTH]; struct device device; struct list_head thermal_instances; /* 该 zone 绑定的所有实例 */ struct thermal_zone_device_ops ops; /* get_temp 等回调 */ struct thermal_zone_params *tzp; /* 平台参数sustainable_power 等 */ struct thermal_governor *governor; /* 当前挂载的策略模块 */ struct list_head trip_points; /* trip 列表 */ int trips; /* trip 数量 */ int temperature; /* 最近一次采样温度 */ int passive; /* 是否处于被动降温状态 */ unsigned long polling_delay; unsigned long passive_delay; ... };几个关键字段的用途要搞清楚。ops 是 zone 和真实硬件的桥get_temp 必须实现tzp 用来传平台参数power_allocator 会读取 sustainable_power、k_po/k_pu/k_i 等temperature 是最近一轮的采样结果governor 做决策时拿的就是它。很多调试问题最后都能在对应 struct 里找到答案——比如你发现 governor 的决策结果总是基于一个旧温度那大概率是 get_temp 的更新链路出了问题。4.2 cooling device 的接口约束配套的struct thermal_cooling_device比 zone 简单得多struct thermal_cooling_device { char type[THERMAL_NAME_LENGTH]; struct device device; struct thermal_cooling_device_ops ops; struct list_head thermal_instances; struct mutex lock; unsigned long updated; ... };ops 的三个回调前面说过了get_max_state、get_cur_state、set_cur_state。注意 state 都是 unsigned long不要想当然用 int。内核里 clamp 的也是 unsigned long用 int 存储 state 再赋值给 unsigned long 会产生类型截断。我帮人排查过一个 cdev state 为负导致 set_cur_state 异常的怪问题最终根因就是驱动里用 int 存 state高位填充出了问题。4.3 thermal_instance绑定关系的最小单元zone 和 cdev 之间不是直接画一条线而是通过 thermal_instance 表达某个 trip 上zone 与 cdev 的一条约束关系。一个 trip 上可以绑多个 cdev一个 cdev 也可以同时挂在多个 zone 的多个 trip 上。每个 instance 保存着 lower、upper、target、weight 这些参数。struct thermal_instance { char name[THERMAL_NAME_LENGTH]; struct thermal_zone_device *tz; struct thermal_cooling_device *cdev; int trip; unsigned long lower; unsigned long upper; unsigned long target; struct list_head tz_node; struct list_head cdev_node; unsigned long weight; };理解了它多对多关系就不神秘了zone 侧的链表由 tz_node 串起来cdev 侧的链表由 cdev_node 串起来中间大家共享同一个 thermal_instance 对象。4.4 驱动注册 sensor 的两种姿势在老式直接驱动风格里传感器驱动自己创建 zone代码大概长这样static int my_sensor_get_temp(struct thermal_zone_device *tz, int *temp) { *temp my_sensor_read_millidegree(); return 0; } static struct thermal_zone_device_ops my_thermal_ops { .get_temp my_sensor_get_temp, }; static int my_probe(struct platform_device *pdev) { struct thermal_zone_device *tz; tz thermal_zone_device_register(my_sensor, 0, NULL, my_thermal_ops, 0, 0, 0, 0); if (IS_ERR(tz)) return PTR_ERR(tz); platform_set_drvdata(pdev, tz); return 0; }这种写法现在越来越少因为 trip 数量、policy 全部由代码写死产品调整灵活性很差。现代推荐做法是设备树里声明 thermal zonesensor 驱动只负责把温度提供给 of-thermal 框架解析后注册的 zonestatic int my_sensor_probe(struct platform_device *pdev) { struct thermal_zone_device *tz; tz devm_thermal_zone_of_sensor_register(pdev-dev, 0, data, my_sensor_thermal_of_ops); if (IS_ERR(tz)) return PTR_ERR(tz); platform_set_drvdata(pdev, tz); return 0; }设备树里 thermal-zones 节点把 trip、cooling-maps 都声明好框架自动解析并完成绑定。sensor 驱动不再关心 trip 和 governor职责单一这是新平台应该走的路线。5. 绑定机制zone 与 cooling device 是怎么牵手的5.1 两条绑定路线静态声明与动态 API绑定本质就是给某个 zone 的某个 trip 挂上某个 cooling device并规定冷却档位范围。设备树路线是静态声明在 cooling-maps 里写动态路线是内核代码里调thermal_zone_bind_cooling_device()。两种路线最终都会生成 thermal_instance。设备树写法里最容易被忽略的是 cooling-device 属性后面的min max语法。一个典型 thermal zone 节点长这样thermal-zones { cpu_thermal: cpu-thermal { polling-delay-passive 250; polling-delay 1000; trips { cpu_alert0: cpu-alert0 { temperature 65000; hysteresis 2000; type passive; }; cpu_crit0: cpu-crit0 { temperature 95000; hysteresis 2000; type critical; }; }; cooling-maps { map0 { trip cpu_alert0; cooling-device cpu0 0 2; }; }; }; };cpu0 0 2表示该冷却设备允许的 state 范围是 [0, 2]THERMAL_NO_LIMIT 表示不设限。这个范围会直接映射到 instance 的 lower/upper。如果上层有人在系统里对 cdev state 做了额外限制出现绑定了却不起作用的问题先检查这里范围是不是被压住了。5.2 thermal_zone_bind_cooling_device 内部做了什么动态绑定 API 原型如下int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz, int trip, struct thermal_cooling_device *cdev, unsigned long upper, unsigned long lower, unsigned int weight);内部流程大致是检查 tz、cdev 指针和 trip 序号合法性。遍历 zone 已有的 thermal_instances如果这个 trip 上已经绑过该 cdev就更新 lower/upper/trip 并返回不重复绑定。分配一个新的 thermal_instance初始化字段。对 lower/upper 做 clampingupper min(upper, cdev-max_state)lower min(lower, cdev-max_state)。把 instance 插入 zone 的 thermal_instances 链表和 cdev 的 thermal_instances 链表。如果 zone 当前正在运行触发一次 thermal_zone_update让新实例立即参与决策。这段代码看着简单但有一个隐含要求调用者必须保证 tz 的锁已经被持有或者该函数内部会加锁。不同内核版本实现有差异自己写调用代码前先确认当前版本是否内部加锁。我见过老版本内核不加锁然后热插拔时并发绑定直接链表操作崩溃的案例。5.3 多 zone 多 cdev 的网状关系怎么管理产品里经常出现一个 cdev 被多个 zone 复用同一个 CPU cpufreq cooling 既受 cpu-thermal 管也受 board-thermal 管还受 skin-thermal外壳温度管。这种网状关系确实复杂但内核的解法很朴素——看 governor 的决策顺序和 weight 权重。在设备树里给每个 map 配不同的 contribution 权重weightpower_allocator 会参考 weight 把总功率预算按比例分配。如果不用 power_allocatorweight 通常只是个信息字段不会影响决策别指望 step_wise 会读它。这点特别容易产生误解我见过有人在 step_wise 模式下调 weight 调了很久结果发现根本不生效。5.4 解绑与卸载常被忽略的坑动态解绑调用int thermal_zone_unbind_cooling_device(struct thermal_zone_device *tz, int trip, struct thermal_cooling_device *cdev);它会把匹配的 thermal_instance 从两个链表上摘下来并释放。卸载 cooling device 驱动时如果还有 zone 绑着它直接删驱动必然产生野指针。所以 cdev 驱动的 remove 回调里要么主动解绑所有实例要么依赖框架在 cdev 释放时统一清理。实际产品热插拔场景比如可插拔风扇模块最常在这里崩溃日志表现为 thermal 相关链表的空指针访问。排查顺序先确认 remove 里是否调用了 unbind再确认有没有其他驱动异步持有 cdev 指针。6. governor 选型与参数调优从 step_wise 到 power_allocator6.1 内核自带 governor 一览不同内核版本预装的 governor 略有差异常见五种governor核心思路适用场景主要局限step_wise按 trip 逐级调节越阈值直接拉满大多数手机、平板、机顶盒容易震荡不够精细bang_bang只开关两态温度高开、低关风扇等开关型设备无法做连续调节fair_share按权重分配设备最大功率服务器主板依赖权重参数的合理性user_space只上报温度由用户态决策需要复杂策略时内核态被动依赖用户程序power_allocatorPID 控温功率预算分配移动 SoC、高功耗且需要精细控制调参复杂依赖准确功率模型我自己的倾向散热器功率和发热源匹配良好的产品step_wise 往往已经够用凡是出现过热死机、电池过热投诉的产品仔细调 power_allocator 的收益远大于换散热硬件。6.2 step_wise 的震荡问题与 hysteresis 设置step_wise 最大的毛病是震荡。温度高于 trip 且趋势上升时它直接把 cdev state 拉到上限降温太快温度瞬间跌穿 hysteresis又把 state 降回 0然后温度又快速升上去——风扇或限频出现周期性波动用户体验就是一会儿卡一会儿不卡。缓解手段通常有三个调大 hysteresis让进入和退出拉开 2 到 5 度的差距。调大 passive_delay降低决策频率给降温效果留出反应时间。在 trips 里设一个较低的 passive trip 作为缓冲区把主 trip 温度留高一点。这些参数在设备树 trips 节点里就能改不用动代码适合量产阶段快速试。6.3 power_allocatorIPA为什么适合移动端power_allocator 的思路是给定一个目标温度passive trip 的温度用 PID 控制器估算当前功率超限值再换算成功率预算把预算分配到各冷却设备。它要求 zone 提供 sustainable_power该热域的可持续散热功率单位 mW以及 PID 系数 k_po、k_pu、k_i。这个 governor 的优点是温度曲线平滑几乎看不到 step_wise 那种方波抖动。代价是需要相对准确的功率模型否则要么过于保守一直压性能要么过于激进导致温度失控。调它是个体力活先测稳态温度再调 sustainable_power再调 k_po/k_pu/k_i 让温度收敛到目标 trip 附近。需要注意power_allocator 只对 passive trip 做控制它默认取第一个 passive trip 作为目标温度点其他 active、critical trip 由框架按类型单独处理。如果设备树里没有 passive trippower_allocator 基本不干活。6.4 决策实时性轮询延迟、passive delay 与紧急中断温控的实时性瓶颈往往不在 governor 计算而在温度采样链路上。轮询模式下每次温度刷新都要经过工作队列调度决策天然滞后一个周期。对于快速热冲击——比如 CPU 突然跑满、充电电流猛增——轮询根本反应不过来所以现代平台会给关键温度加硬件中断。通用架构下轮询依然作为兜底两者并行不冲突。紧急处理上critical trip 大多由内核直接处理关机不经过 governor。如果你的产品在结温极限附近频繁关机与其调 governor 参数不如先检查 critical trip 温度是否离物理上限太近以及 get_temp 的采集误差是不是偏大。很多时候不是框架的问题是传感器精度和 trip 裕量的问题。7. 调试实践sysfs 接口、日志与常见故障排查7.1 sysfs 快速体检排查 thermal 问题的第一步永远是到 /sys/class/thermal/ 下翻目录。一个典型 zone 目录里有这些条目typezone 类型名。temp当前温度单位毫摄氏度。modeenabled/disabled测试时可以直接 echo disabled 临时关闭温控。policy当前生效的 governor 名称可以直接改写切换。trip_point_X_temp第 X 个 trip 的温度值。trip_point_X_type第 X 个 trip 的类型。cdevX_trip_point绑定的第 X 个冷却设备对应哪个 trip。cdevX_weight对应实例的权重。常用命令参考cat /sys/class/thermal/thermal_zone*/temp cat /sys/class/thermal/thermal_zone*/policy echo step_wise /sys/class/thermal/thermal_zone0/policy cat /sys/class/thermal/cooling_device*/cur_state echo 5 /sys/class/thermal/cooling_device0/cur_state注意直接写 cur_state 在部分内核版本里会立即触发一次 thermal_zone_update随后 governor 可能又把 state 改回去。手动灌 state 只适合验证 cdev 驱动本身不适合做温控功能测试。7.2 故障一温度读数始终不对温度读数表现为恒为 0、负数或跳到异常大值。排查链路先直接读 sysfs 的 temp确认温度会不会变化。如果固定不变回到 sensor 驱动确认 get_temp 是否真正读取了硬件寄存器。确认单位是毫摄氏度读到 45000 表示 45 度不是 45000 度。对设备树 zone确认 of-thermal 解析成功dmesg 里有对应 zone created 日志。常见的根因是 IIO 通道初始化失败后 get_temp 返回 0或者驱动依赖的时钟被关闭导致寄存器读不出来。排查时先看返回的错误码而不是直接怀疑 thermal 框架。7.3 故障二CPU 降频不生效表现是温度已经超过 passive trip但频率纹丝不动。排查顺序cat /sys/class/thermal/thermal_zoneX/policy确认当前 governor 是不是期望的那个。cat /sys/class/thermal/thermal_zoneX/cdevX_trip_point确认 cdev 有没有绑到正确的 trip 上。cat /sys/class/thermal/cooling_deviceY/cur_state确认 governor 有没有下发 state。如果 state 变了但频率没变问题在 cpufreq_cooling 驱动的 set_cur_state 到 cpufreq 的链路这时检查是否被用户态频率策略如 userspace governor给盖住了。这条链路我排查过不下五次每次走到最后都是温控在降用户在锁。内核里 cpufreq 的 frequency limits 是有叠加语义的修改频率上限和用户态直接设置频率是两回事别混了。7.4 故障三过热却没有日志甚至直接关机很多平台 thermal 驱动日志默认不开DEBUG 等级被关掉后governor 的决策过程完全不打印。直接关机的情况多半是 critical trip 触发。排查手段是打开 trace eventecho 1 /sys/kernel/tracing/events/thermal/enable cat /sys/kernel/tracing/trace_pipethermal 子系统有一组事件比如 thermal_temperature、thermal_power_allocator能看到每次温度采样和 PID 输出。不想开 trace也可以在各 ops 回调里临时加 printk虽然老土但有效。还有一种该触发没触发的坑系统 suspend 时 thermal 工作队列可能不运行如果 sensor 有中断唤醒能力需要驱动在 suspend 期间把 thermal_zone_device_update 挂到唤醒源上否则睡眠中温度飙升无人管。7.5 一个来自实战的整体调试模板最后给一个相对高效的调试套路按顺序执行看、测、改、查链路、观察。看读所有 zone 的 temp写个简单脚本轮询画温度曲线。测手动改 cooling_device cur_state验证各 cdev 是否真实生效。改调低 trip 温度人为制造触发条件验证 governor 动作。查链路从 zone - governor - instance - cdev - 硬件频率逐级打日志定位断点。观察稳定运行一段时间确认没有震荡、误触发、休眠期失控。这样做完一轮90% 的 thermal 问题都能定位到具体环节。剩下 10% 基本是硬件或 sensor 本身的问题已经不是 framework 能修的了。我在实际调温控策略时还有一个体会永远别只看代码和静态配置thermal 是一个强依赖实物表现的子系统。同样一套参数在测试台上可能跑得好好的换一批物料或者换一个散热器供应商行为就完全变了。所以设备树里把 trip、hysteresis、polling delay 做成可调参数量产阶段会省掉你大量重新编译内核的功夫。这也是为什么 framework 层面的东西值得花时间梳理清楚——把通用架构吃透之后你在任何平台上面对 thermal 问题都不会慌。
返回列表