ARTICLE DETAIL

资讯详情

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

Linux thermal framework 温控框架:从架构到设备树配置与调试

Linux thermal framework 温控框架:从架构到设备树配置与调试 1. 从一次设备发烫的排查说起thermal framework 到底在管什么前阵子帮朋友看一块嵌入式板子跑视频解码不到十分钟外壳就烫得没法摸系统日志里温度读数一路飙到 95 摄氏度但频率该跑还是跑风扇该转还是不转。这种温度失控的现象十有八九是 thermal framework 没配好或者压根没配。Linux 内核功耗子系统里thermal framework 是专门负责感知温度、做出决策、执行降温的一整套通用架构它把温度传感器、降温设备、策略逻辑抽象成统一的模型让驱动开发者不用为每块板子重写一套温控代码。这套框架解决的核心问题很朴素芯片会发热发热到一定程度会降频、会死机、甚至烧毁所以必须有人盯着温度超了就采取动作。听起来简单但真实系统里温度传感器可能有好几个CPU、GPU、电池、外壳降温手段也五花八门降频、关核、限充电电流、开风扇谁先动、动多少、什么时候恢复这些决策逻辑如果散落在各个驱动里维护起来就是灾难。thermal framework 的价值就在于把这些东西标准化、可配置化。这篇文章适合谁看如果你在做嵌入式 Linux 开发、BSP 移植、功耗调优或者单纯想搞懂/sys/class/thermal下面那一堆目录到底是什么意思那这篇梳理就是给你准备的。我会从整体架构讲到关键数据结构再落到设备树配置和实际调试尽量把为什么这么设计讲透而不是只丢一堆结构体定义。thermal zone、cooling device、governor 这几个关键词会贯穿全文它们是理解整套框架的钥匙。2. thermal framework 的四层抽象为什么这样分层2.1 从硬件到策略的职责切分刚接触 thermal framework 的人容易懵因为它的代码分布在drivers/thermal/、include/linux/thermal.h以及各个平台的驱动里结构体之间互相引用理不清谁管谁。我的经验是先别急着看代码先在脑子里建立一个四层模型从下往上分别是硬件层、抽象层、策略层、用户接口层。硬件层就是真实的温度传感器比如 SoC 内部的 TSADC、外部的 I2C 温度芯片和降温设备CPUfreq、devfreq、风扇 PWM、充电器限流。抽象层是 thermal framework 提供的统一表示把传感器抽象成 thermal zone把降温手段抽象成 cooling device。策略层是 governor它决定温度到了某个点该让哪个 cooling device 出多少力。用户接口层就是 sysfs 和 netlink让你能在用户态看温度、改策略、收事件。这样分层的好处是解耦。传感器驱动只需要注册自己不用关心谁来读降温设备驱动只需要实现set_cur_state不用关心什么时候被调用策略逻辑集中在 governor 里换策略不用动硬件驱动。我见过一些老代码把温控逻辑直接写在传感器驱动里结果换块板子就得重写这就是没有分层的代价。2.2 thermal zone温度的观测站thermal zone 是这套框架里最核心的概念你可以把它理解成一个温度观测站。一个 thermal zone 通常对应一个需要监控的热点比如 CPU 集群是一个 zoneGPU 是一个 zone电池是一个 zone。每个 zone 里可以挂多个温度传感器thermal_sensor框架会按配置的方式比如取最大值、取平均算出一个当前温度。为什么一个 zone 要支持多个传感器因为同一个热点在不同位置的温度可能不一样。比如 CPU 集群核心旁边有一个传感器封装外面还有一个取哪个更合理取决于你的保护目标。如果保护的是结温那就取核心的如果保护的是外壳不烫手那就取外部的。框架允许你在设备树里配置多个 sensor 并指定聚合方式这个设计很实用。zone 还维护着一组 trip point触发点这是温度阈值。每个 trip point 有类型critical、hot、passive、active和温度值。温度越过某个 trip point就会触发对应的动作。critical 类型最严重通常直接触发系统关机或重启因为再热下去硬件要坏了。passive 类型用于降频active 类型用于开风扇这类主动散热。2.3 cooling device降温手段的统一接口cooling device 是降温执行者。任何能降低系统温度的东西只要实现标准接口就能注册成 cooling device。最常见的几类CPUfreq 的 cooling device 通过限制 CPU 最大频率来降温devfreq 的 cooling device 限制 GPU 或内存频率风扇 cooling device 通过 PWM 调速还有充电限流、屏幕降亮度等。每个 cooling device 有一个cur_state和一组states。state 是离散的档位比如 CPU 有 0 到 N 档0 是不限制N 是限制到最低频。框架通过设置cur_state来让设备出力。这里有个容易踩的坑不同 cooling device 的 state 语义不一样有的是数字越大越凉快风扇档位有的是数字越大越热CPU 限制档位因为 state 越大频率被压得越低但表示的是限制程度。写 governor 或者调策略时一定要看清楚语义我当初就因为搞反了方向调了半天发现温度越调越高。2.4 governor决策的大脑governor 是策略层决定温度多少度时让哪个 cooling device 出多少力。内核自带几种 governor最常用的是step_wise、power_allocator和fair_share。step_wise逻辑简单温度每越过一个 trip point 就往上加一档降温力度适合大多数场景。power_allocator更复杂它基于 PID 控制思想动态分配每个 cooling device 的功率预算适合对性能敏感的移动设备。fair_share则是把降温需求平均分给各个 cooling device。选哪个 governor 不是拍脑袋决定的。step_wise响应直接但容易震荡温度在阈值附近来回跳power_allocator平滑但调参复杂需要配置sustainable_power、k_po、k_pu等参数。我在做手机项目时初期用step_wise快速验证后期切到power_allocator做精细调优这个路径比较稳妥。3. 关键数据结构与注册流程驱动开发者必须搞清的事3.1 thermal_zone_device 的字段含义struct thermal_zone_device是 zone 的核心结构体字段不少但真正需要关注的没几个。type是 zone 的名字会出现在 sysfs 里temperature是当前温度trips是 trip point 数组governor指向当前使用的策略ops是 zone 的操作函数集最关键的是get_temp框架靠它读温度。get_temp这个回调是必须实现的框架会周期性调用它周期由polling_delay决定来更新温度。如果你的传感器支持中断上报温度变化也可以配置passive模式减少轮询。我建议在传感器精度够用的情况下轮询周期别设太短1000ms 是个常见起点设成 100ms 会让 CPU 频繁被唤醒反而增加功耗这就本末倒置了。struct thermal_zone_device_ops里除了get_temp还有set_trips、get_trend等可选回调。set_trips用于告诉硬件温度越过这两个值再通知我能进一步省电。get_trend返回温度变化趋势power_allocator会用到它做预测。这些可选回调不实现也能跑但实现了能提升效果。3.2 cooling device 的注册与 state 管理注册 cooling device 用thermal_cooling_device_register需要传入名字、私有数据和操作函数集。操作函数集里get_max_state返回最大档位get_cur_state返回当前档位set_cur_state设置档位。这三个是必须的。set_cur_state的实现要小心它可能在中断上下文或原子上下文被调用所以里面不能睡眠。我见过有人在里面调用了会睡眠的函数结果系统偶发卡死排查了很久才发现是这里的问题。如果确实需要做耗时操作用工作队列异步处理。state 的档位设计也有讲究。档位太少降温粒度粗温度控制不精细档位太多governor 决策开销大而且很多档位实际效果差不多。CPUfreq 的 cooling device 通常把档位映射到可用的频率点有多少个频率点就有多少档这个粒度一般够用。风扇的话我一般设 4 到 8 档太少会有明显的转速跳变噪音太多则 PWM 调节频繁。3.3 设备树里的 thermal 配置现在大多数 ARM 平台都用设备树描述 thermal 硬件。一个典型的配置包含三部分thermal zone 节点、cooling device 映射、trip point 定义。zone 节点里用thermal-sensors引用传感器用trips定义触发点用cooling-maps把 trip point 和 cooling device 关联起来。cooling-maps是配置的重点它定义了哪个 trip 触发时影响哪些 cooling device。每个 map 里有trip、cooling-device、contribution等字段。contribution表示这个设备在本次降温中承担多少份额多个设备时可以按比例分配。我调过一个四核平台CPU 和 GPU 共享一个 zone通过 contribution 让 GPU 在轻度发热时先降频CPU 保持性能重度发热时两者一起降这个策略比一刀切体验好很多。trip point 的温度值设置是门艺术。设太低设备动不动就降频性能受影响设太高保护不及时有风险。我的经验是 critical 设在硬件规格的结温上限减 10 到 15 度作为安全余量passive 设在用户能感知到烫手之前active 设在 passive 之下留出提前量。具体数值必须查芯片手册不能拍脑袋。4. 温度控制的实际运作从读数到降温的完整链路4.1 一次温度更新的完整流程搞懂流程对调试至关重要。框架启动后会为每个 zone 创建一个延迟工作delayed work按polling_delay周期触发。触发时调用 zone 的get_temp拿到当前温度更新temperature字段然后交给 governor 处理。governor 遍历 trip point找出当前温度落在哪个区间决定是否需要调整 cooling device 的 state。如果需要就调用对应 cooling device 的set_cur_state。这个链路里任何一环出问题都会导致温控失效。get_temp返回错误值governor 就基于错误信息决策governor 没配对可能压根不触发降温set_cur_state实现有 bug降温指令下发了但没生效。调试时我习惯先看/sys/class/thermal/thermal_zone*/temp确认读数正常再看trip_point_*_temp确认阈值合理最后看cooling_device*/cur_state确认降温动作有没有执行。4.2 sysfs 接口的实用读法/sys/class/thermal/下面是调试的主战场。每个thermal_zoneN目录里有typezone 名字、temp当前温度单位是毫摄氏度注意要除以 1000、modeenabled 或 disabled、policy当前 governor、trip_point_N_type和trip_point_N_temp各触发点信息。cooling_deviceN目录里有type设备类型、cur_state当前档位、max_state最大档位。调优时我会写个小脚本每秒打印一次温度和所有 cooling device 的 state观察温度上升时 state 是不是按预期变化。这个笨办法比看代码快得多能直观发现策略是否符合预期。有个细节要注意temp的单位是毫摄氏度我第一次看的时候以为是摄氏度看到 85000 还纳闷怎么这么高后来才反应过来是 85 度。这个单位设计是为了避免浮点运算内核里很常见。4.3 中断模式与轮询模式的取舍温度读取有两种模式轮询和中断。轮询就是前面说的周期性调用get_temp实现简单但费电。中断模式是传感器在温度越过设定阈值时主动上报框架收到中断再读温度省电但需要硬件支持。设备树里通过polling-delay和polling-delay-passive控制轮询周期。如果传感器支持中断可以把polling-delay设成 0 表示不用轮询靠set_trips配置硬件阈值。但要注意不是所有传感器都支持set_trips不支持的话设成 0 会导致温度永远不更新这个坑我踩过现象是温度读数一直不变查了半天才发现是配置问题。实际项目中我倾向于混合模式正常运行时用中断省电进入 passive 降温状态后切回轮询因为降温过程中需要更及时的温度反馈。框架的polling-delay-passive就是干这个的进入 passive 后会用这个更短的周期轮询。5. 调试温控问题的排查链路几个真实案例5.1 温度读数正常但从不降温这是最常见的现象。温度都 90 度了CPU 频率还是满血。排查第一步看policy确认 governor 不是user_space或disabled。第二步看 trip point 温度确认 passive 阈值不是设得比实际能达到的温度还高。第三步看cooling-maps确认 trip 和 cooling device 的关联没写错。我遇到过一次设备树里cooling-maps的trip引用写错了名字导致映射没建立governor 找不到该控制的设备自然不降温。这种错误编译不报运行时也不报只能靠看 sysfs 里cooling_device的cur_state一直不变来发现。后来我养成了习惯新板子第一次跑温控一定手动加热跑压力测试并盯着cur_state看。5.2 温度在阈值附近反复震荡温度一到 70 度就降频降到 68 度就恢复恢复后又冲到 70 度如此反复。这是step_wisegovernor 的典型问题因为它的恢复逻辑比较激进。解决办法有几个一是加 hysteresis迟滞让恢复温度比触发温度低几度框架里通过 trip point 的hysteresis字段配置二是换power_allocator它的 PID 控制天然平滑三是调整 trip point 间距别让两个阈值挨太近。hysteresis 这个字段很多人不知道它表示温度要低于 trip 温度多少度才认为脱离该 trip。比如 trip 是 70 度hysteresis 是 2 度那温度要降到 68 度以下才恢复。合理设置能显著减少震荡我一般设 2 到 5 度具体看系统热惯性。5.3 降温动作生效但温度不降cur_state确实变了CPU 频率也降了但温度就是下不来。这种情况通常不是 thermal framework 的问题而是散热设计或功耗本身的问题。可能散热片没贴好可能降频幅度不够也可能发热源不止 CPU 一个。这时候要用功耗分析工具看各部分的实际功耗别死磕 thermal 配置。我碰到过一次GPU 降频了但温度不降最后发现是内存控制器在满负荷跑而内存没有对应的 cooling device。加上 devfreq 的 cooling device 后问题解决。这提醒我们配置 cooling device 时要覆盖所有主要发热源不能只盯着 CPU。6. 把 thermal framework 用好的几个经验6.1 trip point 数值的确定方法别抄别人的数值每块板子的散热条件、芯片体质、外壳材质都不一样。正确做法是先查芯片手册拿到结温上限critical 设在上限减安全余量然后做热测试记录不同负载下的稳态温度passive 设在用户可接受温度对应的芯片温度active 设在 passive 之下。整个过程要反复迭代我一般会做三轮测试才定稿。测试时要用真实场景的负载别只用跑分软件。跑分软件的发热模式和实际应用差别很大我见过跑分时温控正常、玩游戏时却降频的案例就是因为游戏负载更接近真实场景。用实际应用测出来的数值才靠谱。6.2 governor 选择的决策依据简单设备、对成本敏感、开发周期短选step_wise够用且好调。移动设备、对续航和性能平衡要求高选power_allocator但要做好调参准备。多发热源需要公平分配选fair_share。没有银弹关键是理解你的场景最看重什么。power_allocator的调参是个技术活。sustainable_power是设备能长期稳定散发的功率设小了会过度降频设大了保护不足。这个值最好通过实测确定让设备在目标温度下稳定运行测出此时的功耗就是sustainable_power。k_po和k_pu是 PID 参数一般用默认值起步根据震荡情况微调。6.3 用户态干预的边界框架提供了 sysfs 接口让用户态改policy、改 trip point甚至直接设cur_state。这给了灵活性但也带来风险。生产设备上我建议锁死关键配置只开放必要的只读接口。用户态乱改温控参数可能导致设备过热这个责任谁都担不起。如果确实需要用户态参与决策比如根据使用场景切换性能模式用user_spacegovernor 配合一个守护进程让守护进程通过 netlink 接收温度事件并决策。这样逻辑在用户态但执行还是走框架的标准路径比直接写 sysfs 规范。6.4 和功耗子系统的协同thermal framework 不是孤立的它和 CPUfreq、devfreq、regulator 等子系统紧密协作。降频靠 CPUfreq限流靠 regulator这些子系统本身也有自己的策略。配置时要注意别让它们打架比如 CPUfreq 的 governor 想升频thermal 想降频最终谁说了算取决于调用顺序和优先级。我的做法是让 thermal 的优先级高于性能策略因为过热是安全问题性能是体验问题安全优先。具体实现上thermal 的 cooling device 会直接限制 CPUfreq 的max_freq这个限制会覆盖 governor 的决策。理解这个覆盖关系能避免很多为什么频率上不去的困惑。7. 写在最后的一点个人体会thermal framework 这套架构刚看会觉得绕但一旦理解了zone 观测、trip 触发、cooling 执行、governor 决策这条主线剩下的都是细节填充。我在多个项目里用它最大的感受是配置比代码重要。框架本身很成熟出问题九成是设备树配错了或者 trip point 设得不合理真正需要改框架代码的情况极少。如果你正在调一块新板子的温控我的建议是先别急着优化先用默认配置跑起来确认整条链路是通的——温度能读、trip 能触发、cooling 能动作。链路通了之后再谈调优否则你连问题出在哪一环都不知道。这个先通后优的思路帮我省下了大量排查时间。
返回列表