☰
Linux thermal framework 深度解析:从架构设计到实战调优
2026/10/7 7:55:03 网站建设 项目流程

1. 从一次散热异常排查说起:thermal framework 到底管什么

前阵子帮一个做嵌入式设备的朋友排查问题,他的板子跑视频解码跑上十分钟就降频,帧率从 60 掉到 30 出头,日志里翻来覆去就是几行thermal_zone的温度上报和cooling device的状态切换。他一开始以为是 CPU 调度器的问题,折腾了两天没结果,最后定位到是 thermal framework 里一个 trip point 的触发阈值配得太保守,导致设备刚热身就被按住了。这件事挺典型——很多人做 Linux 功耗相关的工作,注意力都放在 cpufreq、cpuidle、regulator 这些"显性"的子系统上,thermal framework 反而像个安静的幕后角色,平时不声不响,一旦出问题就是降频、关机、甚至硬件损伤这种硬伤。

thermal framework 是 Linux 内核功耗子系统里负责温度管理的那一层。它要干的事情说起来不复杂:持续采集各个温度传感器的读数,跟预设的阈值(trip point)比较,一旦越界就触发对应的动作——降频、限流、开风扇、甚至直接关机。但真正落到代码里,它要解决的是"多个传感器、多个温区、多种降温手段、多种触发策略之间怎么协调"这个问题。内核里的实现抽象出了 thermal zone、thermal governor、cooling device、thermal trip 这几个核心概念,把它们用一套统一的框架串起来,让驱动开发者只需要填配置、注册回调,不用自己造轮子。

这篇文章适合谁看?如果你在做嵌入式 Linux 开发、手机/平板/车机的功耗调优、服务器散热策略配置,或者单纯想搞明白/sys/class/thermal/下面那一堆目录到底在干什么,那这篇梳理应该能帮你把 thermal framework 的整体骨架搭起来。我会从架构设计讲到核心数据结构,再到实际配置和排查,尽量把"为什么这么设计"讲透,而不是只罗列 API。文中涉及的内核版本以 5.x 到 6.x 的通用实现为准,不同版本细节会有差异,但主干逻辑是稳定的。

2. thermal framework 的整体架构与设计思路

2.1 为什么内核要单独搞一套 thermal 框架

在 thermal framework 成型之前,温度管理这件事是散落在各个驱动里的。CPU 驱动自己读温度、自己判断、自己调频;GPU 驱动另搞一套;风扇驱动再搞一套。这种做法的直接后果是:当 CPU 和 GPU 同时发热时,两边各自为政,谁也不知道对方在干什么,很容易出现"CPU 刚降完频,GPU 又把温度顶上去了"的拉锯战。更麻烦的是,温度传感器和降温设备之间是多对多的关系——一个温区可能被多个传感器影响,一个降温设备也可能服务于多个温区,硬编码的耦合根本没法维护。

thermal framework 的核心设计思路就是解耦。它把"感知温度"和"执行降温"这两件事拆开,中间用一套标准化的注册和回调机制连接。温度传感器驱动只负责上报温度,降温设备驱动只负责提供"我能降多少"的接口,至于什么时候降、降多少、谁来降,全部交给框架里的 governor 去决策。这样一来,新增一个传感器或者新增一种降温手段,都不需要改动其他部分的代码。

这个思路跟 Linux 其他子系统是一脉相承的——就像 input 子系统把输入设备和输入事件处理解耦,thermal 把温度感知和温度控制解耦。理解了这一点,后面看那些数据结构就不会觉得突兀了。

2.2 四个核心概念的关系梳理

thermal framework 里有四个绕不开的概念,我用一个生活化的类比先讲清楚它们的关系,再落到代码上。

把整个温度管理想象成一栋大楼的温控系统:thermal zone就是一个个"房间",每个房间有自己的温度计(传感器)和一套温控规则;thermal sensor是温度计本身,负责读数;trip point是房间墙上贴的"温度警戒线",比如 60 度提醒、80 度报警、100 度强制断电;cooling device是房间里的降温设备,可能是空调、风扇、或者开窗;thermal governor则是那个拿着规则手册做决策的"管理员",它根据温度计读数和警戒线,决定让哪个降温设备出多少力。

