author: hjjdebug
date: 2026年 05月 26日 星期二 17:15:39 CST
descrip: linux 内核, 怎样读取温度?



目的: 以最简单的温度测量为例, 了解一下linux下的驱动架构模型.
写到最后发现,附录2是最简单的,附录1还挂载了platform设备,platform驱动框架.

温度,首先要有一个测温设备, 这里我们没有具体的测温设备,假想用一个platfrom设备.
我们假想用platform 设备,
有设备则必有驱动, 来与设备进行数据交互, 这里用platform 驱动来对应platform设备
platform 设备是用来测温度的, 所以驱动中可用 内置的thermal-zone 设备来读取.
编译成.ko
执行:
$ insmod virtual_thermal.ko
查看打印信息:
$ dmesg
[28904.713790] virt_thermal: probe
[28904.713841] virt_thermal: register ok

进一步查看系统目录及文件变化

1. 添加platrform设备

virt_dev = platform_device_alloc(“virtual-thermal”, -1);
platform_device_add(virt_dev); //添加了一个platform device, 名字叫virtual-thermal

查看/sysfs 文件系统:
/sys/devices/platform/ 目录下新增加一个virtual-thermal 目录

/sys/devices/platform/virtual-thermal$ ls -l
总用量 0
lrwxrwxrwx 1 root root    0 526 15:53 driver -> ../../../bus/platform/drivers/virtual-thermal
-rw-r--r-- 1 root root 4096 526 16:09 driver_override
-r--r--r-- 1 root root 4096 526 16:09 modalias
drwxr-xr-x 2 root root    0 526 16:09 power
lrwxrwxrwx 1 root root    0 526 16:09 subsystem -> ../../../bus/platform
-rw-r--r-- 1 root root 4096 526 15:53 uevent

hjj@hjj-laptop:/sys/devices/platform/virtual-thermal$ cat driver_override
(null)
hjj@hjj-laptop:/sys/devices/platform/virtual-thermal$ cat modalias
platform:virtual-thermal
hjj@hjj-laptop:/sys/devices/platform/virtual-thermal$ cat uevent
DRIVER=virtual-thermal
MODALIAS=platform:virtual-thermal

有一个子系统链接指向子系统 bus/platform
匹配成功后
有一个driver链接指向对应的driver 目录

2. 注册platform驱动,

static struct platform_driver virt_driver = {
.probe = virt_probe,
.remove = virt_remove,
.driver = {
.name = “virtual-thermal”,
},
};

platform_driver_register(&virt_driver); //注册了一个platform driver,叫virtual-thermal 与device 匹配

查看/sysfs 文件系统:
/sys/bus/platform/drivers/ 目录下新增加一个virtual-thermal 目录

hjj@hjj-laptop:/sys/bus/platform/drivers/virtual-thermal$ ls -l
总用量 0
drwxr-xr-x  2 root root    0 526 15:53 ./
drwxr-xr-x 15 root root    0 526 07:35 ../
--w-------  1 root root 4096 526 15:57 bind
lrwxrwxrwx  1 root root    0 526 15:57 module -> ../../../../module/virtual_thermal/
--w-------  1 root root 4096 526 15:53 uevent
--w-------  1 root root 4096 526 15:57 unbind
lrwxrwxrwx  1 root root    0 526 15:57 virtual-thermal -> ../../../../devices/platform/virtual-thermal/

有一个module 链接指向 driver 的module 目录
匹配成功后, 有一个virtual-thermal 链接指向driver的设备链接

3. platform设备是怎样找到它的驱动的 ?

靠名称匹配. 驱动叫virtual-thermal, 设备也叫virtual-thermal, 这是最原始的一种匹配方式

4. 热区,温度区. thermal_zone.

virt_tz = thermal_zone_device_register( //注册一个thremal_zone,热区,温度区域
“virtual_thermal”, // 1 type
0, // 2 trips
0, // 3 mask
NULL, // 4 devdata
&virt_ops, // 5 ops
&virt_tzp, // 6 tzp
0, // 7 passive_delay
1000 // 8 polling_delay (1秒)
);
thermal_zone 是指一个温度监控对象.
thermal_zone_device_register , 向内核温度子系统(thermal 子系统)
注册一个热区设备. 这样就可以在/sys/class/thermal/ 目录下生成对应的节点. 查是thermal_zone4.
lrwxrwxrwx 1 root root 0 5月 26 16:30 thermal_zone4 -> …/…/devices/virtual/thermal/thermal_zone4/
这个节点对应的设备: /sys/devices/virtual/thermal/thermal_zone4

