news 2026/10/8 1:06:14

深入 Linux Thermal Framework:内核温控子系统架构与实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入 Linux Thermal Framework:内核温控子系统架构与实践

做内核功耗控制这些年,我一直觉得 thermal framework 是最容易被忽视、却最不能出乱子的子系统。CPU 频率调慢一点顶多性能差些,内存频率降一点也卡不死系统,可温度一旦压不住,轻则关机重启,重则硬件老化甚至烧毁。尤其现在笔记本、服务器、嵌入式设备都在往高功耗密度走,芯片厂商给的温控阈值越来越紧,thermal 这一块已经从"后勤保障"变成了"基础设施"。这篇我尽量用做项目时的思路,把 thermal framework 的通用架构从头到尾梳理一遍,看完你至少能回答这几个问题:thermal zone 和 cooling device 是怎么绑到一起的?step_wise 和 power_allocator 到底在算什么?为什么设备树里配了 trip point 系统却不动?以及 thermal 和 cpufreq、devfreq 这些功耗子系统是怎么协作的。


1. thermal framework 为什么要独立存在:它不是 cpufreq 的附属品

很多刚从驱动入门转内核的人,会觉得温控嘛,不就是温度高了把频率降下来,直接挂在 cpufreq 里做不就行了?早期有些 SoC 确实这么干过,厂商在 cpufreq driver 里加一个温度回调,超过阈值就限制 policy 的最大频率。这种方案的缺点很快就暴露了。

1.1 热源不只有 CPU

现代 SoC 里发热的大户至少包括 CPU、GPU、NPU、Modem、DDR 控制器、PMIC 供电路径、充电 IC。CPU 只是其中一路。如果只在 cpufreq 里做温度管理,GPU 那边到 90 度根本没人管,屏幕模组、摄像头 ISP 的发热更是失控。thermal framework 要解决的第一个问题就是:把系统里所有能感知温度的点统一注册成 thermal zone,把所有能降低功耗的器件统一建模成 cooling device,然后通过统一的策略把"温度"映射到"动作"上。

你可以把 thermal zone 想成一个个房间的温控器,cooling device 想成空调、排风扇、窗帘这些执行机构。温控器只知道房间温度升到多少度了,它不管空调是变频还是定频;执行机构也不关心是哪个房间太热,它只响应调节命令。这种解耦是 thermal framework 能通用的核心原因。

1.2 降温手段远不止降频

降 CPU 频率是降温手段,但不是全部手段。以我调过的平台为例,降温执行器包括:

  • CPU 调频(cpufreq cooling)
  • CPU 调压(通过 regulator 限制电压上限)
  • 任务迁移/调度限制(把任务从大核挪到小核)
  • GPU 降频(devfreq cooling)
  • DDR 降频
  • 风扇转速提升
  • 屏幕亮度降低
  • 关闭充电或降低充电电流
  • SoC 内部 AVS(Adaptive Voltage Scaling)调整

这些执行器分布在不同的子系统和驱动里,thermal framework 并不直接操作硬件,它只定义一个标准的冷却设备接口——struct thermal_cooling_device_ops,里面无非是get_max_state、get_cur_state、set_cur_state这几个回调。谁想成为一个 cooling device,谁就注册一组 ops。这样,cpufreq 是一个 cooling device,devfreq 是另一个,风扇驱动是第三个。框架层面完全不关心你降的是什么,只关心"当前冷却等级是多少"和"把冷却等级设到多少"。

1.3 框架层面解决的两个核心模型问题

第一个模型问题:温度变化是连续且滞后的。热容量大的器件,你降频半小时温度才掉下来;热容量小的,几秒就冲上去。控制动作不能只看当前温度瞬时值,要结合温度趋势。step_wisegovernor 就是靠"温度在不在跳变区间"来提前动作的,而power_allocator更是用了 PID 的思想,预测温度趋势来调整功耗预算。

第二个模型问题:多热源、多散热设备之间的耦合。一个大核和一个 GPU 挨得很近,GPU 跑起来会拉高 CPU 的温度传感器读数。你如果只为 CPU 单独配一个冷却设备,那么 GPU 发热导致 CPU zone 触发时,你会莫名其妙地限制 CPU 频率,但真正该管的是 GPU。所以 thermal framework 引入了thermal_instance,允许一个 cooling device 同时被多个 thermal zone 引用,也允许一个 thermal zone 绑定多个 cooling device。这种多对多的映射关系,才是现实中芯片热模型的真实形状。