落到内核代码里,这几个概念对应到具体的结构体:

概念内核结构体职责
thermal zonestruct thermal_zone_device代表一个温区,聚合传感器、trip、governor
thermal sensorstruct thermal_zone_device的 ops提供get_temp等回调读取温度
trip pointstruct thermal_trip定义触发阈值、类型和绑定的动作
cooling devicestruct thermal_cooling_device提供get_max_state/set_cur_state接口
thermal governorstruct thermal_governor实现throttle等决策逻辑

这里有个容易混淆的点:thermal zone 和 thermal sensor 在代码里经常是绑在一起的,一个thermal_zone_device通常就对应一个物理传感器,但也可以一个 zone 聚合多个传感器(通过thermal_zone_device_register时的参数或者后续的绑定)。理解这个区别,在排查"为什么温度读数不对"的时候很关键。

2.3 数据流向:从传感器读数到降温动作

整个框架的数据流是单向的、周期性的。我用文字把这条链路串一遍,你对照着看代码会清晰很多。

第一步,thermal zone 注册时,框架会启动一个延时工作队列(thermal_zone_device_update的调度),按polling_delay设定的周期去轮询温度。第二步,轮询时调用 zone 的get_temp回调,拿到当前温度。第三步,把温度跟这个 zone 里所有的 trip point 逐一比较,判断哪些 trip 被触发或解除。第四步,如果 trip 状态有变化,通知 governor。第五步,governor 根据策略(比如 step_wise、fair_share、bang_bang)计算出每个 cooling device 应该处于什么状态。第六步,调用 cooling device 的set_cur_state把状态写下去,实际执行降频、开风扇等动作。

这条链路里,trip point 是触发条件,governor 是决策大脑,cooling device 是执行末端。三者缺一不可,而且顺序不能乱。很多配置错误就出在这条链路的某一环——比如 trip 配了但没绑 cooling device,或者 governor 选了但跟 trip 类型不匹配。

2.4 与 cpufreq、cpuidle 的边界在哪里

经常有人问:thermal 降频和 cpufreq 调频到底谁管谁?答案是:thermal 不直接调频,它通过 cooling device 间接影响 cpufreq。

具体来说,CPU 的降温通常通过注册一个cpufreq_cooling_device来实现。这个 cooling device 内部会调用 cpufreq 的接口,限制 CPU 的最大频率。thermal governor 决定"把 CPU 降到第 3 档",cpufreq cooling device 就把这个"第 3 档"翻译成具体的频率上限,然后 cpufreq 子系统去执行。thermal 本身不关心频率是多少赫兹,它只关心"降温设备的状态等级"。

这个边界很重要。它意味着 thermal 的调优和 cpufreq 的调优是两件事:thermal 决定"什么时候开始压",cpufreq 决定"压到多少合适"。两者配合不好,就会出现前面说的那种"刚热身就被按住"的情况。

3. thermal zone 与 trip point 的核心细节

3.1 thermal zone 的注册流程与关键参数

注册一个 thermal zone 用的是thermal_zone_device_register或者它的带参数版本thermal_zone_device_register_with_trips。这个函数的参数不少,我挑几个最容易踩坑的讲。

type参数是 zone 的名字,会出现在/sys/class/thermal/thermal_zoneX/type里。这个名字要起得有辨识度,比如cpu-thermal、gpu-thermal、board-thermal。我见过有人图省事全叫thermal,结果系统里有五六个同名 zone,排查的时候根本分不清哪个是哪个。

trips和num_trips是 trip point 数组和数量。trip 的定义方式在不同内核版本有变化,早期用struct thermal_trip数组,后来引入了设备树(Device Tree)描述的方式,现在主流做法是在 DTS 里配thermal-zones节点,驱动侧用thermal_zone_device_register_with_trips把解析出来的 trip 传进去。

polling_delay是轮询周期,单位毫秒。这个值直接决定温度采样的频率和系统的功耗开销。设太小(比如 10ms)会导致频繁唤醒,白白耗电;设太大(比如 5000ms)会导致响应迟钝,温度已经冲上去了才反应过来。经验值一般在 100ms 到 1000ms 之间,具体看散热惯性和温度变化速率。对于温度变化快的场景(比如手机玩游戏),可以设小一点;对于温度变化慢的场景(比如服务器机箱),设大一点没关系。

