☰
Linux内核thermal framework架构解析与温控调试实践指南
2026/10/8 19:09:55 网站建设 项目流程

1. 先理解 thermal framework 到底在管什么

做嵌入式 Linux 或手机 SoC 相关开发的朋友,大概率都遇到过"机器突然卡了一下"、"机身发烫后性能明显下降"这类现象。背后的主角就是内核里的 thermal framework,中文叫法挺多:热管理框架、温度控制框架、温控子系统。它要解决的事情其实非常朴素:在芯片发热和用户体验之间找一个平衡点。

CPU 跑得越猛,功耗越高,发热越快。如果不做任何干预,芯片温度一旦突破结温上限,轻则降频掉性能,重则直接 reboot,甚至烧毁硬件。thermal framework 干的事就是:通过温度传感器实时感知芯片温度,当温度超过预设阈值时,主动限制 CPU/GPU 等发热源的频率或电流,让温度回落。这一套机制,在手机里叫"温控",在服务器里叫"功耗管理",在车载和工业设备里叫"热保护",本质是同一套骨架。

这个框架适合谁深入了解?我的建议是这几类人:

  • 做 BSP 或内核移植的工程师,需要在新的板卡上把温度传感器和 cooling device 接进系统。
  • 做性能调优或功耗优化的同学,经常要回答"为什么跑分前几分钟是满频,后面就锁频了"这类问题。
  • 排查稳定性问题的工程师,遇到"高温导致重启/死机"的 case 时,需要知道从哪里下手抓证据。
  • 单纯想搞懂 Linux 内核如何管理硬件的爱好者。

这篇文章我会按自己的理解,把 thermal framework 的通用架构拆开讲清楚:涉及哪些核心数据结构、驱动怎么接入、governor 怎么选、用户态有哪些接口可以观测,以及我实际调试中踩过的一些坑。

2. thermal framework 的通用模型与设计思路

开始看代码之前,我建议先理解框架的设计思想。Linux 内核里很多子系统都是"面向对象"的思路用 C 语言实现,thermal 也不例外。它的核心抽象可以归纳成三样东西:温度来源(thermal zone)、散热手段(cooling device)、控制策略(governor)。

2.1 为什么抽象成 zone + cooling device + governor

先说个生活化的类比。把整个 thermal framework 想象成家里的空调系统:

  • 温度传感器就是房间里的温度计,对应 thermal zone。
  • 空调、风扇、窗帘、加湿器这些能改变环境状态的设备,对应 cooling device。
  • 你设定"26 度最舒服,超过 27 度开空调,超过 28 度再拉窗帘"这套逻辑,对应 governor。

这个类比虽然不完全严谨,但能帮初学者快速建立印象:thermal framework 不是一个"直接调频率"的控制器,而是一个分层协作的系统。温度数据向上上报,策略模块决定怎么控,控制指令向下发给具体设备。

从代码层面看,这个设计带来了几个很实际的好处:

  • 传感器驱动和散热驱动解耦。温度传感器的厂商不用关心你的散热风扇是 PWM 控制还是 GPIO 开关;风扇驱动也不用关心温度数据从哪来。两边各写各的,最后在 device tree 里通过 phandle 关联起来就行。
  • 控制策略可插拔。同样的温度数据和同样的散热资源,你可以用 step_wise 那种"温和地一点点降频",也可以用 power_allocator 那种"直接根据功耗模型算目标频率"。策略替换不影响驱动代码。
  • 用户态和内核态数据流通。thermal 框架把很多关键信息暴露在 sysfs 里,调试时不用上 JTAG,cat 一下文件就能看到温度、当前生效的策略、各 trip 点的状态。

2.2 thermal_zone_device:温度区的"身份证"

内核中每个温度控制域,都要注册成一个struct thermal_zone_device。可以理解为:一个 zone 就是对某个 IC 或某个区域温度的抽象。芯片面积大、热点分散的 SoC,往往会有多个 zone,比如 CPU zone、GPU zone、电池 zone。