在这里我必须强调一个很多人忽略的点:thermal framework 做的是"策略协调",不是"热仿真"。它不会去算芯片内部哪个热点温度多高,它拿到的温度已经是硬件 sensor 的最终结果。framework 的职责是根据温度点触发策略,再根据策略调节冷却设备。理解这个边界,你后面看thermal_zone_device_register那一堆参数就不会迷。


2. zone、trip、governor、cdev:四个对象的连接关系拆解

内核里的 thermal 代码虽然分散在drivers/thermal/下,但顶层对象就那么几个。我把它们之间的引用关系画在脑子里,是一张很清晰的图:每个thermal_zone_device维护一个 trip 列表和一个 cdev 绑定列表;每个绑定项是thermal_instance;每个 instance 关联一个thermal_cooling_device;governor 挂在 zone 上,根据 zone 的温度和 trip 状态决定 instance 的 target state。

2.1 thermal zone 的构成

struct thermal_zone_device里最重要的字段,除了 ops 之外,还有:

  • trips:一个struct thermal_trip数组,表示温度阈值。
  • num_trips:trips 的数量。
  • temperature:当前从 sensor 读到的温度。
  • passive_delay和polling_delay:两种轮询间隔。
  • governor:当前挂载的 governor 实例。
  • thermal_instances:绑定列表。

struct thermal_trip包含type(active/passive/hot/critical)、temperature、hysteresis、flags。内核新版本里还加了target字段,可以在 trip 里直接指定冷却目标,不过兼容性还是老一套为主。

注意一个关键点:zone 本身不主动发起轮询温度。它有两种工作模式:一种是 sensor 驱动主动上报温度(thermal_zone_device_update里的THERMAL_TRIP_*事件),另一种是 framework 用thermal_zone_device_check启动一个轮询定时器。具体走哪种,取决于底层 sensor 是中断型还是轮询型。我们 GPIO 型温度传感器一般走轮询,I2C 的商用 sensor 很多支持中断,能省电。

2.2 trip point 不只是四档

规范里把 trip 分成active、passive、hot、critical四种。active 通常对应可以主动打开风扇的阈值,passive 对应不需要用户感知的被动降频,hot 是接近极限要快速动作,critical 就是强制关机。

但实现上,trip 的具体行为完全由 governor 决定,而不是由框架决定。比如 step_wise 遇到 active trip 也可以降频,只要你把冷却设备的绑定方式配置成主动绑到 active trip 上。所以不要死板地认为 active 只用于风扇。在嵌入式里,我们常把第一级 active 设成 CPU 降频的触发点,第二级 passive 设成 GPU + 亮度联动,第三级 hot 设成 CPU 硬限频。一切都是设备树或 platform data 决定的。

2.3 cooling device 与冷却等级

冷却设备的核心是max_state和cur_state。state 为 0 表示不冷却,state 越大表示冷却能力越强(比如频率越低、风扇越快)。cpufreq cooling device 注册时,会自动根据频率表算出可用档位,比如支持 5 个频点,max_state 就是 4。set_cur_state回调里会做一件重要的事:把高频到低频对应的所有 state 都映射到实际频率限制。

这里有个坑:不同次数频点的冷却曲线不一定是线性的。你可能频率从 1.8GHz 降到 1.6GHz 温度掉得很快,但继续降到 1.4GHz 效果就不明显了。step_wise 这种简单 governor 不管这些,它只看 state 数值;power_allocator 则会结合功耗模型来算,所以它在移动端用的多,而且要和 dt 里的dynamic-power-coefficient搭配。

2.4 thermal instance:绑定关系的载体

每次 zone 和 cdev 绑定时,都会创建一个thermal_instance。它记录了:

  • trip:这个绑定关系在哪个 trip 下生效。
  • upper/lower:cdev 在这个 trip 下允许的 state 范围。
  • target:governor 计算结果写入的 state。