passive_delay是进入被动降温(passive cooling)后的轮询周期。被动降温指的是靠降频这种"软"手段降温,通常需要更频繁地采样来判断效果,所以这个值一般比polling_delay小。

governor参数指定这个 zone 用哪个 governor。如果不指定,框架会用默认的(通常是step_wise)。这个参数可以在注册时指定,也可以后续通过 sysfs 修改。

3.2 trip point 的类型与触发逻辑

trip point 不是简单的"超过某个温度就触发",它分类型,不同类型的处理逻辑完全不同。这是 thermal framework 里最容易被误解的地方之一。

critical trip是最严重的一类。温度达到 critical 阈值时,框架会直接触发系统关机(通过orderly_poweroff或者直接调用thermal_zone_device_critical)。这个动作是不可协商的,governor 没有决策权。critical 的阈值通常设在硬件的绝对安全上限,比如芯片结温 125 度。配这个值要非常谨慎,设低了会误关机,设高了起不到保护作用。

hot trip是"很热但还没到关机"的级别。触发后框架会通知 governor 采取激进降温措施。hot trip 通常跟 critical 配合使用,形成"先警告、再关机"的两级保护。

passive trip是"该降频了"的级别。触发后 governor 会启动被动降温,也就是通过 cooling device 降频。passive trip 是日常功耗调优里最常打交道的类型。

active trip是"该开风扇了"的级别。触发后 governor 会启动主动降温设备,比如风扇。active trip 通常有多个,对应风扇的不同档位。

这四类 trip 的触发逻辑有个细节:trip 有"触发"和"解除"两个方向。温度上升越过阈值叫触发,温度下降到阈值以下叫解除。框架会维护每个 trip 的当前状态,只在状态变化时才通知 governor。这个设计避免了 governor 被重复调用,但也带来一个坑:如果温度在阈值附近抖动,会导致 trip 状态频繁翻转,governor 被反复唤醒。解决办法是设置迟滞(hysteresis),也就是触发阈值和解除阈值之间留一个间隔。有些内核版本支持在 trip 里配hysteresis字段,不支持的版本就得靠 governor 自己处理。

3.3 设备树里怎么描述 thermal zone

现在主流的 ARM 平台基本都用设备树来描述 thermal 配置。一个典型的 thermal zone 节点长这样:

thermal-zones { cpu_thermal: cpu-thermal { polling-delay-passive = <100>; polling-delay = <500>; thermal-sensors = <&tsadc 0>; trips { cpu_alert0: cpu-alert0 { temperature = <70000>; hysteresis = <2000>; type = "passive"; }; cpu_alert1: cpu-alert1 { temperature = <85000>; hysteresis = <2000>; type = "passive"; }; cpu_crit: cpu-crit { temperature = <100000>; hysteresis = <2000>; type = "critical"; }; }; cooling-maps { map0 { trip = <&cpu_alert0>; cooling-device = <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; }; map1 { trip = <&cpu_alert1>; cooling-device = <&cpu0 0 2>; }; }; }; };

这里有几个点值得展开。temperature的单位是毫摄氏度,70000 就是 70 度,这个单位很容易搞错,写成 70 就变成 0.07 度了,trip 永远触发不了。hysteresis也是毫摄氏度,2000 就是 2 度。cooling-maps把 trip 和 cooling device 绑起来,cooling-device后面的两个数字是状态范围,THERMAL_NO_LIMIT表示不限制,0 2表示这个 cooling device 的状态可以从 0 到 2。

thermal-sensors指向温度传感器节点,后面的数字是传感器索引。一个 zone 可以绑多个传感器,框架会取它们中的最大值或者按配置的策略处理。

3.4 实操心得:trip 阈值怎么定才合理

定 trip 阈值这件事,没有万能公式,但有几个原则可以遵循。

第一,critical 阈值必须留足安全余量。芯片手册给的最高结温通常是绝对上限,实际配置要往下留 10 到 15 度。比如手册说 125 度,critical 配 110 度比较稳妥。因为从触发到实际关机有延迟,而且温度传感器本身有误差。