结构体比较长,我只挑核心字段说:

struct thermal_zone_device { char type[THERMAL_NAME_LENGTH]; /* 名字,对应 sysfs 里的 type */ struct device *device; /* 内嵌的 device 结构 */ struct thermal_zone_device_ops *ops; /* 操作集:读取温度、设置模式等 */ struct thermal_zone_params *tzp; /* 与 governor 相关的策略参数 */ struct list_head thermal_instances; /* 挂在这个 zone 下的 trip_instance */ struct thermal_governor *governor; /* 当前绑定的 governor */ struct thermal_zone_trip *trips; /* 全局 trip 表 */ int num_trips; /* trip 数量 */ int temperature; /* 当前温度,单位通常是毫摄氏度 m℃ */ int passive_delay; /* 被动冷却时的轮询间隔 */ int polling_delay; /* 常规轮询间隔 */ bool passive:1; /* 标记是否处于 passive 状态 */ ... };

需要特别提醒:temperature字段在内核里绝大多数情况下是毫摄氏度,不是摄氏度。我在早期调试时直接用cat /sys/class/thermal/thermal_zone0/temp看到输出 52000,一度以为是 52000 度。其实那是 52000 m℃,也就是 52℃。这个单位差异最容易让人误判,后面排查问题会专门提。

trips 是什么?这是 thermal framework 里最核心的概念之一。一个 trip 可以理解为一个"温度阈值 + 对应动作"的节点。比如你设定:

  • trip0:温度到 60℃,开始限制 CPU 频率。
  • trip1:温度到 75℃,把 GPU 频率拉低。
  • trip2:温度到 90℃,触发 critical 关机保护。

每个 trip 有自己的温度阈值、类型(passive/active/hot/critical)以及绑定的 cooling device。在 device tree 里定义 trip 点,然后由 driver 读取解析,这是目前主流 SoC 平台的做法。

2.3 thermal_cooling_device:散热装置的"遥控器"

再说struct thermal_cooling_device,它就是框架对"能降温的东西"的统一抽象。CPU 调频是一个 cooling device,GPU 调频也是一个,风扇转速控制也是一个,甚至连"降低屏幕亮度"这种操作都能注册成 cooling device——因为它确实能降低整机功耗和发热。

关键操作集如下:

struct thermal_cooling_device_ops { int (*get_max_state)(struct thermal_cooling_device *cdev, unsigned long *state); int (*get_cur_state)(struct thermal_cooling_device *cdev, unsigned long *state); int (*set_cur_state)(struct thermal_cooling_device *cdev, unsigned long state); };

这套接口有一个非常直观的映射:state表示"冷却等级"。以 CPUfreq cooling device 为例,state=0 表示不限制,state 越大限制越狠,state 拉满就是最低频率。governor 算出一个目标 state,然后调用set_cur_state下发。get_max_state告诉框架你最多有几档,get_cur_state告诉框架当前在哪一档。

这里有一个概念特别容易混淆:thermal cooling device 和 cpufreq 驱动的关系。cpufreq 驱动负责管理 CPU 的调频策略,但 cpufreq-cooling 是在此之上的一层"包装",把"频率档位"映射成"冷却等级"。它不会直接修改 cpufreq 的策略,而是通过内核接口请求调整频率。这种套娃式设计,保证了 thermal 模块不需要知道底层 CPU 是通过 dvfs 调频还是通过 clock 调频,只需要发出"降频"这个指令即可。

2.4 thermal governor:中间的大脑

governor 是 thermal framework 的决策中枢,它决定"温度到了 trip 点之后,应该把哪些 cooling device 调到哪一档"。内核里有几种内置 governor,在不同场景下取舍逻辑差别很大。

