Linux驱动开发 ---- 4.1_sysfs 详解
📌 sysfs 详细讲解:
sysfs
是 Linux 内核提供的一个虚拟文件系统,它将内核中的设备信息暴露给用户空间。通过 sysfs
,用户可以通过文件接口与内核交互,比如读取硬件设备的状态、修改设备参数等。
主要功能:
- 暴露内核对象:通过
sysfs
,设备、驱动、总线等内核对象的信息被暴露为文件,用户可以通过这些文件读取或写入数据。 - 设备和驱动的接口:很多硬件设备和内核模块在启动时会创建与之相关的
sysfs
文件,供用户空间配置或查询设备状态。
常见路径:
/sys/class/
:包含类目录,如字符设备、块设备等。例如/sys/class/net/
存放网络设备的信息。/sys/devices/
:存放与设备相关的路径,系统中的硬件设备和其属性通过sysfs
展现出来。例如/sys/devices/system/cpu/
存放 CPU 相关的信息。
📌 如何在内核中创建 sysfs
文件:
在 Linux 内核模块中,我们可以通过 sysfs
来创建设备属性文件。这些文件通常会映射到内核对象(如设备)的属性值。
步骤 1:定义 device_attribute
结构体
首先,你需要定义一个 struct device_attribute
类型的结构体来描述你的设备属性。这个结构体至少包含以下内容:
attr
:设备属性本身,包含属性文件的名字和访问权限。show
:一个函数指针,用于读取该属性时的逻辑。store
:一个函数指针,用于修改该属性时的逻辑(可选)。
示例:定义属性文件 myattribute
struct device_attribute dev_attr_myattribute = {
.attr = { .name = "myattribute", .mode = S_IRUGO }, // 属性文件名称和权限
.show = myattribute_show, // 读取文件时调用的函数
};
attr
:这里的name
表示在/sys/class/
路径下文件的名字,mode
定义了文件的访问权限,S_IRUGO
表示该文件可读。show
:这是读取属性时调用的函数。
步骤 2:实现 show
函数
show
函数用于在读取 sysfs
文件时返回相应的数据。函数原型是:
static ssize_t myattribute_show(struct device *dev, struct device_attribute *attr, char *buf);
dev
:指向设备对象的指针。attr
:指向设备属性结构的指针。buf
:用于存放要返回给用户的数据。
在 show
函数中,我们通过 sprintf
函数将要返回的内容写入 buf
。
示例:实现 show
函数
static ssize_t myattribute_show(struct device *dev, struct device_attribute *attr, char *buf) {
return sprintf(buf, "42\n"); // 返回字符串 "42"
}
这个函数会将字符串 "42\n"
写入 buf
,这个字符串就是当用户空间读取 /sys/class/
中对应的文件时返回的数据。
步骤 3:注册 sysfs
文件
使用 sysfs_create_file
函数将设备属性注册到 sysfs
中。这个函数将属性文件创建到设备的 kobj
(内核对象)下。kobj
是与设备关联的内核对象,用来在内核中组织和管理设备。
sysfs_create_file(&dev->kobj, &dev_attr_myattribute.attr);
dev->kobj
:设备对象的kobj
,指向设备的内核对象。dev_attr_myattribute.attr
:dev_attr_myattribute
结构体中的attr
,它包含了属性文件的名字、权限和访问方式。
步骤 4:卸载时移除 sysfs
文件
当内核模块卸载时,必须清理已创建的 sysfs
文件。可以通过 sysfs_remove_file
来移除属性文件:
sysfs_remove_file(&dev->kobj, &dev_attr_myattribute.attr);
📌 完整示例:创建和操作 sysfs
文件
我们将创建一个简单的内核模块,该模块会创建一个 sysfs
文件,返回一个静态的值。
代码实现:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/fs.h>
static ssize_t myattribute_show(struct device *dev, struct device_attribute *attr, char *buf) {
return sprintf(buf, "42\n"); // 返回值为 "42"
}
static struct device_attribute dev_attr_myattribute = {
.attr = { .name = "myattribute", .mode = S_IRUGO },
.show = myattribute_show,
};
static struct class *my_class;
static struct device *my_device;
static int __init my_module_init(void) {
int ret;
// 创建设备类
my_class = class_create(THIS_MODULE, "myclass");
if (IS_ERR(my_class)) {
pr_err("Failed to create class\n");
return PTR_ERR(my_class);
}
// 创建设备
my_device = device_create(my_class, NULL, 0, NULL, "mydevice");
if (IS_ERR(my_device)) {
pr_err("Failed to create device\n");
class_destroy(my_class);
return PTR_ERR(my_device);
}
// 注册 sysfs 属性
ret = sysfs_create_file(&my_device->kobj, &dev_attr_myattribute.attr);
if (ret) {
pr_err("Failed to create sysfs file\n");
device_destroy(my_class, 0);
class_destroy(my_class);
return ret;
}
pr_info("Module initialized\n");
return 0;
}
static void __exit my_module_exit(void) {
// 移除 sysfs 文件
sysfs_remove_file(&my_device->kobj, &dev_attr_myattribute.attr);
// 销毁设备和类
device_destroy(my_class, 0);
class_destroy(my_class);
pr_info("Module exited\n");
}
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple sysfs example");
📌 代码解析:
myattribute_show
:此函数负责返回字符串"42\n"
,表示当用户从sysfs
文件中读取时返回的值。dev_attr_myattribute
:定义了一个名为myattribute
的设备属性,并指定了它的权限为可读(S_IRUGO
)。sysfs_create_file
:将myattribute
属性文件创建到设备的sysfs
中,使得用户空间可以访问该属性。module_init
和module_exit
:模块加载时创建设备并注册sysfs
文件,卸载时删除sysfs
文件并清理资源。
📌 如何使用:
编译并加载该内核模块。
安装开发工具(如果尚未安装):sudo apt-get install build-essential linux-headers-$(uname -r)
1> 创建内核模块代码文件
将你的内核模块代码保存为一个.c
文件(例如my_sysfs_module.c
)。你可以使用任何文本编辑器(例如nano
、vim
)来编辑该文件。nano my_sysfs_module.c
然后将你提供的代码粘贴到
my_sysfs_module.c
文件中并保存。2> 创建
Makefile
为了编译内核模块,你需要创建一个Makefile
,该文件告诉make
如何编译模块。在同一目录下创建一个
Makefile
文件,内容如下:obj-m += my_sysfs_module.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
解释:
obj-m += my_sysfs_module.o
:告诉make
需要编译的模块源文件。make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
:调用内核的构建系统来编译模块。clean
目标:清理编译文件。
3> 编译内核模块
在终端中运行以下命令来编译内核模块:make
这将生成一个
my_sysfs_module.ko
的内核模块文件。
4> 加载内核模块
使用insmod
命令加载内核模块:sudo insmod my_sysfs_module.ko
如果加载成功,你将看到 “Module initialized” 消息,表示模块已成功加载并创建了
sysfs
文件。5> 查看创建的
sysfs
文件
加载模块后,模块会在/sys/class/myclass/mydevice/myattribute
创建一个sysfs
文件。你可以使用以下命令查看该文件:cat /sys/class/myclass/mydevice/myattribute
该文件的内容应为
"42"
,这是在myattribute_show
函数中返回的值。
6> 卸载内核模块
当你不再需要该模块时,可以使用rmmod
卸载它:sudo rmmod my_sysfs_module
在卸载时,会调用
my_module_exit
函数,删除sysfs
文件并销毁设备和类。你会看到 “Module exited” 消息。7> 清理编译文件
为了清理编译产生的文件,可以运行:make clean
8> **总结**
通过这些步骤,你可以成功地编译、加载和操作一个内核模块,该模块创建了一个 `sysfs` 文件并返回一个静态值。
- 加载后,你会在
/sys/class/myclass/mydevice/myattribute
路径下看到一个名为myattribute
的文件。 - 可以通过以下命令查看其内容:
cat /sys/class/myclass/mydevice/myattribute
输出应为:
42
📌 总结:
通过 sysfs
,我们可以方便地暴露内核中的信息给用户空间。通过以上步骤,你不仅学会了如何创建一个简单的 sysfs
文件,还了解了设备与内核对象之间的关系。
- 使用
sysfs_create_file
创建文件; - 使用
show
函数返回数据; - 使用
sysfs_remove_file
移除文件。