第二,passive 阈值要跟散热能力匹配。如果设备在满载时稳定温度是 75 度,passive 阈值配 70 度就意味着会持续降频;配 80 度则可能永远不触发。合理的做法是先测出满载稳定温度,passive 阈值设在它下面 5 到 10 度,这样既能及时降温,又不会过度干预。

第三,多个 passive trip 之间要有梯度。比如 70 度降一档、80 度降两档、90 度降三档,形成渐进式降温。如果只配一个 trip,要么不降要么猛降,体验很差。

第四,hysteresis 不能省。我见过不少配置漏了 hysteresis,结果温度在阈值附近抖动时 trip 反复触发,日志刷屏,CPU 被 governor 的反复调用拖累。一般设 2 到 5 度比较合适。

4. cooling device 与 governor 的协作机制

4.1 cooling device 的注册与状态模型

cooling device 的抽象非常简洁,核心就是两个回调:get_max_state返回最大状态等级,set_cur_state设置当前状态等级。状态等级是个非负整数,0 通常表示"不降温",数值越大表示降温力度越大。

这个"状态等级"的设计是 thermal framework 的一个巧妙之处。它不关心降温设备具体是什么——风扇、CPU 频率、GPU 频率、甚至内存带宽——统统抽象成"状态等级"。governor 只需要知道"把状态从 2 调到 3 能多降一点温",不需要知道 3 档对应多少转或者多少赫兹。这种抽象让 governor 的代码可以通用,也让新增降温设备变得简单。

注册 cooling device 用thermal_cooling_device_register,需要提供设备名、私有数据和thermal_cooling_device_ops。CPU 的 cooling device 通常由 cpufreq 子系统在初始化时自动注册,名字类似cpu0-cpufreq或者cpufreq-cpu0。风扇的 cooling device 由风扇驱动注册。GPU 的由 GPU 驱动注册。

这里有个实际配置中常遇到的问题:cooling device 的状态等级跟实际降温效果的对应关系不是线性的。比如 CPU 频率从 2GHz 降到 1.5GHz 可能降温 5 度,从 1.5GHz 降到 1GHz 可能只降温 2 度。governor 如果按线性假设去调,效果会跟预期差很多。解决办法是在 cooling device 驱动里做频率到状态的映射时,尽量让状态等级跟降温能力成比例,或者用fair_share这类会考虑实际贡献的 governor。

4.2 主流 governor 的决策逻辑对比

内核里内置了几个 governor,各有适用场景。我把常用的几个列出来对比:

governor决策逻辑适用场景特点
step_wise每次温度越界只调整一档通用场景温和,响应慢,不会剧烈波动
fair_share按各 cooling device 的贡献比例分配多设备协同公平,但计算复杂
bang_bang触发就开,解除就关风扇控制简单粗暴,适合开关型设备
power_allocator基于功耗预算分配手机/移动设备精细,需要功耗模型支持
user_space把决策权交给用户态特殊调优灵活,但需要用户态配合

step_wise是默认 governor,也是最常用的。它的逻辑是:温度越过 trip 阈值时,把绑定的 cooling device 状态加一档;温度降回阈值以下时,减一档。每次只动一档,避免剧烈波动。这个策略的缺点是响应慢,如果温度上升很快,一档一档加可能跟不上。解决办法是把 trip 阈值设密一点,或者用power_allocator。

fair_share适合一个 trip 绑了多个 cooling device 的场景。它会根据每个设备当前的状态和最大状态计算一个"贡献度",然后按比例分配降温任务。比如 CPU 和 GPU 都绑在同一个 trip 上,fair_share 会让降温能力强的那个多承担一点。

power_allocator是移动设备上比较常用的,它需要每个 cooling device 提供功耗模型(get_requested_power等回调),然后根据总的功耗预算来分配。这个 governor 配置复杂,但效果最好,适合对续航敏感的场景。

4.3 governor 与 trip 的绑定关系

governor 是绑定在 thermal zone 上的,不是绑定在 trip 上的。一个 zone 只能有一个 governor,这个 governor 负责处理这个 zone 里所有的 trip。这一点在配置时要注意:如果一个 zone 里既有 passive trip 又有 active trip,governor 要能同时处理这两种。