Governor策略特点适用场景
step_wise温度超过 trip 阈值后,按步进逐步上调冷却等级,回落后逐步下调通用场景,逻辑简单,收敛慢,但稳妥
fair_share每个 cooling device 按权重分摊散热需求多个散热设备协同工作
bang_bang类似"开关式"控制,到了阈值就开启,降到阈值就关闭风扇、加热器等两态设备
power_allocator基于功耗模型做预测控制,动态分配各设备的功率预算对温度/性能要求高的移动设备
user_space温度上报到用户态,由用户态程序来决定怎么操作定制化温控策略

关于 governor 的选择,我个人的经验是:不要盲信默认配置。很多开发板默认用的是step_wise,但在移动产品上体验并不好——它的响应速度偏慢,可能出现"温度已经冲到 90℃ 了,冷却等级还在逐级爬升"的情况。power_allocator响应更快,但需要调好 PID 参数和功耗模型系数,否则很容易产生震荡。后面我会单独写一节排查 governor 行为异常的思路。

3. 从驱动注册到策略生效:核心细节拆解

这部分进入实操层面。我们以最常见的"SoC 内部温度传感器 + cpufreq cooling device"组合为例,走一遍完整链路。

3.1 温度传感器驱动如何注册 thermal zone

如果是传统的"独立 sensor driver"模式,驱动里主要做三件事:

  1. 调thermal_zone_device_register()注册 zone。
  2. 实现struct thermal_zone_device_ops里的get_temp回调。
  3. 实现 trip、绑定 cooling device。

一个最小化的注册流程(代码风格摹仿内核驱动,仅示意):

static struct thermal_zone_device_ops my_thermal_ops = { .get_temp = my_sensor_get_temp, .get_trip_type = my_sensor_get_trip_type, .get_trip_temp = my_sensor_get_trip_temp, .set_trip_temp = my_sensor_set_trip_temp, }; static int my_sensor_probe(struct platform_device *pdev) { struct thermal_zone_device *tz; ... tz = thermal_zone_device_register("my_sensor_zone", num_trips, trip_table, &my_thermal_ops, NULL, 0, 0, 0, &pdev->dev); if (IS_ERR(tz)) return PTR_ERR(tz); ... }

这里有一个只需要写一次的陷阱:新版内核推荐用 device tree +thermal_zone_of_sensor_register()的方式注册,而不是直接调thermal_zone_device_register()。因为前者会把 trip、cooling device 的绑定关系全部放在 DT 里描述,驱动代码干净很多,而且不用自己解析 trip 数据。总线级传感器(比如 IIO 框架下的温度 ADC)通常用devm_thermal_zone_of_sensor_register()这个都是 devm 管理的版本,记得用带devm_前缀的 API 可以少写一大段 remove 逻辑。

3.2 cooling device 注册:cpufreq 是个典型例子

散热设备驱动调thermal_cooling_device_register()注册冷却设备。对于 CPU 调频场景,内核已经封装好了一个现成的 helper:

struct thermal_cooling_device *cpufreq_cooling_register(struct cpufreq_policy *policy);

这个函数会根据 CPU 的 frequency table 自动计算出冷却档位。举个例子,某 CPU 支持 1.8GHz、1.4GHz、1.0GHz、600MHz 四档,那么 cooling state=0 对应 1.8GHz,state=3 对应 600MHz。注册成功后,在 sysfs 里就能看到多了一个cooling_device0。

如果自己写的 driver 要注册 cooling device,需要提供三件套:get_max_state、get_cur_state、set_cur_state。框架本质上不知道你是"调频"还是"PWM 调风扇转速",只看你怎么实现这三个函数。我自己调过一个风扇驱动,set_cur_state 里做的就是根据 state 值调整 PWM 占空比,非常直接。

3.3 trip 和 thermal_instance:绑定关系的核心枢纽

这里要引入第四个重要结构:struct thermal_instance。它很关键,但很多讲 thermal 的文章不讲它。

