ARTICLE DETAIL

资讯详情

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

嵌入式Linux温控实战:thermal framework架构、设备树配置与调优避坑指南

嵌入式Linux温控实战:thermal framework架构、设备树配置与调优避坑指南 1. 功耗子系统里的“温控管家”thermal framework 到底管什么搞嵌入式 Linux 的兄弟大概率都遇到过这种场景板子跑着跑着突然降频了或者干脆关机重启串口 log 里蹦出一行thermal thermal_zone0: critical temperature reached。这时候你要么在空调房里骂娘要么就得老老实实去翻 thermal 子系统的代码。我做了这么多年 BSP 和功耗优化thermal framework 是那种“平时不出声一出事就要命”的模块。它不像 CPUFreq、CPUIdle 那样天天被性能调优的人挂在嘴边但一旦温控策略没配对轻则游戏掉帧、重则硬件烧毁尤其在国产化嵌入式平台和 ARM64 服务器上这东西的配置差异能把人折腾到怀疑人生。thermal framework 说白了就是 Linux 内核里的一套温度管理通用架构。它干的事可以拆成三块第一把各种温度传感器片上 TSADC、外挂 I2C 温度芯片、甚至通过 SCMI 上报的温度抽象成统一的thermal_zone第二把可以降温的器件CPU、GPU、风扇、充电 IC、甚至整机电源管理抽象成cooling_device第三用一套 governor调速策略把“温度”和“降温动作”绑起来在温度越界时按预设的 trip point 触发对应的 cooling 动作。听起来简单但真正落地的时候从设备树怎么写、trip 点怎么定、governor 选哪个到为什么你的板子明明没到 80 度就降频了全是坑。这篇文章适合谁看如果你正在做嵌入式 Linux 的功耗与热管理、在调一块新板子的温控策略、或者面试时被问到“thermal zone 和 cooling device 怎么关联”答不上来那这篇就是给你准备的。我会把 thermal framework 的通用架构从头到尾捋一遍包括核心数据结构、注册流程、governor 工作机制、设备树绑定以及我在实际项目里踩过的那些坑。不堆砌源码但关键路径会点到让你看完能自己动手配一套能用的温控策略。2. 通用架构拆解thermal framework 的四大核心角色2.1 thermal_zone_device温度的“信息源”整个 thermal framework 的起点是thermal_zone_device我习惯叫它“温度区”。一个 thermal zone 代表一个可以被监测和管理的温度区域比如 SoC 内部的一个 TSADC 通道、一块电池的温度、或者一个靠近 WiFi 模组的板载温度点。它的核心字段包括type字符串标识比如cpu-thermal、gpu-thermal、board-thermal用户空间通过 sysfs 看到的thermal_zone0/type就是这个。temperature当前温度单位是毫摄氏度millidegree Celsius注意不是摄氏度很多新手在这里算错阈值。trips一组 trip point每个 trip 有温度阈值、类型passive/active/critical/hot和关联的 cooling device。governor当前使用的调速策略比如step_wise、power_allocator、bang_bang。ops底层驱动实现的回调最关键的是get_temp用来读取实际温度。注册一个 thermal zone 的典型流程是驱动里填充thermal_zone_device_ops然后调用devm_thermal_of_zone_register新内核或thermal_zone_device_register老内核。新内核推荐用设备树方式注册因为 trip point 和 cooling map 都能在 DTS 里描述驱动代码会干净很多。注意get_temp回调必须返回毫摄氏度。我见过有人直接返回摄氏度结果温控在 0.08 度就触发了log 里全是降频记录查了半天才发现单位错了。2.2 cooling_device能“降温”的执行者thermal_cooling_device代表任何可以降低系统热量的器件。最常见的当然是 CPUFreq 的 cooling device它通过限制 CPU 最大频率来降温还有风扇 cooling device通过 PWM 调速以及 devfreq cooling device用来限制 GPU 或内存频率。每个 cooling device 有一个max_state和一组statesstate 越大通常代表降温能力越强频率越低或风扇转速越高。cooling device 的注册由各子系统自己完成。比如 CPUFreq 在初始化时会调用cpufreq_cooling_register把每个 policy 注册成一个 cooling device。风扇驱动则通过devm_thermal_of_cooling_device_register注册。注册完成后thermal core 会生成/sys/class/thermal/cooling_deviceN/节点用户空间可以读cur_state和max_state。这里有个容易混淆的点cooling device 的 state 和实际频率不是线性对应的。比如 CPUFreq cooling 的 state 0 是“不限制”state 1 可能是限制到某个频率state 2 限制得更低。具体映射关系由cpufreq_cooling内部根据频率表计算不是简单的一一对应。调策略的时候要先用cat /sys/class/thermal/cooling_device0/cur_state确认当前状态再结合cpufreq的scaling_max_freq一起看。2.3 thermal_governor温度与降温之间的“决策大脑”governor 是 thermal framework 里最灵活也最容易配错的部分。它的职责是当温度跨过某个 trip point 时决定把关联的 cooling device 调到哪个 state。内核里常见的 governor 有Governor适用场景特点step_wise通用最常用每次温度越界只调整一档温和适合大多数嵌入式bang_bang风扇控制温度超过阈值开风扇低于阈值关风扇简单粗暴power_allocator服务器/手机基于 PID 控制动态分配功耗预算复杂但精细user_space调试把决策权交给用户空间通过 sysfs 手动设置step_wise是默认 governor也是我推荐大多数项目先用的。它的逻辑是温度超过 passive trip 时把 cooling device 的 state 加一温度降下来后再慢慢减一。每次调整之间有 polling 周期默认 250ms 左右不会频繁抖动。power_allocator则适合对功耗和性能都有精细要求的场景比如手机 SoC但它需要 IPAIntelligent Power Allocation相关的参数配置调起来门槛高不少。governor 的切换可以通过 sysfs 完成echo step_wise /sys/class/thermal/thermal_zone0/policy但生产环境一般直接在设备树或内核配置里定死避免用户空间误操作。2.4 thermal_cooling_map把温度和动作绑起来的“红线”trip point 和 cooling device 之间的关联通过 cooling map 描述。一个 cooling map 包含关联的 trip point、cooling device、以及 trip 触发时 cooling device 应该达到的 state 范围。在设备树里长这样trips { cpu_alert: cpu-alert { temperature 80000; hysteresis 2000; type passive; }; }; cooling-maps { map0 { trip cpu_alert; cooling-device cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT; }; };THERMAL_NO_LIMIT表示由 governor 动态决定 state而不是固定值。hysteresis是迟滞值防止温度在阈值附近反复触发。这个参数非常关键设太小会导致 cooling device 频繁开关设太大又会让降温不及时。我的经验是被动降温场景下 hysteresis 设 20002 摄氏度比较稳风扇控制可以设 5000 左右。3. 设备树与驱动注册从 DTS 到 sysfs 的完整链路3.1 设备树里怎么描述一个 thermal zone以一块典型的 ARM64 嵌入式板子为例SoC 内部有一个 TSADC支持多个通道其中一个通道接 CPU 温度。设备树大致这样写tsadc: tsadcff280000 { compatible rockchip,rk3588-tsadc; reg 0x0 0xff280000 0x0 0x100; interrupts GIC_SPI 139 IRQ_TYPE_LEVEL_HIGH; clocks cru SCLK_TSADC, cru PCLK_TSADC; clock-names tsadc, apb_pclk; resets cru SRST_TSADC; reset-names tsadc-apb; #thermal-sensor-cells 1; status okay; }; thermal_zones: thermal-zones { cpu_thermal: cpu-thermal { polling-delay-passive 100; polling-delay 1000; thermal-sensors tsadc 0; trips { cpu_alert0: cpu-alert0 { temperature 75000; hysteresis 2000; type passive; }; cpu_crit: cpu-crit { temperature 95000; hysteresis 2000; type critical; }; }; cooling-maps { map0 { trip cpu_alert0; cooling-device cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT, cpu4 THERMAL_NO_LIMIT THERMAL_NO_LIMIT; }; }; }; };几个关键点polling-delay-passive是进入被动降温后的轮询间隔单位毫秒polling-delay是正常状态下的轮询间隔。被动降温时轮询要更频繁否则温度冲上去了才发现。thermal-sensors指向 tsadc 节点和通道号。#thermal-sensor-cells 1表示这个传感器需要 1 个参数来指定通道。提示不同 SoC 的 TSADC 驱动对#thermal-sensor-cells的要求不一样。有的只支持一个通道写 0有的支持多通道写 1。写错了会在 probe 时报thermal_sensor: invalid number of cells别问我怎么知道的。3.2 驱动侧注册流程与关键回调TSADC 驱动里probe 阶段会做几件事申请寄存器、使能时钟、注册 thermal sensor。核心是填充thermal_zone_device_opsstatic const struct thermal_zone_device_ops tsadc_of_ops { .get_temp tsadc_get_temp, .set_trips tsadc_set_trips, .change_mode tsadc_change_mode, };get_temp是必须实现的返回毫摄氏度。set_trips是可选的如果硬件支持温度阈值中断实现它可以减少轮询开销。change_mode用于切换工作模式比如从轮询模式切到中断模式。注册时用devm_thermal_of_zone_registerzone devm_thermal_of_zone_register(dev, 0, sensor, tsadc_of_ops); if (IS_ERR(zone)) return PTR_ERR(zone);这里的0是传感器通道号对应设备树里的thermal-sensors tsadc 0。注册成功后/sys/class/thermal/thermal_zone0/就会出现里面能看到type、temp、policy、trip_point_0_temp等节点。3.3 sysfs 接口调试温控的第一现场thermal framework 通过 sysfs 暴露了大量调试接口这是排查温控问题最直接的手段。常用的节点/sys/class/thermal/thermal_zone0/temp当前温度毫摄氏度。/sys/class/thermal/thermal_zone0/typezone 类型。/sys/class/thermal/thermal_zone0/policy当前 governor。/sys/class/thermal/thermal_zone0/trip_point_0_temp第 0 个 trip 的温度。/sys/class/thermal/thermal_zone0/trip_point_0_typetrip 类型。/sys/class/thermal/cooling_device0/cur_state当前 cooling state。/sys/class/thermal/cooling_device0/max_state最大 state。我一般会写个小脚本每 500ms 打印一次温度和 cooling state观察温控是否按预期工作while true; do temp$(cat /sys/class/thermal/thermal_zone0/temp) state$(cat /sys/class/thermal/cooling_device0/cur_state) echo temp${temp} state${state} sleep 0.5 done如果发现温度到了 trip 点但 state 没变先确认 cooling map 是否正确关联再确认 governor 是否在跑。有时候 governor 是user_space那就需要手动写 state不会自动调整。4. 实操从零配置一套可用的温控策略4.1 确定 trip point 和 cooling 策略配温控的第一步不是写代码而是定策略。你需要回答几个问题这颗芯片的结温上限是多少被动降温从多少度开始critical 关机点设多少风扇什么时候开以一颗工业级 ARM64 SoC 为例datasheet 标称结温上限 105 摄氏度但实际板子在密闭机箱里 85 度就开始不稳定。我的做法是passive trip75 摄氏度开始限制 CPU 频率。active trip65 摄氏度开风扇如果有。critical trip95 摄氏度触发关机保护。hysteresis被动 2000风扇 5000。这些值不是拍脑袋来的。75 度 passive 是留了 10 度余量给散热惯性因为从触发降频到温度真正回落有延迟。critical 设 95 度而不是 105 度是因为传感器本身有误差加上热点和传感器位置可能差 5 到 10 度留足安全边际。4.2 配置 cooling device 的 state 映射CPUFreq cooling device 的 state 映射由内核根据频率表自动计算。假设 CPU 支持 8 个频率档位从 400MHz 到 2.0GHz那么 cooling state 0 对应不限制state 1 到 state N 对应逐步降低最大频率。具体对应关系可以看cat /sys/class/thermal/cooling_device0/stats输出里会显示每个 state 对应的频率限制。如果发现 state 1 就直接把频率压到最低说明频率表配置有问题需要检查cpufreq的freq_table是否完整。对于风扇 cooling devicestate 映射由驱动自己定义。比如 4 线 PWM 风扇state 0 是停转state 1 到 state 5 对应 20% 到 100% 占空比。这个映射要在驱动里写清楚否则 governor 调了 state 但风扇不转温度照样飙。4.3 验证温控是否生效配置完成后怎么验证最直接的办法是用 stress 工具把 CPU 跑满同时监控温度和频率stress-ng --cpu 8 --timeout 300s while true; do temp$(cat /sys/class/thermal/thermal_zone0/temp) freq$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq) state$(cat /sys/class/thermal/cooling_device0/cur_state) echo temp${temp} freq${freq} state${state} sleep 1 done预期行为是温度上升到 75 度附近cooling state 从 0 变成 1CPU 频率被限制温度回落或稳定在 75 度左右。如果温度继续上升到 95 度系统应该触发 critical 关机。如果温度到了 75 度但 state 没变检查 governor 和 cooling map如果 state 变了但频率没降检查 cpufreq cooling 的注册是否成功。注意有些平台的 CPUFreq cooling 只注册了 policy0也就是小核集群。大核集群如果没注册 cooling device温度再高也不会被限制。这时候需要在设备树里把大核的 cooling device 也加进 cooling map。5. 常见问题与排查技巧实录5.1 温度读数异常从单位到传感器校准温度读数不对是最常见的问题。表现有三种读数恒定为 0、读数明显偏高或偏低、读数跳变剧烈。恒定为 0 通常是get_temp回调没实现或返回错误。检查驱动里是否填充了get_temp以及 TSADC 的时钟和寄存器配置是否正确。读数偏高或偏低先确认单位是不是毫摄氏度。如果单位对了再看传感器校准参数。有些 SoC 的 TSADC 需要写校准值到 efuse驱动启动时读取并补偿。如果 efuse 没烧录或读取失败温度就会有偏差。跳变剧烈一般是轮询周期太短或者传感器本身噪声大。可以适当增大polling-delay或者在驱动里做滑动平均滤波。我在一个项目里遇到过温度在 60 到 80 度之间反复跳最后发现是 TSADC 的参考电压不稳硬件同事加了个滤波电容就好了。5.2 cooling device 不生效关联与权限排查cooling device 注册了但 governor 调不动原因通常有几个cooling map 没关联、governor 是user_space、或者 cooling device 的max_state是 0。先看/sys/class/thermal/thermal_zone0/cdev0_cur_state是否存在不存在说明 cooling map 没建好。再看/sys/class/thermal/thermal_zone0/policy如果是user_spacegovernor 不会自动调整。最后看/sys/class/thermal/cooling_device0/max_state如果是 0说明 cooling device 注册时没正确设置 state 数量。还有一种情况是 cooling device 注册了但被其他 zone 占用。一个 cooling device 可以同时被多个 thermal zone 管理但 state 是全局的。如果两个 zone 同时调同一个 cooling device可能会出现“抢方向盘”的情况。这时候要么拆开用不同的 cooling device要么用power_allocator统一管理。5.3 温控抖动hysteresis 与 polling 的配合温控抖动表现为 cooling state 在 0 和 1 之间反复切换频率也跟着上下跳。根本原因是温度在 trip 点附近波动而 hysteresis 太小或 polling 太快。解决办法增大 hysteresis比如从 1000 加到 3000同时适当增大polling-delay-passive比如从 100ms 加到 250ms。但 polling 也不能太慢否则温度冲上去了还没反应过来。我的经验值是被动降温 polling 100 到 250mshysteresis 2000 到 3000大多数场景够用。如果抖动依然存在可以考虑换power_allocatorgovernor它内部有 PID 控制对抖动抑制更好但配置复杂度也更高。5.4 常见问题速查表现象可能原因排查方法解决温度恒为 0get_temp 未实现或返回错误检查驱动 ops 和寄存器实现 get_temp修正单位温度偏高/偏低单位错误或校准缺失对比 datasheet 和实测修正单位读取 efuse 校准cooling state 不变cooling map 未关联检查 cdev_cur_state 节点补全 cooling-maps频率不降cpufreq cooling 未注册检查 cooling_device 列表注册大核 cooling device温控抖动hysteresis 太小观察 state 切换频率增大 hysteresis 和 pollingcritical 不触发trip 类型写错检查 trip_point_type改为 critical6. 进阶多 zone 协同与 power_allocator 的取舍6.1 多 thermal zone 的协同管理实际系统里往往不止一个 thermal zone。CPU、GPU、NPU、电池、充电 IC 可能各自有独立的温度区。这些 zone 之间会相互影响GPU 跑满会加热 SoC进而影响 CPU 温度充电时电池发热也会传导到主板。多 zone 协同的核心是 cooling device 的共享和优先级。比如 CPU 和 GPU 可能共享同一个大核集群的 cooling device。这时候需要决定CPU 温度高时限制多少GPU 温度高时限制多少。一种做法是用不同的 cooling map 关联同一个 cooling device但设置不同的 state 范围。另一种做法是用power_allocator它会把所有 cooling device 纳入统一的功耗预算按需分配。我在一个车载项目里遇到过 CPU 和 GPU 抢 cooling device 的问题导航和娱乐同时跑CPU 温度先到 trip 点把 GPU 频率也压下去了导致画面卡顿。后来把 GPU 的 cooling device 独立出来用 devfreq cooling 单独管理问题才解决。6.2 power_allocator 的适用边界power_allocator是 thermal framework 里最复杂的 governor它基于 IPA 算法把系统功耗预算分配给各个 cooling device。适合手机、平板这类对功耗和性能都有精细要求的设备。但它有几个前提需要准确的功耗模型sustainable_power、需要每个 cooling device 提供get_static_power和get_dynamic_power回调、需要配置k_po、k_pu、k_i等 PID 参数。如果这些参数没调好power_allocator的表现可能还不如step_wise。我见过有人直接抄了别的项目的参数结果温控反应迟钝温度冲到 90 度才开始降频。所以我的建议是除非你有明确的功耗优化目标和足够的调试时间否则先用step_wise稳定可靠调起来也简单。6.3 温控与性能的平衡经验最后分享几条我在实际项目里总结的温控调优经验。第一trip point 不要设得太激进。有些团队为了跑分好看把 passive trip 设到 85 度结果长时间高负载下温度在 85 度附近震荡频率反复升降体验反而更差。第二critical trip 一定要留足余量传感器误差、热点偏移、散热老化都要考虑进去。第三调试阶段把 thermal 相关的 log 打开CONFIG_THERMAL_DEBUG和CONFIG_THERMAL_STATISTICS能帮你看到每次 state 切换的原因和时间点。第四用户空间不要随便改 policy生产固件里把 governor 定死避免误操作导致温控失效。温控这东西调好了用户感知不到调不好就是死机重启。花点时间把 trip point、hysteresis、polling 和 cooling map 这四样东西理清楚比事后救火强得多。
返回列表