step_wise和fair_share都能处理多种 trip 类型。bang_bang主要针对 active trip(风扇)。power_allocator主要针对 passive trip(降频)。选 governor 的时候要看 zone 里 trip 的类型组合。

切换 governor 可以通过 sysfs:

echo fair_share > /sys/class/thermal/thermal_zone0/policy

这个操作在运行时就能生效,不需要重启。调试的时候很有用,可以快速对比不同 governor 的效果。

4.4 实操心得:cooling device 状态映射的坑

前面提到状态等级跟实际降温效果非线性,这里展开讲一个具体的坑。

假设一个 CPU 有 8 个频率档位,从 300MHz 到 2GHz。cpufreq cooling device 注册时,如果简单地把状态 0 映射到 2GHz、状态 7 映射到 300MHz,那么状态每加一档,频率下降约 240MHz。但实际功耗跟频率的关系是超线性的(大致是频率的三次方关系),所以高频段的降温效果远大于低频段。governor 按线性假设调档,会出现"前几档降了没感觉,后几档一降就过头"的情况。

解决办法有两个。一是在 cooling device 驱动里做非线性映射,让状态等级跟功耗降低量成比例。二是用power_allocatorgovernor,它直接基于功耗模型决策,绕开了状态等级的线性假设。我个人的经验是,如果平台支持,优先用power_allocator;如果不支持,就在 cooling device 驱动里把映射做细一点。

还有一个坑是cooling device 的注册顺序。如果 thermal zone 先注册,cooling device 后注册,那么 zone 在 cooling device 注册前的那段时间里是没有降温手段的。如果这时候温度冲上去,就只能等 critical 关机了。正确的做法是保证 cooling device 在 thermal zone 之前注册,或者在设备树里用cooling-maps的引用关系让框架自动处理依赖顺序。

5. 完整实操:从零配置一个 thermal zone

5.1 确认硬件与传感器信息

动手配置之前,先把硬件情况摸清楚。第一步是确认温度传感器在哪、怎么访问。在设备树里找thermal-sensors或者tsadc、tmu、thermal这类节点。如果用的是现成的开发板,厂商的 DTS 里通常已经有基础配置,可以拿来改。

第二步是确认有哪些降温手段。CPU 的 cpufreq cooling device 一般是自动注册的,用ls /sys/class/thermal/能看到cooling_deviceX目录。进去看type文件,能知道这个 cooling device 是什么类型。风扇的话要看硬件有没有 PWM 控制接口,有的话需要对应的驱动支持。

第三步是确认传感器的读数和精度。可以手动读一下:

cat /sys/class/thermal/thermal_zone0/temp

输出是毫摄氏度。如果读数是 0 或者明显不合理,说明传感器驱动有问题,得先解决这个再谈配置。

5.2 编写设备树节点

假设硬件情况是:一个 CPU 温度传感器,一个 CPU 频率降温设备,一个风扇。配置思路是:70 度启动降频,85 度启动风扇,105 度强制关机。

thermal-zones { cpu_thermal: cpu-thermal { polling-delay-passive = <100>; polling-delay = <500>; thermal-sensors = <&tsadc 0>; trips { cpu_passive: cpu-passive { temperature = <70000>; hysteresis = <3000>; type = "passive"; }; cpu_active: cpu-active { temperature = <85000>; hysteresis = <3000>; type = "active"; }; cpu_crit: cpu-crit { temperature = <105000>; hysteresis = <2000>; type = "critical"; }; }; cooling-maps { map_passive { trip = <&cpu_passive>; cooling-device = <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; }; map_active { trip = <&cpu_active>; cooling-device = <&fan0 0 3>; }; }; }; };

这里cpu0是 CPU 节点,框架会自动为它创建 cpufreq cooling device。fan0是风扇节点,需要风扇驱动支持 thermal cooling device 接口。THERMAL_NO_LIMIT表示不限制状态范围,让 governor 自己决定。

5.3 编译、烧录与验证

改完 DTS 后重新编译设备树,烧录到板子上,重启。重启后先确认 thermal zone 注册成功:

ls /sys/class/thermal/ # 应该能看到 thermal_zone0 和 cooling_device0、cooling_device1 等 cat /sys/class/thermal/thermal_zone0/type # 输出应该是 cpu-thermal cat /sys/class/thermal/thermal_zone0/temp # 输出当前温度,单位毫摄氏度 cat /sys/class/thermal/thermal_zone0/trip_point_0_temp cat /sys/class/thermal/thermal_zone0/trip_point_0_type # 确认 trip 配置正确

