4.9 实现sysfs属性的持久存储
通过第4.6节 sysfsdrv.c 驱动设置的 bdrate 属性,仅被存储在驱动程序定义的 baudrate 变量中。
一旦系统重启,或者卸载驱动后重新加载,则用户设置的属性值将失效,需要用户重新设置。
如果要使 bdrate 属性值在系统重启后仍然有效,则需要将其持久存储。
通常,可以将用户设置的属性值保存到一个配置文件中,并在驱动加载时从该文件中读取并应用。
4.9.1 创建配置文件
首先,选择一个位置来存储配置文件,例如 /etc/my_device_config
。在这个文件中保存 baudrate 的值:
(base) neardi@LPA3588:~/drivers/sysfs$ sudo touch /etc/my_device_config
(base) neardi@LPA3588:~/drivers/sysfs$ sudo echo "baudrate=9600" > /etc/my_device_config
-bash: /etc/my_device_config: Permission denied
(base) neardi@LPA3588:~/drivers/sysfs$ echo "baudrate=9600" | sudo tee /etc/my_device_config
baudrate=9600
(base) neardi@LPA3588:~/drivers/sysfs$ sudo cat /etc/my_device_config
baudrate=9600
(base) neardi@LPA3588:~/drivers/sysfs$
注意:向配置文件中写入初始数据时,不能用 sudo echo "baudrate=9600" > /etc/my_device_config
,而应使用 echo "baudrate=9600" | sudo tee /etc/my_device_config
。因为 sudo 只会提升 echo 命令的权限,而重定向操作 >
仍然在普通用户的权限下执行。
4.9.2 加载配置文件
新增一个读取配置文件,加载用户配置数据的函数 load_baudrate_from_file,内容如下:
// Load baud rate from the configuration file
static void load_baudrate_from_file(void)
{
struct file *file;
mm_segment_t old_fs;
char buf[32];
int ret;
// Save the current address space limit
old_fs = get_fs();
// Set the address space limit to kernel space
set_fs(KERNEL_DS);
// Open the configuration file in read-only mode
file = filp_open("/etc/my_device_config", O_RDONLY, 0);
if (IS_ERR(file))
{
printk(KERN_ERR "Failed to open config file\n");
set_fs(old_fs);
return;
}
// Read the content of the configuration file into the buffer
ret = kernel_read(file, buf, sizeof(buf), &file->f_pos);
if (ret > 0)
{
sscanf(buf, "baudrate=%d", &baudrate);
}
// Close the configuration file
filp_close(file, NULL);
// Restore the original address space limit
set_fs(old_fs);
}
在模块的初始化函数 sysfsdrv_init 中,增加 load_baudrate_from_file 函数的调用语句,如下:
static int __init sysfsdrv_init(void)
{
// Load baud rate from the configuration file
load_baudrate_from_file();
printk(KERN_INFO "Baudrate loaded: %d\n", baudrate);
....
}
4.9.3 同步保存属性值
修改 store_bdrate 函数的定义,一旦波特率属性 baudrate 发生更改,则同步保存 baudrate 属性值到配置文件 /etc/my_device_config 中。
修改后的 store_bdrate 函数定义如下:
static ssize_t store_bdrate(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
struct file *file;
mm_segment_t old_fs;
char config_buf[32];
int new_baudrate;
printk("%s: buf = %s\n", __func__, buf);
sscanf(buf, "%d", &new_baudrate);
baudrate = new_baudrate;
old_fs = get_fs(); // Save the Current Address Space Limit
set_fs(KERNEL_DS); // Set the Address Space Limit to Kernel Space
// Open the Configuration File
file = filp_open("/etc/my_device_config", O_WRONLY | O_CREAT, 0644);
if (!IS_ERR(file))
{
// Write the New Baud Rate to the File
snprintf(config_buf, sizeof(config_buf), "baudrate=%d\n", baudrate);
kernel_write(file, config_buf, strlen(config_buf), &file->f_pos);
filp_close(file, NULL);
}
else
{
printk(KERN_ERR "Failed to open config file for writing\n");
}
set_fs(old_fs);
return count;
}
4.9.4 导入命名空间
上面的修改使用到了 kernel_read、kernel_write 和 filp_open 这些函数,由于这些函数属于 VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver 命名空间。 如果驱动模块不导入这个命名空间,编译时将会报 modpost 错误,因此需要在模块中添加如下声明,来显式导入这个命名空间。
MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver);
4.10 更新后的sysfs驱动代码
复制第4.6节的 sysfsdrv.c 文件,得到 sysfsdrv_store_attr.c 文件,在新文件上添加第4.9节的修改之后,得到的完整代码如下:
#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_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver);
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,
};
// Load baud rate from configuration file
static void load_baudrate_from_file(void)
{
struct file *file;
mm_segment_t old_fs;
char buf[32];
int ret;
// Save the current address space limit
old_fs = get_fs();
// Set the address space limit to kernel space
set_fs(KERNEL_DS);
// Open the configuration file in read-only mode
file = filp_open("/etc/my_device_config", O_RDONLY, 0);
if (IS_ERR(file))
{
printk(KERN_ERR "Failed to open config file\n");
set_fs(old_fs);
return;
}
// Read the content of the configuration file into the buffer
ret = kernel_read(file, buf, sizeof(buf), &file->f_pos);
if (ret > 0)
{
sscanf(buf, "baudrate=%d", &baudrate);
}
// Close the configuration file
filp_close(file, NULL);
// Restore the original address space limit
set_fs(old_fs);
}
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)
{
struct file *file;
mm_segment_t old_fs;
char config_buf[32];
int new_baudrate;
printk("%s: buf = %s\n", __func__, buf);
sscanf(buf, "%d", &new_baudrate);
baudrate = new_baudrate;
old_fs = get_fs(); // Save the Current Address Space Limit
set_fs(KERNEL_DS); // Set the Address Space Limit to Kernel Space
// Open the Configuration File
file = filp_open("/etc/my_device_config", O_WRONLY | O_CREAT, 0644);
if (!IS_ERR(file))
{
// Write the New Baud Rate to the File
snprintf(config_buf, sizeof(config_buf), "baudrate=%d\n", baudrate);
kernel_write(file, config_buf, strlen(config_buf), &file->f_pos);
filp_close(file, NULL);
}
else
{
printk(KERN_ERR "Failed to open config file for writing\n");
}
set_fs(old_fs);
return count;
}
static DEVICE_ATTR(bdrate, 0664, show_bdrate, store_bdrate);
static int __init sysfsdrv_init(void)
{
// Load baudrate from configure file
load_baudrate_from_file();
printk(KERN_INFO "Baudrate loaded: %d\n", baudrate);
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);
可以依次执行如下命令,安装 meld,然后使用 meld 对 sysfsdrv.c 和 sysfsdrv_store_attr.c 文件的内容进行比较。
(base) neardi@LPA3588:~/drivers/sysfs$ sudo apt update
(base) neardi@LPA3588:~/drivers/sysfs$ sudo apt install meld
(base) neardi@LPA3588:~/drivers/sysfs$ meld sysfsdrv.c sysfsdrv_store_attr.c
4.11 编译更新后的 sysfs 驱动代码
4.11.1 修改 Makefile
修改 Makefile 中的模块文件名之后,内容如下:
obj-m += sysfsdrv_store_attr.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.11.2 编译、加载、卸载、测试
1) 编译、加载驱动模块,查看配置文件和驱动模块中的初始属性值
(base) 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_store_attr.o
MODPOST /home/neardi/drivers/sysfs/Module.symvers
CC [M] /home/neardi/drivers/sysfs/sysfsdrv_store_attr.mod.o
LD [M] /home/neardi/drivers/sysfs/sysfsdrv_store_attr.ko
make[1]: Leaving directory '/usr/src/linux-headers-5.10.110'
(base) neardi@LPA3588:~/drivers/sysfs$
(base) neardi@LPA3588:~/drivers/sysfs$ sudo dmesg -C
(base) neardi@LPA3588:~/drivers/sysfs$ sudo insmod sysfsdrv_store_attr.ko
(base) neardi@LPA3588:~/drivers/sysfs$ sudo dmesg
[30637.711306] Baudrate loaded: 9600
[30637.711554] sysfs(demo sysfs): Device registered with major number 234
(base) neardi@LPA3588:~/drivers/sysfs$ sudo cat /etc/my_device_config
baudrate=9600
(base) neardi@LPA3588:~/drivers/sysfs$ cd /sys/class/demo_sysfs_class/demo_sysfs
(base) neardi@LPA3588:/sys/class/demo_sysfs_class/demo_sysfs$ ll
total 0
drwxr-xr-x 3 root root 0 Aug 28 09:46 ./
drwxr-xr-x 3 root root 0 Aug 28 09:46 ../
-rw-rw-r-- 1 root root 4096 Aug 28 09:51 bdrate
-r--r--r-- 1 root root 4096 Aug 28 09:51 dev
drwxr-xr-x 2 root root 0 Aug 28 09:51 power/
lrwxrwxrwx 1 root root 0 Aug 28 09:51 subsystem -> ../../../../class/demo_sysfs_class/
-rw-r--r-- 1 root root 4096 Aug 28 09:46 uevent
(base) neardi@LPA3588:/sys/class/demo_sysfs_class/demo_sysfs$ cat bdrate
9600
2) 用户重新设置属性值,查看驱动模块和配置文件中,修改后的属性值均为 19200
(base) neardi@LPA3588:/sys/class/demo_sysfs_class/demo_sysfs$ echo 19200 | sudo tee bdrate
19200
(base) neardi@LPA3588:/sys/class/demo_sysfs_class/demo_sysfs$ cat bdrate
19200
(base) neardi@LPA3588:/sys/class/demo_sysfs_class/demo_sysfs$ cat /etc/my_device_config
baudrate=19200
3) 卸载驱动模块或重启系统后,重新加载驱动模块,驱动模块和配置文件中,均仍为用户上一次设置的属性值 19200
(base) neardi@LPA3588:/sys/class/demo_sysfs_class/demo_sysfs$ sudo rmmod sysfsdrv_store_attr
(base) neardi@LPA3588:/sys/class/demo_sysfs_class/demo_sysfs$ lsmod
Module Size Used by
bcmdhd 1490944 0
dhd_static_buf 16384 1 bcmdhd
(base) neardi@LPA3588:/sys/class/demo_sysfs_class/demo_sysfs$ sudo dmesg
[30637.711306] Baudrate loaded: 9600
[30637.711554] sysfs(demo sysfs): Device registered with major number 234
[31191.412304] store_bdrate: buf = 19200
[31481.874312] Goodbye!
(base) neardi@LPA3588:/sys/class/demo_sysfs_class/demo_sysfs$ cd ~/drivers/sysfs/
(base) neardi@LPA3588:~/drivers/sysfs$ sudo dmesg -C
(base) neardi@LPA3588:~/drivers/sysfs$ sudo insmod sysfsdrv_store_attr.ko
(base) neardi@LPA3588:~/drivers/sysfs$ dmesg
[31821.326116] Baudrate loaded: 19200
[31821.327951] sysfs(demo sysfs): Device registered with major number 234
(base) neardi@LPA3588:~/drivers/sysfs$ cd /sys/class/demo_sysfs_class/demo_sysfs
(base) neardi@LPA3588:/sys/class/demo_sysfs_class/demo_sysfs$ ll
total 0
drwxr-xr-x 3 root root 0 Aug 28 10:05 ./
drwxr-xr-x 3 root root 0 Aug 28 10:05 ../
-rw-rw-r-- 1 root root 4096 Aug 28 10:06 bdrate
-r--r--r-- 1 root root 4096 Aug 28 10:06 dev
drwxr-xr-x 2 root root 0 Aug 28 10:06 power/
lrwxrwxrwx 1 root root 0 Aug 28 10:06 subsystem -> ../../../../class/demo_sysfs_class/
-rw-r--r-- 1 root root 4096 Aug 28 10:05 uevent
(base) neardi@LPA3588:/sys/class/demo_sysfs_class/demo_sysfs$ cat bdrate
19200
(base) neardi@LPA3588:/sys/class/demo_sysfs_class/demo_sysfs$ cat /etc/my_device_config
baudrate=19200
由此可见,用户设置的属性值,能够在系统中持久存储,并且能够被 sysfs 驱动成功读取和更新。