一个 cooling device 可能被多个 trip 引用。比如 CPU 这个 cooling device,既在 60℃ 的 trip 里被征用(降一档),又在 75℃ 的 trip 里被征用(降到最低)。那么 CPU cdev 上就要挂两个 instance,分别对应两个 trip。thermal_instance就是这一层"绑定关系"的具体载体,里面包含了:

  • struct thermal_zone_device *tz;属于哪个 zone。
  • struct thermal_cooling_device *cdev;对哪个 cooling device。
  • struct thermal_trip *trip;对应哪个 trip。
  • unsigned long upper, lower;这个冷却设备在当前 trip 下允许的上下限。
  • unsigned long target;governor 算出来的目标 cooling state。

governor 工作时,实际上就是遍历 zone 下所有thermal_instance,根据温度、trip 阈值、每个 instance 的上下限,逐个调用thermal_instance_set_target去调整冷却等级。

这个结构让"一个 zone 管理多个传感器、多个散热设备、多个 trip 阈值"成为可能。比如服务器上"CPU zone"同时挂着 CPU cooling device 和风扇 cooling device,trip0 到 trip2 管控 CPU,trip3 到 trip5 启动风扇全速,这些都是靠多个 instance 组合实现的。

3.4 核心工作流程:轮询、中断还是主动上报

了解了这些结构之后,可以串一下 thermal framework 的大致运行流程了。

温度数据从 sensor 到 governor 的路径,常见有两种模式:

轮询模式。框架会创建一个工作队列,按照passive_delay或polling_delay周期性地调 sensor 的get_temp,更新tz->temperature,然后把新温度交给 governor 评估。这种模式实现简单、兼容性好,缺点是温度采样有延迟、系统会周期性唤醒,白白耗电。很多低功耗产品对 polling 很敏感,因为 CPU 每 100ms 醒一次做个温度读取,累加的功耗不小。

中断模式。传感器在温度越过阈值时主动触发中断,框架通过中断处理函数读取温度并触发 governor。这种模式的实时性好,也不会周期性耗电。但硬件门槛高,很多温度传感器没有中断输出,或者中断阈值配置复杂。内核里 thermal 的thermal_zone_trip_updated()和__thermal_zone_set_trips()这套机制就是为中断模式准备的,它会在温度变化时动态更新比较器阈值,确保中断只在跨阈值时触发。

两种模式在 sysfs 里的表现差异很明显:轮询模式下temp文件的值会周期性变化;中断模式下,如果温度平稳,文件长时间不动也是正常的。调试时可以用这个特征快速判断驱动用的是哪种模式。

4. 实操:从零观测一个运行中的 thermal 系统

说一千道一万,不如跑一遍。如果你手头有一块运行 Linux 的板子(树莓派、RK 开发板、x86 工控机都行),可以跟着下面操作,观测 thermal framework 的真实状态。

4.1 先看 sysfs:系统里有哪些 zone 和 cooling device

连上串口或 SSH,进入系统后敲:

ls /sys/class/thermal/

典型输出类似:

cooling_device0 cooling_device1 cooling_device2 thermal_zone0 thermal_zone1 thermal_zone2

每一台机器的布局不一样。x86 上常能看到thermal_zone0是 ACPI 的处理器温度,thermal_zone1可能是芯片组。ARM 开发板上则往往是 SoC 内部 sensor。

接着看 zone 的具体信息:

cat /sys/class/thermal/thermal_zone0/type cat /sys/class/thermal/thermal_zone0/temp cat /sys/class/thermal/thermal_zone0/mode cat /sys/class/thermal/thermal_zone0/policy
  • type:zone 名字,比如cpu-thermal、gpu-thermal。
  • temp:当前温度,单位是 m℃。
  • mode:enabled或disabled,disabled 时框架不做任何温控。
  • policy:当前绑定的 governor 名字,比如step_wise。

再挨个看 trip 点:

cat /sys/class/thermal/thermal_zone0/trip_point_0_temp cat /sys/class/thermal/thermal_zone0/trip_point_0_type