然后确认 cooling device 绑定正确:

cat /sys/class/thermal/cooling_device0/type # 应该是 processor 或者 cpufreq 相关 cat /sys/class/thermal/thermal_zone0/cdev0_cur_state cat /sys/class/thermal/thermal_zone0/cdev0_max_state # 确认 cooling device 状态可读

5.4 压力测试与参数微调

配置对不对,得靠压力测试验证。用stress-ng或者dd把 CPU 跑满,同时监控温度:

# 一个终端跑压力 stress-ng --cpu 4 --timeout 300s # 另一个终端监控 watch -n 1 'cat /sys/class/thermal/thermal_zone0/temp; \ cat /sys/class/thermal/thermal_zone0/cdev0_cur_state'

观察温度上升过程中,cooling device 状态是否按预期变化。如果温度到了 70 度但状态还是 0,说明 trip 没触发或者 cooling map 没绑对。如果状态变了但温度不降,说明降温力度不够,需要调整 trip 阈值或者 cooling device 的状态范围。

微调的时候一次只改一个参数,改完重新测。我一般会记录一组数据:时间、温度、cooling device 状态、CPU 频率,画成曲线看趋势。这样能直观看出哪个环节有问题。

5.5 实操心得:验证阶段的三个注意点

第一,压力测试要跑够时间。温度上升有惯性,跑 30 秒可能还没到稳态。我一般至少跑 5 分钟,等温度曲线平了再判断。

第二,环境温度要记录。同样的配置,冬天和夏天测出来的结果可能差十几度。测试时记下环境温度,方便对比。

第三,注意其他热源的影响。如果板子上有多个发热大户(CPU、GPU、充电芯片),单独压 CPU 可能测不出问题,要模拟真实场景同时加压。

6. 常见问题与排查技巧实录

6.1 温度读数异常的问题定位

温度读数不对是最常见的问题,表现有好几种:读数恒为 0、读数跳变剧烈、读数明显偏离实际。

读数恒为 0 通常是传感器驱动没加载或者get_temp回调返回错误。先看dmesg有没有传感器相关的报错,再确认设备树里传感器节点是否被正确引用。有些平台的传感器需要先配置寄存器才能读数,驱动初始化顺序不对就会读到 0。

读数跳变剧烈一般是采样或滤波的问题。有些传感器原始读数噪声大,需要驱动侧做滑动平均或者中值滤波。如果驱动没做,可以在 governor 层面用hysteresis缓解,但治标不治本。

读数偏离实际可能是校准问题。部分传感器的精度需要出厂校准,校准参数存在 efuse 或者 OTP 里,驱动要负责读取并应用。如果校准参数没读到,读数会有系统性偏差。

6.2 cooling device 不生效的排查路径

配置了 cooling map 但降温设备不动,排查路径是这样的:

先确认 cooling device 本身能不能手动控制。直接写 sysfs:

echo 3 > /sys/class/thermal/cooling_device0/cur_state cat /sys/class/thermal/cooling_device0/cur_state

如果写不进去或者读出来没变,说明 cooling device 驱动有问题,跟 thermal 框架无关。

如果手动能控制,但自动不触发,那就是 trip 或者 governor 的问题。检查 trip 的temperature单位是不是毫摄氏度,检查 cooling map 的trip引用是不是指向了正确的 trip 节点,检查 governor 是不是支持这个 trip 类型。

还有一个隐蔽的坑:cooling device 的状态范围。如果 cooling map 里写的是<&cpu0 0 0>,那状态范围就是 0 到 0,governor 想调也没得调。这种情况在复制粘贴配置时很容易发生。

6.3 trip 频繁触发的抑制方法

trip 频繁触发表现为日志里 trip 状态反复翻转,governor 被反复调用,CPU 占用升高。根本原因是温度在阈值附近抖动。

最直接的解决办法是加hysteresis。如果内核版本支持在 trip 里配,直接加就行。如果不支持,可以拉开多个 trip 之间的间距,让触发和解除落在不同的 trip 上。