hjj@hjj-laptop:/sys/devices/virtual/thermal/thermal_zone4$ ls -l
总用量 0
drwxr-xr-x  4 root root    0 526 16:30 ./
drwxr-xr-x 16 root root    0 526 07:35 ../
-r--r--r--  1 root root 4096 526 16:33 available_policies
drwxr-xr-x  3 root root    0 526 16:30 hwmon3/
-rw-r--r--  1 root root 4096 526 16:33 integral_cutoff
-rw-r--r--  1 root root 4096 526 16:33 k_d
-rw-r--r--  1 root root 4096 526 16:33 k_i
-rw-r--r--  1 root root 4096 526 16:33 k_po
-rw-r--r--  1 root root 4096 526 16:33 k_pu
-rw-r--r--  1 root root 4096 526 16:33 offset
-rw-r--r--  1 root root 4096 526 16:33 passive
-rw-r--r--  1 root root 4096 526 16:33 policy
drwxr-xr-x  2 root root    0 526 16:33 power/
-rw-r--r--  1 root root 4096 526 16:33 slope
lrwxrwxrwx  1 root root    0 526 16:30 subsystem -> ../../../../class/thermal/
-rw-r--r--  1 root root 4096 526 16:33 sustainable_power
-r--r--r--  1 root root 4096 526 16:33 temp
-r--r--r--  1 root root 4096 526 16:33 type
-rw-r--r--  1 root root 4096 526 16:30 uevent

这么多文件可读写,
hjj@hjj-laptop:/sys/devices/virtual/thermal/thermal_zone4$ cat k_d
0
hjj@hjj-laptop:/sys/devices/virtual/thermal/thermal_zone4$ cat k_i
0
hjj@hjj-laptop:/sys/devices/virtual/thermal/thermal_zone4$ cat k_po
0
hjj@hjj-laptop:/sys/devices/virtual/thermal/thermal_zone4$ cat k_pu
0
hjj@hjj-laptop:/sys/devices/virtual/thermal/thermal_zone4$ cat offset
0
hjj@hjj-laptop:/sys/devices/virtual/thermal/thermal_zone4$ cat passive
0
hjj@hjj-laptop:/sys/devices/virtual/thermal/thermal_zone4$ cat policy
step_wise
hjj@hjj-laptop:/sys/devices/virtual/thermal/thermal_zone4$ cat slope
0
hjj@hjj-laptop:/sys/devices/virtual/thermal/thermal_zone4$ cat temp  ***************
45000
hjj@hjj-laptop:/sys/devices/virtual/thermal/thermal_zone4$ cat type
virtual_thermal
hjj@hjj-laptop:/sys/devices/virtual/thermal/thermal_zone4$ cat uevent

其中temp文件暴露温度给用户态读取. 它会调用到我们驱动中的代码
其它文件不用关心,它是系统自动生成的.

static int virt_get_temp(struct thermal_zone_device *tz, int *temp)
{
*temp = 45000; // 45℃
return 0;
}
//用户态接口通过系统调用到驱动的回调函数, 要求设备提供温度. 设备把测量的温度反馈回去.

为什么设备会挂靠在devices/virtual/ 总线下?
这是thermal_zone_device_register 函数调用决定的.
thermal 子系统提前提前注册了一个虚拟 class:
static struct class thermal_class = {
.name = “thermal”,
.dev_groups = thermal_groups,
};
thermal_class 本身的属性,决定了设备会自动挂靠到 virtual 总线下
结果,最终自动生成路径:/sys/devices/virtual/thermal/thermal_zoneX

5. 小结:

本来,读取温度,在简单的嵌入式系统中, 只要从一个io地址读取数据就够了.
可是到了linux世界里却变得如此复杂.
主要是因为linux 内核管理之下,用户态程序不能访问I/O.
module 属于内核态, module 可以直接访问I/O,
但当温度由一个外部设备来提供时. 而且外部设备可能千奇百怪.
此时,内核要把外部测温设备想象成一个虚拟设备.
外部设备的数据都要符合一定的规范, 这样不管是任何外部测温设备,都能够和系统接上口.
其中thermal_tone 规范, 就是对外部设备的数据要求规范.
这样,系统屏蔽了具体的测温设备的差异, 具体的测温设备,靠它们的驱动绑定到系统上.

6. 测试代码

$ cat virtual_thermal.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/thermal.h>
#include <linux/err.h>

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Virtual Thermal Driver (8 params)");
MODULE_AUTHOR("test");

static struct thermal_zone_device *virt_tz;

// 温度获取函数(必须实现)
static int virt_get_temp(struct thermal_zone_device *tz, int *temp)
{
    *temp = 45000; // 45℃
    return 0;
}

// 操作函数集
static struct thermal_zone_device_ops virt_ops = {
    .get_temp = virt_get_temp,
};

// 空参数结构体
static struct thermal_zone_params virt_tzp = { };