能看到每个 trip 的温度阈值和动作类型。这里要小心一点:新内核里 trip 目录结构和旧内核不太一样,有些内核把 trip 信息拆成了trip_point_*_temp目录下的属性文件,还有些用的是thermal_zone_trip目录,但基本思路一致——一个 trip 一个目录,里面有temperature和type两个属性。

4.2 观察 cooling device 的冷却动作

接着看 cooling device 的档位变化:

cat /sys/class/thermal/cooling_device0/type cat /sys/class/thermal/cooling_device0/max_state cat /sys/class/thermal/cooling_device0/cur_state

如果你想模拟一次温控过程,可以手动把cur_state拉高,比如:

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

注意,这个操作通常会立即调低 CPU 频率(如果你改的是 cpufreq cooling device)。用cat /proc/cpuinfo或者cpufreq-info观察频率变化,能直观看到 thermal framework 对散热设备的控制效果。测完记得echo 0 > .../cur_state恢复。

其实这是我很推荐的一个验证方法:先手动操作 cdev,确认散热设备本身工作正常,再回过来查 governor 和 trip 的联动逻辑。我排查过不少 case,最后发现底层风扇设备根本不转或 CPU 频率没降,属于 cdev 驱动自身的问题,跟 thermal framework 半毛钱关系都没有。

4.3 设备树里 thermal zone 的定义示例

如果你的平台用 DT 描述 thermal 配置,thermal-zones节点里通常长这样:

thermal-zones { cpu_thermal: cpu-thermal { polling-delay-passive = <250>; polling-delay = <1000>; thermal-sensors = <&cpu_temp_sensor>; trips { cpu_alert0: cpu-alert0 { temperature = <60000>; hysteresis = <2000>; type = "passive"; }; cpu_alert1: cpu-alert1 { temperature = <75000>; hysteresis = <2000>; type = "active"; }; cpu_crit: cpu-crit { temperature = <95000>; hysteresis = <0>; type = "critical"; }; }; cooling-maps { map0 { trip = <&cpu_alert0>; cooling-device = <&cpu0 0 2>; }; map1 { trip = <&cpu_alert1>; cooling-device = <&cpu0 3 3>; }; }; }; };

几个细节值得说一下:

  • cooling-device = <&cpu0 0 2>的三个参数分别是:phandle、最小 state、最大 state。map0绑定时允许 cpu0 这个 cdev 在 0~2 档之间调整,map1允许拉到第 3 档。这是"分 trip 分级限频"的典型写法。
  • hysteresis是迟滞区间。如果没有它,温度在阈值附近抖动时,governor 会反复横跳,一会儿限频一会儿恢复,用户体验很差。有了 2℃ 的迟滞,温度要降到 58℃ 以下才会解除 throttling,降到 58℃ 以上时再次触发。正确理解 hystersis 的语义比调大 max_state 更重要。
  • type = "critical"的 trip 是系统的最后一道保险。温度超过它之后,框架会执行紧急关机流程,保护硬件。千万不要在生产固件里去调高或删除它,除非你确定硬件学过设计外的散热余量。

4.4 用 trace 和 debugfs 抓运行时信息

系统跑起来之后,光看 sysfs 的静态值还不够,我们需要动态观察 framework 内部的决策过程。

内核提供了 thermal 相关的 tracepoint,可以用trace-cmd或perf抓:

trace-cmd record -e thermal temperature trace-cmd report

可以抓到温度更新、trip 触发、cdev target 变化等事件,对分析"为什么限频了""哪一步卡住了"很有帮助。tracing 的详细输出取决于内核版本,但thermal_temperature、thermal_zone_trip这类 trace event 长期稳定存在。

另外一个调试手段是 debugfs。内核配置开启CONFIG_THERMAL_DEBUGFS后,在/sys/kernel/debug/thermal/下能看到每个 zone 和 cooling device 的运行时信息,包括当前 governor 计算出的 target state。不过这个选项不是所有内核都默认开,需要自己编内核时打开。

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