另一个办法是调整polling_delay。采样太频繁会放大抖动,适当加大轮询周期能让温度读数更平滑。但也不能太大,否则响应迟钝。

如果抖动来自负载本身的波动(比如视频解码的帧率波动),那就要从 governor 策略上想办法。step_wise每次只动一档,本身就有一定的平滑效果。如果还不行,可以考虑用power_allocator,它基于功耗预算决策,对短期波动不敏感。

6.4 常见问题速查表

现象可能原因排查方法解决思路
温度读数恒为 0传感器驱动未加载/回调错误查 dmesg,手动读 sysfs修复驱动,检查设备树引用
温度读数跳变采样噪声大/无滤波连续读多次看波动范围驱动加滤波,或加大 hysteresis
trip 不触发单位错误/阈值过高检查 temperature 单位确认是毫摄氏度
cooling device 不动状态范围为空/驱动问题手动写 cur_state 测试修正 cooling map 范围
trip 频繁翻转温度在阈值附近抖动看日志翻转频率加 hysteresis,调 polling_delay
降频后温度不降降温力度不够/其他热源看 CPU 频率和温度曲线调整 trip 阈值或状态范围
系统意外关机critical 阈值过低查关机前温度提高 critical 阈值
governor 切换无效governor 未编译进内核查 /sys 下 policy 可写性重新配置内核编译选项

6.5 独家避坑技巧

分享几个我在实际项目中踩过的坑,都是文档里不会写的。

坑一:设备树里的 temperature 单位。这个前面提过,但值得再强调。我见过一个项目,三个人排查了两天,最后发现是 temperature 写成了 70 而不是 70000。建议在 DTS 里加注释标明单位,比如temperature = <70000>; /* 70摄氏度,单位毫摄氏度 */。

坑二:cooling device 注册顺序导致的竞态。如果 thermal zone 在 cooling device 之前注册,zone 初始化时会找不到 cooling device,cooling map 绑定失败。这个错误在启动日志里可能只是一行 warning,很容易被忽略。解决办法是在设备树里保证依赖顺序,或者在驱动里用-EPROBE_DEFER机制延迟注册。

坑三:多核 CPU 的 cooling device 共享。8 核 CPU 如果每个核都注册一个 cooling device,governor 要同时管 8 个,决策开销大且容易不一致。正确做法是用一个 cooling device 代表整个 CPU cluster,内部统一调频。这个在 cpufreq 驱动里通常已经处理好了,但自己写 cooling device 时要注意。

坑四:thermal zone 的 passive 和 active 混用。一个 zone 里同时有 passive trip 和 active trip 时,governor 要能区分处理。step_wise可以,但配置时要注意 cooling map 别绑错。passive trip 应该绑降频设备,active trip 应该绑风扇,绑反了会出现"温度高了先开风扇不降频"的奇怪行为。

坑五:suspend/resume 后的状态恢复。系统休眠再唤醒后,thermal zone 的 polling 可能没有正确恢复,导致温度监控中断。这个在部分内核版本里有 bug,需要在驱动里显式处理PM_POST_SUSPEND事件,重新调度 polling。排查方法是休眠前后对比/sys/class/thermal/thermal_zone0/temp是否还在更新。

7. 从框架到实战:thermal 调优的进阶思路

7.1 用 power_allocator 做精细功耗控制

如果你的平台支持功耗模型,power_allocator是比step_wise更优的选择。它的核心思想是:给 thermal zone 设一个功耗预算(sustainable_power),governor 根据当前温度和功耗模型,计算出每个 cooling device 应该分配多少功耗额度。

配置power_allocator需要 cooling device 提供几个回调:get_requested_power(当前请求的功耗)、state2power(状态到功耗的映射)、power2state(功耗到状态的映射)。CPU 的 cpufreq cooling device 通常已经实现了这些。配置参数通过设备树或者 sysfs 设置:

echo 2000 > /sys/class/thermal/thermal_zone0/sustainable_power

这个 2000 表示 2000 毫瓦的可持续功耗预算。这个值要根据实际散热能力测出来,设太小会过度降频,设太大会压不住温度。