// 探针函数
static int virt_probe(struct platform_device *pdev)
{
    pr_info("virt_thermal: probe\n");

    // ======================
    // 8 个参数 完全匹配5.4.156内核
    // ======================
    virt_tz = thermal_zone_device_register(  //注册一个thremal_zone,热区,温度区域
        "virtual_thermal",    // 1 type
        0,                    // 2 trips
        0,                    // 3 mask
        NULL,                 // 4 devdata
        &virt_ops,            // 5 ops
        &virt_tzp,            // 6 tzp
        0,                    // 7 passive_delay
        1000                  // 8 polling_delay (1秒)
    );

    if (IS_ERR(virt_tz)) {
        pr_err("thermal register failed\n");
        return PTR_ERR(virt_tz);
    }

    pr_info("virt_thermal: register ok\n");
    return 0;
}

static int virt_remove(struct platform_device *pdev)
{
    thermal_zone_device_unregister(virt_tz);
    pr_info("virt_thermal: removed\n");
    return 0;
}

static struct platform_driver virt_driver = {
    .probe = virt_probe,
    .remove = virt_remove,
    .driver = {
        .name = "virtual-thermal",
    },
};

static struct platform_device *virt_dev;

static int __init virt_init(void)
{
    virt_dev = platform_device_alloc("virtual-thermal", -1);
    platform_device_add(virt_dev); //添加了一个platform device, 名字叫virtual-thermal
    return platform_driver_register(&virt_driver); //注册了一个platform driver,叫virtual-thermal 与device 匹配

}

static void __exit virt_exit(void)
{
    platform_driver_unregister(&virt_driver);
    platform_device_unregister(virt_dev);
}

module_init(virt_init);
module_exit(virt_exit);

能不能再简化一下,不用platform总线,platform 设备, 直接注册一个thermal_zone 设备呢?
应该可以,去掉外边的这层包装就可以了. 代码还可以再继续缩小,
由此代码使我认识到, 系统提供了接口,驱动提供了实现,
这种接口和实现分离的原则.使得可以支持各种设备.
因为实现是驱动完成的,不同的设备跟不同的驱动就可以了.但都要符合内核规范.

7. 简化后的测试代码:

$ cat virtual_thermal.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/thermal.h>
#include <linux/err.h>

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Virtual Thermal Driver (8 params)");
MODULE_AUTHOR("test");

static struct thermal_zone_device *virt_tz;

// 温度获取函数(必须实现)
static int virt_get_temp(struct thermal_zone_device *tz, int *temp)
{
    *temp = 45000; // 45℃
    return 0;
}

// 操作函数集
static struct thermal_zone_device_ops virt_ops = {
    .get_temp = virt_get_temp,
};

// 空参数结构体
static struct thermal_zone_params virt_tzp = { };

static int __init virt_init(void)
{
    pr_info("virt_thermal: init\n");
    // ======================
    // 8 个参数 完全匹配5.4.156内核
    // ======================
    virt_tz = thermal_zone_device_register(  //注册一个thremal_zone,热区,温度区域
        "virtual_thermal",    // 1 type
        0,                    // 2 trips
        0,                    // 3 mask
        NULL,                 // 4 devdata
        &virt_ops,            // 5 ops        //这个热区实例,与驱动的virt_ops 相binding
        &virt_tzp,            // 6 tzp
        0,                    // 7 passive_delay
        1000                  // 8 polling_delay (1秒)
    );

    if (IS_ERR(virt_tz)) {
        pr_err("thermal register failed\n");
        return PTR_ERR(virt_tz);
    }

    pr_info("virt_thermal: register ok\n");
    return 0;
}

static void __exit virt_exit(void)
{
    thermal_zone_device_unregister(virt_tz);
    pr_info("virt_thermal: removed\n");
}

module_init(virt_init);
module_exit(virt_exit);

思考:
什么是linux 驱动?
内核提供接口, 驱动和设备提供具体实现.
设备是丰富多彩的,千变万化的. 驱动要把设备的语言翻译成内核认识的格式.
所以说驱动是翻译官, 驱动是设备的操作方法. 说法都正确.
内核只管喊给我温度, 驱动1就会问与自己binding的热偶设备1 多少度啊? 50, 马上把50反馈给系统了.
现在换个设备, 是用热电阻测量温度的, 驱动2就会用另一种方式问设备, 现在多少度? 50, 驱动2 又返回给系统数值了.
测温远不止这2种方式, 人家用红外的, 用玻璃的,用核磁的,甚至新工艺的,不管你是什么设备,
与之配套的驱动只要完成virt_get_temp, 从设备中拿到数据,返回给系统就可以了.

内核里多了一个设备,内核通过驱动来与设备实例交互.
其中platform设备与platform驱动有一个binding 过程. 设备和驱动binding才能工作.

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