这部分是这篇文章的重点,我把这些年实际调试 thermal 问题踩过的坑整理了一下,按问题类型分块讲。

5.1 温度读数明显不对:单位、换算和 sensor 自身误差

症状:temp文件读出 80000,摸芯片外壳感觉也就 40℃;或者读取结果长期固定不动。

排查思路:

第一步,确认单位。读取 80000 对应 80℃,不是 80000℃。这个前面强调过,我说过犯了不止一次,尤其是从旧平台 v5.15 的内核源码切到新平台时,很容易把这茬忘了。get_temp回调的返回温度统一按 m℃ 填,如果驱动里拿到的是 ADC 原始值,一定要先查芯片手册确认换算公式,常见的公式是temp = raw * scale + offset。

第二步,确认是不是 sensor 本身读值异常。直接把 sensor 驱动里的get_temp单独打印出来,和 InfraRed 测温仪对比一下。温差在 5℃ 以内属于正常(毕竟热偶位置和芯片内部结温差很多),超过 10℃ 就要怀疑 sensor 校准参数、PCB 导热设计或驱动换算公式了。

第三步,检查是不是触发了thermal_zone_device_register时的passive_delay或polling_delay配置太慢,导致温度显示看起来"冻结"。有些板子默认 polling 周期 10 秒,你 5 秒内反复 cat 当然觉得没变化。

5.2 trip 到了阈值却不触发降温

这个是我目前开发过程中遇到的最高频的坑。温度显示 85℃,trip_point_0_temp是 75℃,结果 CPU 频率纹丝不动。

可能原因之一:governor 没选对或没绑定。cat /sys/class/thermal/thermal_zone0/policy看看是不是step_wise。如果显示 unknown,多半是内核没编入对应 governor,或者区在注册时与 governor 匹配失败。检查内核配置:

  • CONFIG_THERMAL_GOV_STEP_WISE
  • CONFIG_THERMAL_GOV_POWER_ALLOCATOR

多数发行版内核是 all governors 都开的,但嵌入式板卡裁剪严重,能省则省,结果把默认 governor 省没了。

可能原因之二:trip 的 type 不是被动冷却。如果 trip_type 是critical,这个 trip 不负责调节冷却设备,它只负责出场跳闸关机。降温逻辑要挂在passive或active类型的 trip 上。很多新手把 critical 和 passive 搞混,理解成"到了这个温度才启动冷却",逻辑上就错了。

可能原因之三:cooling map 没有绑定。zone 里有 trip,但 trip 下面没有对应的cooling-maps,或者 maps 里 phandle 引用的 device 没有被 probe 成功。检查 dmesg 里有没有failed to get cooling device一类的日志,或者直接看/sys/class/thermal/thermal_zone0/cdev0_trip_point这类关联文件是否存在。

5.3 温度震荡、频繁限频与恢复

温度在 trip 阈值附近来回穿越,导致性能忽高忽低,这类问题在 power_allocator 场景尤其常见。解决办法:

加 hysteresis。DT 里每个 trip 都有hysteresis = <2000>这种配置,表示 2℃ 的回差。如果没配,内核默认回差为 0,系统就在阈值边缘疯狂摇摆。这算是我见过最多的"性能抖动"case 了,配了 hysteresis 后客户直接不投诉了。

调整 governor 的采样周期。polling-delay-passive表示进入 passive 状态后的轮询间隔。默认 1000ms 在一些高功耗平台不够,会来不及响应快速升温;设成 250ms 或 100ms,能显著改善 overshoot,但代价是 CPU 唤醒更频繁,功耗增加。这个值需要实际权衡。

对 power_allocator 调 PID。内核里k_po、k_pu、k_i这几个参数控制 PID 的响应强度。如果温度一直在目标附近震荡,多半是k_i积分项太大;如果降温太慢、冲过目标太多,是k_pu比例项过小。这块没有万能参数,不同 SoC 的热容差异很大,只能实测调参。

5.4 高温关机:critical trip 触发后的现场

