ARTICLE DETAIL

资讯详情

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

Linux thermal framework 深度解析:从架构设计到实战调优

Linux thermal framework 深度解析:从架构设计到实战调优 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、governorthermal 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 tripgovernor 要能同时处理这两种。step_wise和fair_share都能处理多种 trip 类型。bang_bang主要针对 active trip风扇。power_allocator主要针对 passive trip降频。选 governor 的时候要看 zone 里 trip 的类型组合。切换 governor 可以通过 sysfsecho 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 zone5.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 本身能不能手动控制。直接写 sysfsecho 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 到 0governor 想调也没得调。这种情况在复制粘贴配置时很容易发生。6.3 trip 频繁触发的抑制方法trip 频繁触发表现为日志里 trip 状态反复翻转governor 被反复调用CPU 占用升高。根本原因是温度在阈值附近抖动。最直接的解决办法是加hysteresis。如果内核版本支持在 trip 里配直接加就行。如果不支持可以拉开多个 trip 之间的间距让触发和解除落在不同的 trip 上。另一个办法是调整polling_delay。采样太频繁会放大抖动适当加大轮询周期能让温度读数更平滑。但也不能太大否则响应迟钝。如果抖动来自负载本身的波动比如视频解码的帧率波动那就要从 governor 策略上想办法。step_wise每次只动一档本身就有一定的平滑效果。如果还不行可以考虑用power_allocator它基于功耗预算决策对短期波动不敏感。6.4 常见问题速查表现象可能原因排查方法解决思路温度读数恒为 0传感器驱动未加载/回调错误查 dmesg手动读 sysfs修复驱动检查设备树引用温度读数跳变采样噪声大/无滤波连续读多次看波动范围驱动加滤波或加大 hysteresistrip 不触发单位错误/阈值过高检查 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 devicecooling map 绑定失败。这个错误在启动日志里可能只是一行 warning很容易被忽略。解决办法是在设备树里保证依赖顺序或者在驱动里用-EPROBE_DEFER机制延迟注册。坑三多核 CPU 的 cooling device 共享。8 核 CPU 如果每个核都注册一个 cooling devicegovernor 要同时管 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_powergovernor 根据当前温度和功耗模型计算出每个 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 zoneCPU 一个、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 这东西最怕想当然。温度曲线不会骗人配置改完一定要实测实测数据一定要记录记录了一定要对比。凭感觉调参数十有八九要翻车。另外不同平台的传感器精度、散热设计、负载特性差异很大别人的配置只能参考不能照搬最终还是要落到自己的板子上一点点试出来。
返回列表