在 Linux 内核开发中,DEVICE_ATTR
是一个非常有用的宏,用于在设备驱动中创建设备属性(device attributes)。这些属性可以通过 sysfs 文件系统暴露给用户空间,允许用户空间程序读取或修改设备的某些特性。DEVICE_ATTR
是 Linux 设备模型的一部分,广泛用于驱动开发。
本文将详细讲解 DEVICE_ATTR
的用法,并提供一个简单的例程。
一、DEVICE_ATTR
详解
1. 什么是 DEVICE_ATTR
?
DEVICE_ATTR
是一个宏,用于定义设备属性。它会生成一个 struct device_attribute
类型的变量,并关联到 sysfs 文件系统中。用户空间可以通过 /sys/devices/
目录下的文件与内核交互。
定义在头文件 <linux/device.h>
中,DEVICE_ATTR
的作用是:
- 创建一个 sysfs 文件,允许用户空间读取或写入设备的属性。
- 提供回调函数(show 和 store),用于处理用户空间对属性的读写操作。
2. DEVICE_ATTR
的定义
DEVICE_ATTR
的宏定义如下:
#define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
-
参数说明:
_name
:属性的名称。最终会在 sysfs 中生成一个名为_name
的文件。_mode
:文件的权限模式,例如S_IRUGO
(只读)、S_IWUSR
(只写)、S_IRUGO | S_IWUSR
(读写)。这些权限定义在<linux/stat.h>
中。_show
:读取属性时的回调函数,类型为ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf)
。_store
:写入属性时的回调函数,类型为ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
。
-
生成的变量:
DEVICE_ATTR
会生成一个名为dev_attr_<name>
的struct device_attribute
变量。这个变量可以用于向 sysfs 注册属性。
3. 回调函数的实现
-
show
函数:当用户空间读取 sysfs 文件时调用。函数需要将数据写入buf
并返回写入的字节数。ssize_t show(struct device *dev, struct device_attribute *attr, char *buf);
dev
:设备结构体指针。attr
:设备属性结构体指针。buf
:用户空间读取数据时用来存放数据的缓冲区。- 返回值:写入
buf
的字节数,或者错误码(负值)。
-
store
函数:当用户空间写入 sysfs 文件时调用。函数需要解析buf
中的数据并返回写入的字节数。ssize_t store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
buf
:用户空间写入的数据。count
:写入数据的字节数。- 返回值:成功时返回
count
,或者错误码(负值)。
4. 如何使用 DEVICE_ATTR
- 使用
DEVICE_ATTR
定义属性。 - 使用
device_create_file
将属性注册到 sysfs。 - 在驱动卸载时,使用
device_remove_file
移除属性。
二、简单例程
下面是一个简单的 Linux 内核模块例程,展示如何使用 DEVICE_ATTR
创建一个设备属性。例程的功能是:
- 创建一个虚拟设备。
- 提供一个 sysfs 属性
value
,用户空间可以读取或写入该属性。 - 属性
value
存储一个整数值。
1. 代码实现
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
static struct class *my_class;
static struct device *my_device;
static int my_value = 0; // 用于存储属性值的变量
/* show 函数:用户读取属性时调用 */
static ssize_t value_show(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", my_value); // 将 my_value 写入 buf
}
/* store 函数:用户写入属性时调用 */
static ssize_t value_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
int ret;
// 将用户空间的输入解析为整数
ret = kstrtoint(buf, 10, &my_value);
if (ret < 0) {
return ret; // 解析失败,返回错误
}
return count; // 返回写入的字节数,表示成功
}
/* 定义设备属性:名称为 value,权限为读写,关联 show 和 store 函数 */
static DEVICE_ATTR(value, S_IRUGO | S_IWUSR, value_show, value_store);
static int __init my_driver_init(void)
{
int ret;
/* 创建一个类 */
my_class = class_create(THIS_MODULE, "my_class");
if (IS_ERR(my_class)) {
printk(KERN_ERR "Failed to create class\n");
return PTR_ERR(my_class);
}
/* 创建一个设备 */
my_device = device_create(my_class, NULL, MKDEV(0, 0), NULL, "my_device");
if (IS_ERR(my_device)) {
printk(KERN_ERR "Failed to create device\n");
class_destroy(my_class);
return PTR_ERR(my_device);
}
/* 注册设备属性到 sysfs */
ret = device_create_file(my_device, &dev_attr_value);
if (ret < 0) {
printk(KERN_ERR "Failed to create device attribute\n");
device_destroy(my_class, MKDEV(0, 0));
class_destroy(my_class);
return ret;
}
printk(KERN_INFO "My driver initialized\n");
return 0;
}
static void __exit my_driver_exit(void)
{
/* 移除设备属性 */
device_remove_file(my_device, &dev_attr_value);
/* 销毁设备和类 */
device_destroy(my_class, MKDEV(0, 0));
class_destroy(my_class);
printk(KERN_INFO "My driver exited\n");
}
module_init(my_driver_init);
module_exit(my_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple device attribute example");
2. 代码说明
- 属性定义:
DEVICE_ATTR(value, S_IRUGO | S_IWUSR, value_show, value_store)
定义了一个名为value
的属性,权限为读写,关联的回调函数分别是value_show
和value_store
。 - 初始化:在模块初始化函数中,使用
class_create
和device_create
创建一个虚拟设备,并通过device_create_file
注册属性。 - 卸载:在模块卸载函数中,使用
device_remove_file
移除属性,并销毁设备和类。 - 读写操作:
- 读取属性时,
value_show
将my_value
的值写入缓冲区。 - 写入属性时,
value_store
解析用户输入的字符串并更新my_value
。
- 读取属性时,
3. 编译和测试
-
编写 Makefile:
obj-m += my_driver.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
-
编译模块:
make
-
加载模块:
sudo insmod my_driver.ko
-
查看 sysfs 文件:
加载模块后,属性文件会出现在/sys/class/my_class/my_device/value
。可以用以下命令测试:# 读取属性 cat /sys/class/my_class/my_device/value # 写入属性 echo 42 > /sys/class/my_class/my_device/value # 再次读取属性,验证写入是否成功 cat /sys/class/my_class/my_device/value
-
卸载模块:
sudo rmmod my_driver
4. 测试输出
- 初始读取
value
时,会输出0
(初始值)。 - 写入
42
后,再次读取会输出42
。
三、注意事项
-
权限设置:
- 确保
DEVICE_ATTR
的权限设置合理。如果属性只读,使用S_IRUGO
;如果只写,使用S_IWUSR
;如果读写,使用S_IRUGO | S_IWUSR
。 - 权限设置会影响用户空间对 sysfs 文件的操作权限。
- 确保
-
错误处理:
- 在
store
函数中,解析用户输入时要做好错误处理。例如,使用kstrtoint
或类似函数时,检查返回值。 - 在
show
和store
函数中,返回负值表示错误,会被用户空间程序感知。
- 在
-
缓冲区大小:
show
函数写入buf
的数据不要超过PAGE_SIZE
(通常是 4096 字节)