一个 cdev 可以出现在多个 instance 里,对应不同的 trip,也可以被多个 zone 使用。最终生效的 state 是取的多个 zone 计算结果中的最大值(保守策略),也就是"谁要求最严就听谁的"。比如你的风扇被 CPU zone 和 GPU zone 同时控制,CPU 侧要求 state 2,GPU 侧要求 state 4,风扇最终走 state 4。

2.5 governor 的挂载方式

系统里的 governor 有很多种:fair_share、step_wise、power_allocator、user_space。zone 的governor_name可以通过设备树属性thermal-governor指定,或者通过 sysfs 的policy节点切换。如果指定的 governor 没注册,内核会回退到一个默认 governor,通常是step_wise。

这里我强烈建议做产品时,把 governor 的选择当成整体功耗策略的一部分,不要无脑选 power_allocator,也不要无脑 step_wise。后面专门讲它们区别。


3. 从代码看注册流程:zone 和 cdev 是怎么"牵手"的

我直接把关键调用链捋一遍,这部分是纯源码视角,能帮你建立"从驱动注册到系统动作"的完整链路。

3.1 thermal zone 的注册流程

底层 sensor 驱动要做的事,核心是填充struct thermal_zone_device_ops:

static struct thermal_zone_device_ops my_sensor_ops = { .get_temp = my_sensor_get_temp, .get_trip_temp = my_sensor_get_trip_temp, .get_trip_type = my_sensor_get_trip_type, .set_trips = my_sensor_set_trips, };

然后调用:

