第四章 驱动sysfs
在 Linux 驱动开发中,sysfs 提供了一种强大的机制,用于将内核对象的属性公开给用户空间。通过 sysfs,用户可以方便地读取和修改设备的属性,从而实现设备的配置和监控。本章将详细介绍如何在驱动程序中使用 sysfs。
4.1 什么是 sysfs?
sysfs 是一个基于 RAM 的文件系统,最初基于 ramfs; sysfs 是 Linux 内核中的一种虚拟文件系统,通常挂载在 /sys 目录下。 它提供了一种统一的接口,使用户空间程序可以以文件系统的方式访问内核数据。每个内核对象(如设备、驱动程序、类等)都可以在 sysfs 中有一个对应的目录,目录下包含该对象的属性文件。
如下命令查看sysfs挂载属性:
root@LPA3588:# mount | grep sysfs
sysfs on /sys type sysfs (rw,relatime)
root@LPA3588:# cat /proc/mounts | grep sysfs
sysfs /sys sysfs rw,relatime 0 0
root@LPA3588:# findmnt -t sysfs
TARGET SOURCE FSTYPE OPTIONS
/sys sysfs sysfs rw,relatime
sysfs 表示文件系统类型。
/sys 是挂载点。
rw,nosuid,nodev,noexec,relatime 是挂载选项。
4. 2. 为什么使用 sysfs?
使用 sysfs 有以下几个主要优点:
• 调试和监控:开发人员可以通过 sysfs 属性轻松地调试和监控设备的状态和行为。
• 配置设备:用户可以通过 sysfs 属性配置设备的参数,而无需重新编译内核或驱动程序。
• 简化用户接口:sysfs 提供了一种统一的接口,使用户空间程序可以以文件系统的方式访问内核数据。
4.3. 如何添加 sysfs 属性
以下是更改baudrate属性值示例,展示了如何在驱动程序中添加 sysfs 属性。
4.3.1 定义属性的显示和存储函数
首先,需要定义属性的显示和存储函数。显示函数用于读取属性值,存储函数用于写入属性值。
static int baudrate= 115200; // the global variable to export this attribute.
/**
* show this attribute when user 'cat' command from user space.
*/
static ssize_t show_bdrate(struct device *dev, struct device_attribute *attr, char *buf) {
return sprintf(buf, "%d\n", baudrate);
}
/**
* save attribute to this global variable when user to set a new value by 'echo' from user space.
*/
static ssize_t store_bdrate(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) {
sscanf(buf, "%d", &baudrate);
return count;
}
4.3.2 定义属性结构
Linux设备属性结构体定义
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
};
• attr: 这是一个标准的sysfs属性结构体。
• show: 读取属性值的回调函数。
• store: 写入属性值的回调函数。
使用 DEVICE_ATTR 宏定义属性结构, DEVICE_ATTR宏本身定义如下:。
#define DEVICE_ATTR(name, mode, show, store)
• name: 属性名称。
• mode: 文件权限模式(如0644)。
• show: 读取属性值的函数指针。
• store: 写入属性值的函数指针。
这样使用如下定义即可导出设备属性:
static DEVICE_ATTR(bdrate, 0664, show_bdrate, store_bdrate);
4.4 创建属性文件
在驱动程序的初始化函数中,使用 device_create_file 函数将属性文件添加到 sysfs。
static int __init sysfsdrv_init(void) {
int ret;
/**
* here is other initialize codes ...
*/
ret = device_create_file(demo_sysfs_dev, &dev_attr_bdrate);
if (ret) {
printk(KERN_ALERT "Failed to create sysfs attribute\n");
return ret;
}
return 0;
}
4.5 删除属性文件
在驱动程序的退出函数中,使用 device_remove_file 函数删除属性文件。
static void __exit hello_exit(void) {
device_remove_file(demo_sysfs_dev, &dev_attr_bdrate);
// other free resource codes...
}
4.6 sysfs驱动代码
在第三章的代码基础上, 添加sysfs属性, 创建一个sysfsdrv.c文件, 完整代码如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#define DEVICE_NAME "demo_sysfs"
#define CLASS_NAME "demo_sysfs_class"
#define BUFFER_SIZE 1024
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linx zhang");
MODULE_DESCRIPTION("A simple driver with sysfs property");
static int major;
static char message[BUFFER_SIZE] = { 0 };
static short message_len;
static struct class *demo_sysfs_class = NULL;
static struct device *demo_sysfs_device = NULL;
static int baudrate = 115200;
static int device_open(struct inode*, struct file *filp);
static int device_release(struct inode*, struct file *filp);
static ssize_t device_read(struct file *filp, char __user *buffer, size_t len, loff_t *offset);
static ssize_t device_write(struct file *filp, const char __user *buffer, size_t len, loff_t *offset);
static struct file_operations fops = { .open = device_open, .read = device_read,
.write = device_write, .release = device_release, };
static ssize_t show_bdrate(struct device *dev, struct device_attribute *attr, char *buf) {
return sprintf(buf, "%d\n", baudrate);
}
static ssize_t store_bdrate(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) {
printk("%s: buf = %s\n", __func__, buf);
sscanf(buf, "%d", &baudrate);
return count;
}
static DEVICE_ATTR(bdrate, 0664, show_bdrate, store_bdrate);
static int __init sysfsdrv_init(void) {
major = register_chrdev(0, DEVICE_NAME, &fops);
if (major < 0) {
printk(KERN_ALERT "sysfs: Registering char device failed with %d\n", major);
return major;
}
demo_sysfs_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(demo_sysfs_class)) {
unregister_chrdev(major, DEVICE_NAME);
printk(KERN_ALERT "Failed to register device class\n");
return PTR_ERR(demo_sysfs_class);
}
demo_sysfs_device = device_create(demo_sysfs_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);
if (IS_ERR(demo_sysfs_device)) {
class_destroy(demo_sysfs_class);
unregister_chrdev(major, DEVICE_NAME);
printk(KERN_ALERT "Failed to create the device\n");
return PTR_ERR(demo_sysfs_device);
}
if (device_create_file(demo_sysfs_device, &dev_attr_bdrate)) {
printk(KERN_ALERT "Failed to create the sysfs\n");
device_destroy(demo_sysfs_class, MKDEV(major, 0));
class_destroy(demo_sysfs_class);
unregister_chrdev(major, DEVICE_NAME);
return -ENOMEM;;
}
printk(KERN_INFO "sysfs(demo sysfs): Device registered with major number %d\n", major);
return 0;
}
static void __exit sysfsdrv_exit(void) {
device_remove_file(demo_sysfs_device, &dev_attr_bdrate);
device_destroy(demo_sysfs_class, MKDEV(major, 0));
class_unregister(demo_sysfs_class);
class_destroy(demo_sysfs_class);
unregister_chrdev(major, DEVICE_NAME);
printk(KERN_INFO "Goodbye!\n");
}
/**
* Applications open this device driver.
*/
static int device_open(struct inode *inode, struct file *file) {
printk(KERN_INFO "sysfs: Device opened\n");
return 0;
}
/**
* Applications close this device driver.
*/
static int device_release(struct inode *inode, struct file *file) {
printk(KERN_INFO "sysfs: Device closed\n");
return 0;
}
/**
* Send data to applications.
*/
static ssize_t device_read(struct file *filp, char __user *buffer, size_t len, loff_t *offset) {
int bytes_read = 0;
size_t message_len = strlen(message); // Assuming message is a null-terminated string
// Ensure we do not read more than the message length
if (len > message_len) {
len = message_len;
}
// Copy data from kernel space to user space
if (copy_to_user(buffer, message, len)) {
return -EFAULT; // Return error if copy fails
}
bytes_read = len;
message_len = 0; // Reset message length after reading
return bytes_read;
}
/**
* Get user data from applications.
*/
static ssize_t device_write(struct file *filp, const char __user *buffer, size_t len, loff_t *offset) {
size_t bytes_to_write = len < BUFFER_SIZE ? len : BUFFER_SIZE;
// Copy data from user space to kernel space
if (copy_from_user(message, buffer, bytes_to_write)) {
return -EFAULT; // Return error if copy fails
}
message_len = bytes_to_write;
return bytes_to_write;
}
module_init( sysfsdrv_init);
module_exit( sysfsdrv_exit);
4.7 编译sysfs驱动代码
4.7.1 Makefile
创建一个Makefile文件, 内容如下:
obj-m += sysfsdrv.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
4.7.2 编译
编译环境的搭建, 请参考第二章"编译“小节。
neardi@LPA3588:~/drivers/sysfs$ make
make -C /lib/modules/5.10.110/build M=/home/neardi/drivers/sysfs modules
make[1]: Entering directory '/usr/src/linux-headers-5.10.110'
CC [M] /home/neardi/drivers/sysfs/sysfsdrv.o
MODPOST /home/neardi/drivers/sysfs/Module.symvers
CC [M] /home/neardi/drivers/sysfs/sysfsdrv.mod.o
LD [M] /home/neardi/drivers/sysfs/sysfsdrv.ko
make[1]: Leaving directory '/usr/src/linux-headers-5.10.110'
4.7.3 加载驱动
加载sysfsdrv.ko, 如下:
neardi@LPA3588:~/drivers/sysfs$ sudo insmod sysfsdrv.ko
neardi@LPA3588:~/drivers/sysfs$ dmesg
[21790.565127] sysfs(demo sysfs): Device registered with major number 234
上面可以看出加载驱动成功。
4.8 测试sysfs属性
1). 先查看属性节点是否创建。属性节点在如下路径:
neardi@LPA3588:~$ sudo -i
root@LPA3588:~# cd /sys/class/demo_sysfs_class/demo_sysfs
root@LPA3588:/sys/class/demo_sysfs_class/demo_sysfs# ls -l
total 0
-rw-rw-r-- 1 root root 4096 Aug 15 16:10 bdrate
-r--r--r-- 1 root root 4096 Aug 15 16:10 dev
drwxr-xr-x 2 root root 0 Aug 15 16:10 power
lrwxrwxrwx 1 root root 0 Aug 15 16:10 subsystem -> ../../../../class/demo_sysfs_class
-rw-r--r-- 1 root root 4096 Aug 15 16:04 uevent
2). 读取及更改属性值
root@LPA3588:/sys/class/demo_sysfs_class/demo_sysfs# cat bdrate
115200
root@LPA3588:/sys/class/demo_sysfs_class/demo_sysfs# echo 9600 > bdrate
root@LPA3588:/sys/class/demo_sysfs_class/demo_sysfs# cat bdrate
9600
root@LPA3588:/sys/class/demo_sysfs_class/demo_sysfs# dmesg
[25078.707651] sysfs(demo sysfs): Device registered with major number 234
[25743.765609] store_bdrate: buf = 9600
上面可以看出, 读取和更改属性值成功。