系统高温自动关机,是最紧急也最需要快速定位的问题。我的排查顺序是:

  1. 先确认是不是真的过热关机。查 dmesg 是否有Critical temperature reached,或者看 reboot reason 是否指向 thermal。这一步能排除硬件电源问题、看门狗重启等干扰项。
  2. 如果是过热,确认触发的是哪个 zone 的 critical trip,结合设备树定位是 CPU 还是 GPU 还是其他外设。
  3. 追根溯源:是散热器没装好、硅脂没涂好、还是负载真的异常高?有时候是某个传感器误报导致误关机,比如 sensor 的 I2C 读到 0xff 或者寄存器默认值,被驱动误当成 120℃。这时候要重点排查 sensor 读取失败时有没有做合理性校验。
  4. 软硬件结合分析:如果硬件端散热余量确实不足,靠软件降低 critical 阈值毫无意义;如果是散热模组装配问题,调 governor 也只是掩耳盗铃。

我在实际项目里见过一个经典 case:某板卡的 sensor 供电 rail 设计参考错误,导致 sensor 在高温下读到的值是跳变的 85℃ 和 160℃ 交替,系统直接 160℃ 触发 critical 关机,即使实际温度只有 70℃ 左右。后来在驱动里加了一个滑动滤波 + 连续性校验,问题才彻底解决。

5.5 用户态改写 trip 点:老版本内核的一个坑

如果你在旧内核(v4.x~v5.x 早期)上尝试用 sysfs 修改 trip 温度,会发现很多 zone 只支持 type 读取,不支持写。这是因为老版本thermal_zone_device_ops里没有实现set_trip_temp。很多 SoC 厂商的驱动也确实不实现这个回调。

新内核中,部分 zone 的 trip 可以通过echo 65000 > /sys/class/thermal/thermal_zone0/trip_point_0_temp动态修改,但前提还是驱动实现了set_trip_temp操作,并且 governor 支持运行时更新 trip。如果你做产品时希望用户态能动态调整温控策略(比如"性能模式"和"省电模式"切换),光依赖 sysfs 写入不够可靠,更建议在用户态自己实现一套策略层,通过 netlink 或 ioctl 与内核中的用户态 governor 协作。

6. governor 深度实践:从 step_wise 到 power_allocator

governor 是整个 framework 的灵魂,我单独拎出来写一节实践内容。

6.1 step_wise 的运行机制与局限

step_wise 的逻辑很直观:温度超过当前 trip 阈值,就按步进把 cdev 调高一档;温度回落到安全区,再逐步降回。它依赖的是 trip 在 zone 中的排序,一步步逼近目标冷却等级。

它的优势在于实现简单、稳定性好、基本不需要配置参数,因而成了出厂默认。但它有一个天然缺陷:它不知道该芯片的功耗-温度曲线如何,只能"试探式"地升降。在高功耗平台上,温度以 5℃/s 的速度飙升时,step_wise 一档一档爬完全来不及。这也是有人说它"反应迟钝"的原因。

如果产品对瞬时性能要求高,我一般会在设备树里把polling-delay-passive调小,同时把 trip 点之间的档位跨度调大一些,让 step_wise 每次调整的步长更大。这个方案改动小,风险低,适合快速 give 给内核团队上线。

6.2 power_allocator 的核心控制逻辑

power_allocator 与 step_wise 是完全不同的思路。它基于控制理论里的 PID 思想:给每个 cooling device 分配一个"power budget",根据当前温度与目标温度的差距动态调节预算,再通过 cdev 的 power model 将预算转化为实际的 state(比如频率、电压)。

关键参数:

参数含义对行为的影响
sustainable_power系统在稳态下能散掉的热功率(mW)设置过小,控制过于保守,性能差
k_po比例增益(超温侧)越大,对温度偏差响应越激进
k_pu比例增益(低温侧)越大,恢复性能越快
k_i积分增益越大,消除稳态偏差越快,但容易震荡

