当前位置: 首页 > news >正文

深入解析Linux Thermal子系统架构

Linux 的 Thermal 子系统采用了典型的“分层与抽象”设计思想,将底层的硬件差异与上层的策略控制隔离开来。理解这套机制能让你在查驱动代码时犹如庖丁解牛。我们分两部分来拆解:自下而上的注册,以及自上而下的调用

第一部分:底层thermal_zone是如何注册上来的?

在现代 Linux 内核(特别是引入了设备树 DTS 之后),注册流程主要依赖of_thermal机制和面向对象的回调函数设计。
1. 核心数据结构:thermal_zone_device_ops
底层传感器驱动(比如 MTK 的内部温度传感器驱动)不需要关心上层怎么处理数据,它只需要向核心层(thermal_core)提供一个“操作函数表”。这其中最核心的就是.get_temp
// 底层驱动定义操作集合
static const struct thermal_zone_device_ops mtk_thermal_ops = { .get_temp = mtk_thermal_get_temp, // 必须实现:读取真实温度 .set_trips = mtk_thermal_set_trips, // 可选:设置硬件中断阈值 };
2. 注册 API 桥梁
底层驱动在probe函数初始化硬件后,会调用核心层提供的注册 API。在 Android 平台上,最常用的是基于设备树的接口:
// 在驱动的 probe 函数中调用 tzdev = devm_thermal_zone_of_sensor_register(dev, sensor_id, sensor_data, &mtk_thermal_ops);
这个注册动作在thermal_core.c中引发了连锁反应:
  1. 解析 DTS:核心层去解析设备树里的thermal-zones { ... }节点,读取你在里面配置的 polling-delay(轮询时间)、trips(触发点 117℃、136℃等)和 cooling-maps。
  2. 分配对象:在内存中创建一个struct thermal_zone_device对象,并将底层传过来的mtk_thermal_ops挂载到这个对象上。
  3. 暴露接口:在/sys/class/thermal/目录下动态生成对应的thermal_zoneX文件夹,并创建temptrip_point_X_temp等属性节点。
至此,底层硬件就成功“挂载”到了核心层。

第二部分:上层是如何调用到底层查询温度的?

这就涉及到你之前经常敲的命令:cat /proc/driver/thermal/tzpmiccat /sys/class/thermal/thermal_zoneX/temp。上层的读取请求会经历一段**“穿透式”**的调用栈。
调用链路拆解 (Top-Down)
Step 1: 用户空间发起请求 (Userspace)
当 Android 层的thermal load algod进程或者你通过adb shell cat读取温度节点时,触发了一个系统的sys_read调用。
Step 2: Sysfs 文件系统拦截 (VFS -> Sysfs)
/sys/class/thermal/下的节点是由thermal_core.c注册的。针对temp节点,核心层绑定了一个show函数:
// thermal_sysfs.c 或 thermal_core.c 中的节点读函数 static ssize_t temp_show(struct device *dev, struct device_attribute *attr, char *buf) { struct thermal_zone_device *tz = to_thermal_zone(dev); int temperature, ret; // 核心层调用获取温度的统一下发口 ret = thermal_zone_get_temp(tz, &temperature); if (ret) return ret; return sprintf(buf, "%d\n", temperature); }
Step 3: 核心层统一下发 (Thermal Core)
thermal_zone_get_temp()thermal_core的大管家。它负责加锁(防止并发读取导致硬件死锁),并在需要时处理错误。
int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp) { int ret = -EINVAL; mutex_lock(&tz->lock); // 线程安全保护 // 关键点:通过函数指针,穿透回到底层驱动! if (tz->ops->get_temp) ret = tz->ops->get_temp(tz, temp); mutex_unlock(&tz->lock); return ret; }
Step 4: 底层驱动响应 (Driver Layer -> Hardware)
此时,代码执行流回到了第一步里注册的mtk_thermal_get_temp函数中。
底层驱动会:
  1. 读取对应的 ADC (模拟数字转换器) 寄存器。
  2. 按照具体的硬件公式,将原始的 ADC 值转换成毫摄氏度(m℃)。
  3. 将计算结果放入*temp指针中层层返回,最终被sprintf格式化为字符串,吐给上层。

总结架构图

理解了这个流程,你就能在脑海中画出这幅数据流向图:
[ 上层应用 / Vendor 服务 (如 thermal_manager) ] │ (File I/O: read "/sys/class/thermal/thermal_zoneX/temp") ▼ [ VFS / Sysfs 接口层 ] <-- 格式化输出为字符串 │ (temp_show) ▼ [ Thermal Core 核心层 ] <-- 维护设备树配置、状态机和同步锁 │ (tz->ops->get_temp) ▼ [ MTK 传感器驱动层 ] <-- 换算 ADC 到 m℃ │ (读写寄存器) ▼ [ 物理硬件 (ADC / PMIC / CPU Sensor) ]
实战意义:
如果发现温度读不出来,或者遇到系统卡死,你可以根据这套架构精确定位:
  • 没节点?说明底层devm_thermal_zone_of_sensor_register失败了(检查 DTS 或 probe 函数)。
  • 节点有,但 cat 卡死?说明上层调用下发后,底层驱动在读写硬件寄存器时死锁了,或者是tz->lock没解开。
  • 温度跳变不准?直接去查底层驱动里.get_temp函数里的 ADC 转换公式,核心层不管这笔账。
这是一个非常深入且击中要害的问题!在 Linux 驱动开发中,很多初学者会疑惑:上层应用(如 C 语言或 Android native 代码)既然调用了fd = open("/sys/class/thermal/thermal_zone0/temp", O_RDONLY),为什么在底层的mtk_thermal.c驱动里却找不到对应的open函数?
作为驱动工程师,我们需要把 Linux 的文件系统分为两套体系来看:

体系一:Sysfs 伪文件系统(针对你之前查的/sys/.../temp

对于/sys//proc/目录下的节点,底层驱动工程师是不需要、也没有接口去实现open的。
1.open到底在哪?
它在 Linux 内核的通用文件系统层(更具体地说,是fs/kernfs/file.c中的kernfs_fops_open函数)。
2. 为什么底层不需要实现open
因为sysfs的设计理念是“属性(Attribute)映射”。内核认为,读取一个温度值,本质上是读取一个变量(属性),这不需要像打开一个物理串口那样去初始化硬件状态。
3.open发生时,内核做了什么?
当上层应用调用open("/sys/.../temp")时,内核 VFS(虚拟文件系统)拦截了这个请求,交给了kernfs_fops_open。这个通用的open函数只做了一件事:在内存中分配一块 Buffer(通常是基于seq_file机制),准备好一会儿用来装数据。
在这个阶段,底层的 MTK 驱动毫无察觉,你的硬件也不会收到任何指令。
只有当上层接着调用read(fd)时,内核才会沿着我们上一讲的调用栈,穿透下去调用你驱动里的temp_showmtk_thermal_get_temp

体系二:传统字符设备(针对/dev/xxx

如果你的设备节点是创建在/dev/目录下的物理设备或虚拟字符设备(比如/dev/ttyS1或自定义的/dev/mtk_thermal_ctrl),那么open的接口就非常清晰且完全由你掌控了。
1.open在哪里?
它挂载在 Linux 驱动最著名的结构体struct file_operations中。
2. 接口代码是什么样的?
在你的底层驱动里,你需要亲自实现这个open函数,并把它注册给内核:
#include <linux/fs.h> // 1. 你自己实现的底层 open 函数 static int mtk_thermal_dev_open(struct inode *inode, struct file *file) { // 这里做硬件级别的初始化工作 // 比如:给 PMIC 传感器上电、开启 ADC 时钟、申请中断等 bm_debug("MTK thermal device is opened by user!\n"); return 0; // 0 表示成功打开 } // 同理,读写函数 static ssize_t mtk_thermal_dev_read(struct file *file, char __user *buf, size_t count, loff_t *pos) { // ... 读取温度并 copy_to_user ... } // 2. 将它们打包进 file_operations (简称 fops) static const struct file_operations mtk_thermal_fops = { .owner = THIS_MODULE, .open = mtk_thermal_dev_open, // <--- 接口在这里! .read = mtk_thermal_dev_read, .release = mtk_thermal_dev_release, // 对应上层的 close() }; // 3. 在 probe 函数中注册这个字符设备 // register_chrdev( ... , &mtk_thermal_fops);

总结

  • 如果你在看/sys/下的节点代码:别找open了,找不到的。直接搜__ATTR宏或者show/store回调函数,那才是你的数据出入口。
  • 如果你在看/dev/下的节点代码:直接全局搜索struct file_operations或者.open =,就能精确找到上层open呼叫到达底层的落脚点。
http://www.jsqmd.com/news/892430/

相关文章:

  • BMS测试员必看:如何用CANoe+vTESTstudio设计覆盖过压、均衡、SOC的自动化测试场景?
  • Godot 4.2插件实战筛选指南:稳定性、可扩展性与调试友好性黄金三角
  • 司拉德帕Livdelzi常见副作用为背痛及转氨酶一过性升高,需定期监测肝功能
  • 阿富汗物流现状与操作指南(干货版)
  • Linux内核container_of宏的深度解析与实战应用指南
  • 告别数据线:巧用ADB与Scrcpy打造高效无线投屏工作流
  • 终极指南:5分钟免费解锁WeMod专业版功能,告别付费限制
  • 收藏!小白程序员也能抓住的风口:年薪80万+的AI Agent开发之路
  • 如何发起微信投票活动,免费好用热门推荐 - 投票小程序
  • BLE精准设备过滤方案:UUID/名称/MAC/厂商数据过滤
  • 测试工程师转型必备技能,Lovable工具链集成实践与CI/CD无缝对接全路径
  • 避坑指南:用Qt开发蓝牙上位机时,那些官方文档没细说的信号槽和内存管理
  • Node.js + Chrome DevTools 完整联调详细步骤
  • 沙利鲁单抗Kevzara常见副作用为上呼吸道感染中性粒细胞减少及注射部位反应
  • ROS 2机器人网络安全挑战与SROS2安全实践
  • 3分钟搞定Windows PDF处理:Poppler预编译工具完整指南
  • 在自动化工作流中利用 Taotoken 实现多模型智能切换策略
  • 普宁锤子看房锤子哥陈楚周: 从北京一无所有,到普宁房产中介行业翘楚 - 品牌观察
  • 为什么金融企业更倾向于选择全栈国产化Agent方案?金融数字化转型指南
  • FPGA高速并行BCH纠错方案:架构优化与工程实践
  • 在AutoDL上跑图形化AI工具:手把手配置PaddleX的远程开发环境
  • AI导演工坊 · 用角色扮演Agent编排让复杂任务自动化
  • BLE扫描性能与功耗极致优化:间歇扫描、限时扫描、杜绝常驻扫描
  • MP-GT模型:融合GCN与Transformer的App使用预测实战解析
  • 哪家小程序开发工具性价比高?
  • 教育加盟主流指标较量:四类品牌口碑选型 - 资讯速览
  • 车机端实时诊断失效,订单履约中断频发,深度复盘Lovable微服务链路追踪断点及全链路可观测性重构路径
  • Python命令行参数解析:从sys.argv到argparse生产实践
  • 终极指南:如何将Nvidia DLSS-G帧生成替换为AMD FSR 3技术
  • 成都中厚板代理商集团|全系规格,中宽厚钢板工程集采,一站式供货 - 四川盛世钢联营销中心