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 驱动成功读取和更新。