调试 power_allocator 时,我建议在/sys/class/thermal/thermal_zone0/下找到sustainable_power、k_po等参数直接写入,做在线调参。调好后再固化到设备树或板级文件里。

echo 5000 > /sys/class/thermal/thermal_zone0/sustainable_power echo 80 > /sys/class/thermal/thermal_zone0/k_po echo 80 > /sys/class/thermal/thermal_zone0/k_pu echo 0 > /sys/class/thermal/thermal_zone0/k_i

修改后注意观察温度曲线和频率曲线,看是否有 overshoot、震荡或响应过慢。这是个反复试错的过程,没有捷径。我看到有些团队直接在 SoC 厂商参考代码的默认参数上跑产品,结果在低负载场景下性能也受限——仔细一查,sustainable_power是厂商按满载估算的,比实际平台高了一倍,power_allocator 一直以为系统很能散热,控温自然控不住。

6.3 自研 governor:内核态还是用户态

如果你的产品策略很特殊,比如要结合"电池电量""前台应用场景""网络信号"等综合因素来控温,内置 governor 往往不够用。两条路可选:

  • 内核态自研 governor:实现thermal_governor结构体,注册进框架。优点是响应快、数据都在内核态;缺点是开发门槛高,且因为 thermal 框架的锁与通知机制比较复杂,调试周期长。
  • 用户态控制:zone 绑定user_spacegovernor,温度数据通过 sysfs 或 netlink 上报到用户态的一个 daemon,由 daemon 计算后调 cdev 的/sys/class/thermal/cooling_device*/cur_state控制。优点是业务逻辑随意写;缺点是路径长,响应延迟取决于 daemon 的调度策略和 CPU 负载。

两种方案在真实产品里都大量存在。安卓的 thermal HAL 和 vendor thermal engine 本质上就是"用户态为主、内核框架兜底"的混合方案。如果让我给一个通用建议:能基于 user_space 做的策略就用 user_space,因为它好维护、好迭代,线上的参数下发也能做到动态更新;只有响应速度要求到毫秒级、或者用户空间可能被冻结的场景,才值得深入内核态定制。

7. 关于这套架构的扩展与未来

thermal framework 这套架构并不只服务于"手机降频"这个场景。我在车载项目里见过它管理电池加热和冷却系统,在服务器里见过它联动液冷泵的转速,在工业设备里见过它对电机驱动芯片做温度保护。这套抽象之所以能跨领域复用,核心在于"温度来源"和"散热手段"被高度抽象化,策略层又是可替换的。

对新接触这块的人来说,我建议先别急着深挖某个驱动的实现细节。先在自己板子上把 sysfs 里每个文件读一遍,理清 zone、trip、cdev、governor 四者之间的关系;再读drivers/thermal/thermal_core.c的注册阶段源码;最后再看一个具体的 SoC 平台驱动(比如 qcom 或 rockchip 的 thermal driver)怎么把 DT 里的配置变成运行时行为。这条路线走下来,比你直接啃thermal_zone_device_register的一百多个参数要高效得多。

另外,内核社区这些年一直在重构 thermal 框架的接口。比如 trip 从"数组式"变成struct thermal_trip_desc描述,还引入了THERMAL_DEVICE_ENABLED机制、devfreq_cooling等等。但我这篇文章里讲的底层模型—— zone、cdev、governor、instance 这套外壳——在可预见的未来不会变。把核心模型理解透了,版本升级只是换 API 壳子而已。

我在实际运维和开发中最大的体会是:thermal 框架本身不复杂,复杂的是它和电源管理、调度器、cpuidle 之间的联动。真正出问题的时候,往往是 npm 链路(netlink pm manager,可理解为电源管理相关链路)或者调度策略把某个核压得太死,导致轮询线程跑不动,温度数据得不到更新,冷却动作延迟触发。遇到离谱的温控案例,先查系统整体负载和调度情况,很多时候比死磕 thermal 代码更快定位到根因。

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

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

立即咨询