power_allocator的优势在于它能动态分配。比如 CPU 和 GPU 共享一个温度预算,governor 会根据两边的实时功耗需求动态调整,而不是简单地各降一半。这在移动设备上对续航和性能的平衡很有帮助。

7.2 多温区协同的配置策略

复杂一点的平台往往有多个 thermal zone:CPU 一个、GPU 一个、电池一个、充电芯片一个。这些 zone 之间可能相互影响,需要协同配置。

协同的核心是避免降温手段冲突。比如 CPU 和 GPU 可能共享同一个风扇,如果两个 zone 都绑了这个风扇,就可能出现"CPU 让风扇开三档,GPU 让风扇关掉"的冲突。解决办法是用fair_sharegovernor,或者把风扇绑到一个专门的 zone 上,其他 zone 通过影响这个 zone 来间接控制。

另一个协同点是温度传播。CPU 发热会传导到电池,导致电池温度升高。如果电池 zone 的 trip 阈值设得跟 CPU 一样,就会出现 CPU 还没到阈值、电池先触发的情况。合理的做法是电池 zone 的阈值设得比 CPU 低,提前介入。

7.3 调试工具与数据采集方法

thermal 调试离不开数据采集。除了直接读 sysfs,还有几个工具可以用。

thermal-engine或者厂商提供的 thermal daemon 可以记录温度历史,画成曲线。perf和ftrace可以跟踪 governor 的调用频率和耗时。powertop可以看 thermal 相关的唤醒次数。

我自己常用的一个方法是写个简单的 shell 脚本,每秒采集一次温度和 cooling device 状态,输出成 CSV,然后用表格软件画图:

#!/bin/bash echo "timestamp,temp,cdev0_state,cdev1_state" > thermal_log.csv while true; do ts=$(date +%s) temp=$(cat /sys/class/thermal/thermal_zone0/temp) s0=$(cat /sys/class/thermal/thermal_zone0/cdev0_cur_state 2>/dev/null || echo -1) s1=$(cat /sys/class/thermal/thermal_zone0/cdev1_cur_state 2>/dev/null || echo -1) echo "$ts,$temp,$s0,$s1" >> thermal_log.csv sleep 1 done

这个脚本跑一两个小时,数据量足够分析趋势了。看曲线的时候重点关注:温度上升斜率、trip 触发点、cooling device 状态变化点、温度回落速度。这几个点能反映出配置是否合理。

7.4 实操心得:调优的迭代节奏

thermal 调优不是一次性能搞定的,需要迭代。我的节奏一般是这样的:

第一轮,用保守配置(阈值设低一点,降温力度大一点),确保不会过热。这一轮的目标是"安全",不是"性能"。

第二轮,在安全的基础上逐步放宽阈值,观察温度曲线。每次放宽 2 到 3 度,测一轮,找到"刚好不触发 critical"的临界点。

第三轮,优化 governor 和 cooling device 的配合,让降温过程平滑,避免频繁抖动。这一轮主要调 hysteresis 和 polling_delay。

第四轮,做场景化测试。不同的使用场景(待机、轻载、重载、充电)温度表现不同,要分别验证。如果某个场景下表现不好,针对性地调整。

整个迭代过程可能要花几天时间,但这是值得的。thermal 配置不好,轻则性能打折,重则硬件损伤,返修成本远高于调优成本。

7.5 后续可以扩展的方向

thermal framework 本身还在演进。几个值得关注的方向:一是跟Energy Model(能量模型)的整合,让功耗决策更精确;二是跟DPTF(动态平台和热框架)这类用户态框架的配合,实现更复杂的策略;三是针对异构计算(大小核、NPU)的多温区协同调度。

如果你已经把基础的 thermal 配置跑通了,下一步可以研究power_allocator的功耗模型怎么标定,或者看看厂商的 thermal daemon 是怎么跟内核框架配合的。这些内容展开又是另一篇的篇幅了,先把框架的主干吃透,细节可以按需深入。

我个人在实际操作中的体会是,thermal 这东西最怕"想当然"。温度曲线不会骗人,配置改完一定要实测,实测数据一定要记录,记录了一定要对比。凭感觉调参数,十有八九要翻车。另外,不同平台的传感器精度、散热设计、负载特性差异很大,别人的配置只能参考,不能照搬,最终还是要落到自己的板子上一点点试出来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询