tzd = thermal_zone_device_register( "soc_thermal", // 名字,会在 sysfs 里显示 trips_array, num_trips, devdata, &my_sensor_ops, passive_delay_ms, polling_delay_ms, passive_polling_delay_ms, // 新版本参数 governor_name );

注册后,驱动还要手动做一次thermal_zone_device_update(tzd, THERMAL_EVENT_UNSPECIFIED)触发初始检查。框架内部会为这个 zone 创建 sysfs 目录/sys/class/thermal/thermal_zoneX,里面会出现temp、type、trip_point_*_temp、policy等节点。

有个细节:get_trip_temp是回调,但内核在thermal_zone_get_trip时优先用的是trips数组里的静态值,如果你动态改变 trip,需要调thermal_zone_update_trip。很多驱动在运行时想调整 trip 温度,却只改了内部变量,sysfs 读到的还是旧值,那是因为没有调 update 接口。这是实战里经常踩的坑。

3.2 cooling device 的注册流程

以 cpufreq cooling 为例,驱动里:

cdev = cpufreq_cooling_register(policy);

其中会创建一个struct thermal_cooling_device,ops 来自cpufreq_cooling_ops。如果你想让自己的模块变成冷却设备,比如驱动一个风扇:

cdev = thermal_cooling_device_register("fan0", fan_data, &fan_cooling_ops);

里面其实做的工作是:分配thermal_cooling_device,调dev_register创建 sysfs 设备节点,然后调用__thermal_cooling_device_register,再走thermal_zone_device_register的内部绑定逻辑。

注意:在老版本内核里,cdev 注册完不会自动绑定 zone,需要 zone 驱动显式调thermal_zone_bind_cooling_device。新版本里如果你用设备树中的cooling-maps,of 接口会自动完成绑定。手动绑定与自动绑定可以混用,但有冲突风险,我见过 both 方式导致的重复 instance,后文会写排查方法。

3.3 设备树绑定一图流(无 mermaid)

我给你一个典型的设备树配置结构,它描述了一个 SoC 内部热区:

soc_thermal: thermal-zones { cpu_thermal: cpu-thermal { polling-delay-passive = <250>; polling-delay = <1000>; thermal-sensors = <&tsens0 0>; trips { cpu_alert0: cpu-alert0 { temperature = <65000>; hysteresis = <2000>; type = "passive"; }; cpu_crit: cpu-crit { temperature = <95000>; hysteresis = <2000>; type = "critical"; }; }; cooling-maps { map0 { trip = <&cpu_alert0>; cooling-device = <&cpu_cooling0 0 2>; }; map1 { trip = <&cpu_alert0>; cooling-device = <&gpu_cooling 0 1>; }; map_cluster { trip = <&cpu_alert0>; cooling-device = <&cpu_alert0>; }; }; }; };

cooling-device三元组表示为<phandle, 最低state, 最高state>。比如<&cpu_cooling0 0 2>允许 state 0 到 2,如果 governor 算出 target=3,会被夹到 2。这种方式可以灵活限制某个 trip 下冷却设备的参与程度。

关键点:thermal-sensors 引用的 sensor 节点,必须在驱动里实现 OF thermal helper 接口:devm_thermal_of_zone_register等。如果你的 sensor 驱动没有实现get_temp回调,zone 启动后会一直是非法温度,毫无动作。

3.4 注册顺序与 deferred probe

有一个常见的启动日志:thermal thermal_zone0: failed to find thermal zone。这类问题多半是cooling device 注册晚了。设备树解析时,zone 会尝试 bind 所有 cooling-maps 里的 phandle,如果对应的 cooling device 还没注册,of 绑定流程会返回 -EPROBE_DEFER,zone 注册会推迟。但如果你在 driver 里用thermal_zone_bind_cooling_device手动绑定,就要注意调用时机,最好放在component_late_probe或late_initcall里,否则同样会失败。

我在调试时习惯在启动日志里搜thermal关键字,看每个 zone 是否成功创建,以及 cdev 是否成功注册。一个健康系统的日志大概是:

thermal_sys: registered thermal governor 'fair_share' thermal_sys: registered thermal governor 'step_wise' thermal_sys: registered thermal governor 'power_allocator' thermal_sys: registered thermal governor 'user_space' thermal_sys: registered thermal zone 'cpu-thermal'

如果少了某个 governor,多半是内核 config 没开CONFIG_THERMAL_GOV_POWER_ALLOCATOR之类的选项。


4. governor 到底在算什么:step_wise、fair_share 与 power_allocator 的实际差异

很多人把 governor 想得太玄。其实它们就干三件事:读 zone 温度,比较 trip 阈值,然后决定绑定的 cdev 该设到哪个 state。差别在于"怎么比较、怎么决定"。

4.1 step_wise:查表式的趋势判断

step_wise是默认 governor,也是最直观的。它维护每个 trip 的状态:THERMAL_TRIP_*是否被触发,还会根据温度是上升还是下降,给出THERMAL_TREND_RAISING、THERMAL_TREND_DROPPING或THERMAL_TREND_STABLE。核心算法在step_wise的throttle函数里。

它有一个关键概念叫"上一个 trip 的 target"。算法会找到当前温度所在的区间,设在区间内所有已触发 trip 对应的 instance 上:

  • 如果温度上升且越过 threshold,在原有 state 基础上加 1。
  • 如果温度下降且低于 trip 的 hysteresis 回差,在原有 state 基础上减 1。
  • 一次更新最多加 1 或减 1,不会直接跳满。

所以 step_wise 天然具备"缓慢升档、缓慢降档"的特性。加 1 减 1 的节奏由polling-delay决定。比如你 passive 轮询间隔 250ms,每轮最多升一档,那么从 state 0 升到 state 4 需要 1s。这是好事,避免温度在阈值附近来回抖动导致频率疯狂波动。

不过 step_wise 的缺点是它完全没考虑功耗模型。一个 cdev 从 state 0 到 1 可能降低了 500mW,但从 state 3 到 4 可能只降低 50mW,它不在乎。所以 CPU 的 cpufreq 曲线通常用 step_wise 问题不大,但对功耗敏感的手机平台,它不够精细。

4.2 fair_share:按权重摊派指标

fair_share逻辑更简单:每个 instance 有一个contribution权重(通常在代码里写死,或通过 thermal_instance 里的 weight 设定)。它根据当前温度超过第一级 trip 的比例,把总降温需求按权重分摊到各个 cdev。

举个例子:zone 里有 A、B 两个 cdev,权重分别为 70 和 30。如果温度超过 trip 的比例是 40%,那么 A 需要承担 28% 的冷却能力,B 承担 12%。具体换算成 state 时,按各自max_state乘一下比例取整。

这个 governor 在 PC 主板行业还有一定使用率,但在消费级 SoC 里我用得很少,因为它对传感器滞后误差的容忍度低,容易产生过冲。适合温度响应快、冷却设备线性度好的场景。

4.3 power_allocator:把温度问题翻译成功率预算问题

power_allocator是这几年的重点。它的核心思路是:既然温度是功耗积分得来的,控制温度不如直接控制功耗。它先给 zone 一个可允许的最大功耗sustainable_power(通常在设备树节点属性里写,比如<&cpu_thermal 2500>),然后通过一个 PID 控制器算出当前功耗预算需要调整多少:

err = temp - switch_on_temp; power_range = sustainable_power + Kp * err + Ki * integral(err) + Kd * derivative(err);

然后对预算内的 cdev 按各自的power actorAPI 分配实际功率值。cpufreq 的 power actor 会根据动态功耗系数dynamic-power-coefficient(单位 uA/MHz/V^2 等)反推出允许的频率上限。

使用 power_allocator 的前提是:

  1. 必须实现power2state或get_requested_power等 power actor 回调。
  2. 设备树里要有dynamic-power-coefficient。
  3. zone 的sustainable_power要调得准,否则 PID 会震荡。

我自己的经验是,power_allocator 不是拿来即用的。它需要花时间整定k_po、k_pu、k_i这些 PID 参数(在 dt 的trips子节点里可配置)。如果参数不对,温度会像过山车。在快速交付的项目里,我会先上 step_wise 跑通,再去细调 power_allocator。

4.4 选择 governor 的实战建议

下表是我的经验总结,仅供参考:

场景推荐 governor原因
服务器 CPU 温控step_wise稳定、可预测、性能影响可控
手机/平板 SoCpower_allocator需要精细功耗预算,延长续航
工业设备风扇控制fair_share多风扇均衡
调试阶段或实验室user_space所有决策丢给用户态,方便观测
车载多热源耦合step_wise + 自定义冷却映射避免牵一发动全身

当然,内核里还允许你写自己的 governor,注册方式就是实现thermal_governor结构体然后thermal_governor_add。但多数项目改参数就够了,没必要造新轮子。


5. 设备树与驱动适配:如何落地一个可靠的 thermal 方案

前面说了理论,这里写实际配置和排障。我在不同平台调过 thermal,踩坑最深的往往不是 governor 算法,而是设备树配置和驱动回调的细节。

5.1 thermal zone 的 polling-delay 到底怎么设

很多初学者困惑polling-delay-passive和polling-delay的区别。其实规则是:

  • polling-delay:zone 未触发任何主动降温 trip 时的轮询间隔,单位毫秒。这个值可以很大,比如 1000 甚至 5000,因为常温下不需要频繁读。
  • polling-delay-passive:zone 触发 passive 及以上 trip 后的轮询间隔。要小,比如 100~500ms,保证响应速度。

如果你的 zone 只有一个恒温空调场景,没有被动降温,那polling-delay-passive不会被用到。但如果你的冷却机制是降频,一定要设置polling-delay-passive,否则温度越过阈值后还是用大间隔轮询,反应迟钝,可能导致瞬间冲高到 critical。

我见过一个项目把polling-delay设 100ms,polling-delay-passive没写,结果默认是 0,内核用默认值 1000ms 轮询。温度从 60 度跑到 90 度花了 3s,系统没来得及降频就被 hot 保护了。这种问题不会报警,只看温度曲线才发现。

5.2 critical trip 不能只靠 framework

critical trip 指的是当温度达到阈值时,framework 会调用orderly_poweroff尝试关机。但注意:这个动作不是原子级别的,如果温度和硬件安全上限只差一点,建议更早设置 hot trip,并让 hot trip 触发一个最激进的冷却动作(比如瞬间把 CPU 限到最低频、关闭 GPU)。critical 是最后一道闸,不要指望它能优雅保存数据。

另外,critical trip 的温度设定要留足余量。考虑 sensor 精度 ±5 度,器件温度与 sensor 读数的差异也可能有几度。比如芯片结温最大 105 度,你设 critical 到 100 度其实很危险,最好设 95 度,hot 设 90 度。

5.3 动态 trip 温度调整

量产产品常碰到一个问题:同型号不同批次的 sensor 校准偏差不同,或者不同 SKU 的散热条件不同。这时不该改设备树,而应该在 driver 里根据 eFuse 校准值动态调整 trip 温度。

代码可以这样:

struct thermal_trip *trip = thermal_zone_get_trip(tzd, trip_id); trip->temperature = new_temp; thermal_zone_update_trip(tzd, trip, trip_id);

thermal_zone_update_trip会重新计算 zone 状态并通知 governor。注意别在中断上下文里调用,因为里面可能触发 governor 的 throttling,需要可睡眠锁。

另外,sysfs 里trip_point_0_temp是可写的(如果你的 ops 没有锁定),写进去后内核会自动调set_trip_temp。很多自动化测试脚本就是靠这个做 thermal 测试的,不用改固件就能模拟不同温度点。

5.4 多 zone 多 cdev 互相争抢的问题

当两个 zone 共用一个 cdev 时,比如电池温度 zone 和 CPU zone 都管 cpufreq,可能出现"电池明明没发热,只是 CPU 自己热,结果 CPU 被压得很低"的情况。解决办法是:不同 zone 对 cdev 设不同的upper/lower范围,并把governor的敏感度调低。或者用weight机制让某 zone 对 cdev 的影响权重变小。

在内核新版本里,thermal_instance有个weight字段,配合 power_allocator 时会按权重来计算功率分配。如果你用的是 step_wise,weight 无效,那就只能用上下限控制了。

5.5 调试 thermal 的常见命令

我调试时几乎离不开这几个节点:

cat /sys/class/thermal/thermal_zone*/temp cat /sys/class/thermal/thermal_zone*/type cat /sys/class/thermal/cooling_device*/cur_state cat /sys/class/thermal/cooling_device*/max_state cat /sys/class/thermal/thermal_zone*/policy

还可以临时改 policy:

echo "power_allocator" > /sys/class/thermal/thermal_zone0/policy

想观察 governor 内部状态,开启内核 trace:

trace-cmd record -e thermal trace-cmd report

thermal的 tracepoint 会输出 zone 温度变化和 trip 触发事件,是定位"为什么没降频"的第一手资料。


6. thermal 与功耗子系统的协作:从被动降温到主动功耗预算

前面讲的大都是"温度高了再去降频"的闭环。但从功耗系统视角看,thermal 还有更高级的玩法:在温度还没升高之前,就把功耗预算控制住。这就涉及到和 cpufreq、devfreq、PM QoS、energy model 的协作。

6.1 cpufreq cooling 与 cpufreq governor 的互动

cpufreq cooling device 的set_cur_state本质上是在调用cpufreq_update_policy,把 policy 的max_freq限制到某个档位。但它不会干预 cpufreq governor 的内部选频逻辑,只是把可用的最高频点封顶。这保证了 thermal 的介入不会影响调度器的性能预期,也不会破坏 schedutil 的频率爬升逻辑。

这类限制是有"惯性"的:一旦max_freq被限制,即使温度降下来,step_wise 也要等温度低于 hysteresis 后才逐级恢复,这是有意防止频率抖动。但极端场景下,用户可能觉得性能迟迟不恢复,这就要看你的降级步长和轮询间隔是否合理。

6.2 devfreq cooling:GPU/DDR 的温控路径

GPU 和其他 devfreq 设备的冷却注册类似:

dcd = devfreq_cooling_register(devfreq);

它会根据 devfreq 的频率表建立冷却 state。因为 GPU 没有 schedutil 那么精细的调频逻辑,冷却映射相对简单:state 每加一档,频率往下降一档。power_allocator对 devfreq 的功率预测通常依赖dynamic-power-coefficient,这部分在新版内核里有现成的 power actor 实现,老版本需要自己补。

6.3 PM QoS 与 thermal 的关系

PM QoS 分 CPU 和 device 两类限制。thermal 可以通过PM_QOS_CPU_DMA_LATENCY或PM_QOS_FREQ来约束硬件行为,不过它一般不直接调 PM QoS,而是通过 cooling device 的用户态接口或自定义驱动间接使用。如果你在写自定义冷却设备,想要"温度高时限制 DMA 延迟预算",可以考虑在set_cur_state里更新 PM QoS 请求。

6.4 用户态 thermal 和 thermal netlink

user_spacegovernor 会把决策权交给用户态。老方式是 sysfs 里trip_point_0_type写成 "user_space",并监听uart或轮询 temp 节点。新内核提供thermal_netlink组播事件,应用层可以通过 netlink 订阅温度变化、trip 触发、cdev 状态变化等事件。这个接口对实现"智能温控策略"非常有用,比如手机上的游戏模式时,用户态收到温度告警后,直接调整 GPU 频率上限、屏幕刷新率、充电功率,而不是让内核手动限频。

我的建议是:能放在内核里的决策不要上用户态。因为用户态进程可能被调度延迟,关键 cooling action 一旦延迟几百毫秒,温度可能已经越过临界。用户态更适合做宏观策略(切模式、弹提醒、调充电),微观 throttle 交给 kernel governor。

6.5 energy model 与 thermal 的未来趋势

近年内核引入了能源模型(Energy Model)框架,drivers/thermal也开始和 EM 结合,power_allocator可以通过 EM 更准确地估算每个 device 在不同频率下的功耗。未来的 thermal 会往"预测式"发展:根据任务负载预测未来几秒的功耗和温度,提前调整预算,而不是等温度升上来再反应。这仍然是个活跃演进的方向,但对普通产品研发来说,理解当前的通用架构已经足够解决绝大多数工程问题。


7. 一些实战排查经验,按症状逐个说

最后这一段,我不写完整章节了,直接把我这几年调过的典型问题清单列出来,每条都是一次真实的排障记忆,希望帮你少走弯路。

问题 1:温度明明到了 trip,cdev 不动。

先查两点:一是cooling-maps里 trip 对应的 phandle 对不对,二是 cdev 的upper/lower范围是不是把 target 卡死了。我有一次就是cooling-device = <&cdev 0 0>,上限也是 0,那 governor 怎么算都不可能让它动。

问题 2:zone 的 temperature 一直读 0。

大概率是 sensor 驱动没实现get_temp,或者 I2C 通信失败。还有可能是设备树thermal-sensors的 phandle 指向了父节点,而不是具体的 sensor 子节点。用cat /sys/class/thermal/thermal_zone0/temp看一眼,如果报错就查 dmesg。

问题 3:降温恢复太慢,性能损失严重。

把polling-delay-passive调大一点试试,然后看 step_wise 的下降方向判断逻辑是否被 hysteresis 卡住。hysteresis 设太大,会导致温度已经降到阈值以下很多才恢复,设太小又会在阈值附近反复抖动。经验值:传感器噪声大的设 2000~3000,噪声小的设 1000。

问题 4:开机阶段 thermal 报错导致启动失败。

常见是cooling-device的 phandle 指向一个还没 probe 成功的设备。检查日志里的deferred probe信息,必要时把thermal模块的late_initcall优先级调低。不要在module_init里做绑定。

问题 5:critical trip 触发后系统反复重启。

我建议在关机前加一个用户态 hook,比如让thermal_zone的 hot trip 提前触发一个reboot命令而不是poweroff,或者至少把关键日志持久化。否则 critical 关机后用户无法分析原因。内核提供了thermal_trip_notify回调,可以在里面加打印或 panic dump。

thermal framework 说到底是一个"温度事件分发 + 冷却能力调度"的框架。你不需要把每个 governor 的数学推到极限,但只要能把握住 zone、trip、cdev、instance 这四个对象的协作关系,再配合设备树和 sysfs 做验证,绝大多数平台的温控问题都能在半小时内定位到根因。写这篇的目的,就是帮你把这半小时缩短成五分钟。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 1:06:13

神经3D网络渲染器实战:单图像三维重建从原理到代码

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/8 1:06:10

TPS259483+PIC24FJ256GA705构建工业级电源主动防护系统

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/8 1:05:52

用Python和dlib自建人脸识别系统:从录入到比对全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/8 1:05:51

TPS259483AYWPR与R7FA8D1BHECBD构建嵌入式电源韧性系统

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/8 1:05:05

工业级电源路径保护:TPS259483AYWPR与PIC32MZ协同设计实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/8 1:05:05

Hadoop+Spark信贷风控系统源码实战:架构、部署与调优

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华