Linux kernel 将buf保存到文件

 old_fs = get_fs();
    set_fs(get_ds());
   
    filp->f_op->write(filp, buff, strlen(buff), &filp->f_pos);
   
    filp->f_op->llseek(filp,0,0);
    ret = filp->f_op->read(filp, tmp, strlen(buff), &filp->f_pos);
   
    set_fs(old_fs);

要注意filp->f_op->write 可能为空
一般用以下方法将buf保存到文件


old_fs = get_fs();
     set_fs(KERNEL_DS);
 
 
     if( first_flag==0){
     first_flag=1;
     /* open file to write */
     fp = filp_open("/data/at_log1", O_WRONLY|O_CREAT, 0640);
     if (!fp) {
         printk("%s: open file error\n", __FUNCTION__);
         ret = -1;
         goto exit;
         }
     }
     pos=(unsigned long)offset;
 
     /* Write buf to file */
     nwrite=vfs_write(fp, buf, size, &pos);

访问文件系统主要用到以下函数。
1 打开文件,与c库类似
strcut file* filp_open(const char* filename, int open_mode, int mode);
2 读写。pos为偏移,需要初始化。更需要注意的是buffer是__user* ,指用户空间地址, 如果我们直接使用内核空间的指针,则会返回-EFALUT。则需要多做一步修改内核地址检查的操作。
ssize_t vfs_read(struct file* filp, char __user* buffer, size_t len, loff_t* pos);
ssize_t vfs_write(struct file* filp, const char __user* buffer, size_t len, loff_t* pos);
get_fs()、set_fs() 的作用
内存地址检查的处理方式。这点补充有关内核地址和用户空间地址的操作。在mm_segment_t fs中只有两个值,USER_DS,KERNEL_DS,分别代表用户空间和内核空间,默认情况为USER_DS,即对用户空间地址检查并做变换。
老版本的内核用上述方法是OK的。我用的是5.15的内核,编译到get_fs、set_fs会报错。
在这里插入图片描述
5.10后的版本不能使用set_fs了。
在这里插入图片描述
看到一种方案,根据内核版本做以下调整。Linux-5.0.0前的内核使用set_fs( get_ds() ,介于linux-5.0.0和linux-5.10.0之间的内核使用set_fs( KERNEL_DS ),linux-5.10.0之后的内核使用old_fs = force_uaccess_begin();
在我用的内核下编译还是有问题,应该是我的内核版本太新了。


#if LINUX_VERSION_CODE < KERNEL_VERSION(5,0,0)
    old_fs = get_fs();
    set_fs( get_ds() );
#elif LINUX_VERSION_CODE < KERNEL_VERSION(5,10,0)
    old_fs = get_fs();
    set_fs( KERNEL_DS );
#else
    old_fs = force_uaccess_begin();
#endif
...
        ret = fd->f_op->unlocked_ioctl(fd, TCSETS, (unsigned long int) &newtio);
...
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,10,0)
    set_fs(old_fs);
#else
    force_uaccess_end(old_fs);
#endif

coredump保存内存到文件的方法
kernel里有大量保存内存的文件的操作,参考coredump的用法。
在这里插入图片描述
coredump中使用了__kernel_write,这个函数不依赖set_fs的操作。
一个完整的·demo


1 #include <linux/init.h>
  2 #include <linux/module.h>
  3 #include <linux/sched.h>
  4 #include <linux/dcache.h>
  5 #include <linux/fs.h>
  6 #include <linux/err.h>
  7 #include <linux/string.h>
  8 #include <linux/errno.h>
  9 #include <asm/fcntl.h>
 10 #include <asm/processor.h>
 11 #include <asm/uaccess.h>
 12 #include <linux/thread_info.h>
 13
 14 int __init hello_init(void)
 15 {
 16     unsigned char buf1[12]="hello world.";
 17     unsigned char buf2[12]="kernel file.";
 18
 19     struct file *fp;
 20     //mm_segment_t fs;
 21     loff_t pos;
 22
 23     printk("hello enter\n");
 24     fp = filp_open("/root/perf_file", O_RDWR | O_CREAT, 0644);
 25     if (IS_ERR(fp)) {
 26         printk("create file error\n");
 27         return -1;
 28     }
 29     //fs = force_uaccess_begin();
 30
 31    // current_thread_info()->addr_limit = 0;
 32     //fs = get_fs();
 33     //set_fs(KERNEL_DS);
 34
 35     pos = fp->f_pos;
 36     __kernel_write(fp, buf1, sizeof(buf1), &pos);
 37     fp->f_pos = pos;
 38
 39     pos = fp->f_pos;
 40     __kernel_write(fp, buf2, sizeof(buf2), &pos);
 41     fp->f_pos = pos;
 42
 43     //set_fs(fs);
 44     //force_uaccess_end(fs);
 45
 46     filp_close(fp, NULL);
 47     return 0;
 48 }
 49
 50 void __exit hello_exit(void)
 51 {
 52     printk("hello exit\n");
 53 }
 54
 55 module_init(hello_init);
 56 module_exit(hello_exit);
 57
 58 MODULE_LICENSE("GPL");

<think>好的,用户的问题是:如何在Linux内核驱动中实现变量的持久化存储,确保设备重启后能够恢复其值。我需要逐步思考如何解决这个问题。 首先,我需要理解用户的需求。用户希望在内核驱动中保存变量的值,即使设备重启后,这个值还能被读取到。这类似于应用程序中的持久化存储,但内核空间和用户空间不同,所以方法也不同。 接下来,我应该考虑Linux内核中可用的持久化存储机制。通常,用户空间会使用文件系统来保存数据,但内核模块不能直接使用用户空间的文件操作。那么内核模块有哪些方法呢? 可能的解决方案包括: 1. 使用sysfs:通过sysfs将变量暴露给用户空间,然后用户空间的脚本在关机时保存,启动时读取并写回。这种方法需要用户空间配合,可能不够可靠,因为如果系统意外关机,数据可能丢失。 2. 内核文件系统:比如在/proc或/sys下创建文件,但同样需要用户空间协助保存数据,存在同样的问题。 3. 保留内存(Reserved Memory):在内核启动参数中保留一块内存区域,不会被系统覆盖。驱动可以使用这块内存来存储数据。这个方法需要在设备树或启动参数中预留内存,并且确保该区域不会被其他部分使用。设备重启时,如果内存没有被重置(如冷启动),数据可能保留,但某些情况下如硬重启或断电,数据还是会丢失。 4. 文件系统操作:在内核模块中直接操作文件系统,将数据保存到特定的文件中。虽然内核有文件操作的函数,如filp_open、vfs_write等,但这种方法风险较高,容易导致内核不稳定,尤其是在写入文件时可能引发死锁或其他问题。此外,文件系统必须已经挂载,这在启动早期或关机后期可能不可用。 5. NVRAM(非易失性存储器):如果硬件支持,可以直接读写NVRAM区域。这需要硬件有特定的存储区域,如SPI Flash或EEPROM,并且驱动需要访问这些设备的接口。这种方法可靠性高,但依赖硬件支持。 6. 内核配置或参数:使用内核的启动参数来传递值,或者在模块加载时传递参数。但这需要每次启动时手动设置,或者通过引导加载程序(如U-Boot)来保存和传递,可能不够自动化。 现在需要分析每个方法的优缺点,并给出推荐的方法。 保留内存和NVRAM是比较可靠的方法,但依赖硬件或启动参数配置。Sysfs和文件系统方法需要用户空间配合,可能在意外关机时丢失数据。内核文件操作风险较大,不推荐。 所以,可能的解决方案是:推荐使用sysfs与用户空间脚本配合,或者使用保留内存,或者直接访问NVRAM。 接下来需要具体说明如何实现这些方法,并给出示例代码。 例如,对于sysfs方法,可以在驱动中创建sysfs属性,在关机时触发保存操作,启动时读取。但需要处理如何捕获关机事件,可能需要注册一个关机通知回调。 对于保留内存,需要在设备树中预留内存区域,驱动中使用ioremap或memremap来访问该区域。例如,在设备树中添加保留内存的节点,驱动中解析该节点并映射内存。这样,在重启时(如果是软重启,且内存未被清除),数据可以保留。 对于NVRAM访问,需要具体硬件的驱动支持,例如通过SPI或I2C接口访问EEPROM,驱动中使用相应的读写函数。 此外,可能还需要考虑数据的一致性和完整性,比如在写入时防止断电导致数据损坏,可以使用校验和或者事务机制。 然后,用户可能想知道如何具体实现这些方法。需要分步骤说明,并提供代码示例。 例如,对于sysfs方法,可以创建一个变量,并在模块初始化时从文件中读取值,在模块退出时写入文件。但需要用户空间的服务来在关机时保存数据,启动时加载。这可能涉及到init脚本或systemd服务。 对于保留内存,需要在设备树中添加: reserved-memory { #address-cells = <1>; #size-cells = <1>; ranges; my_region: region@addr { compatible = "my_driver,reserved-memory"; reg = <0xaddr 0xsize>; no-map; }; }; 驱动中通过of_reserved_mem_lookup获取区域,然后映射内存,使用指针访问。 对于NVRAM,假设硬件有EEPROM,驱动可以使用i2c_transfer或spi_write等函数进行读写。 最后,需要总结这些方法,并指出各自的适用场景,比如硬件支持的情况下优先使用NVRAM,否则使用保留内存或sysfs与用户空间配合。 同时,需要引用相关的Linux内核文档或资料,比如设备树绑定文档,或者内核中关于保留内存和sysfs的章节。 现在,检查是否有相关的引用可以支持这些方法。例如,引用[2]提到BootLoader设置环境,可能和保留内存有关,但不确定。引用[1]提到kobject的作用,可能和sysfs相关,因为kobject是sysfs的基础。引用[3]关于root模式,可能不相关。 可能需要调整引用,例如在讨论sysfs时,可以引用kobject的作用,因为它涉及sysfs的创建。</think>在 Linux 内核驱动中实现变量的持久化存储可通过以下方法实现: ### 一、使用 SysFS 与用户空间配合 通过 SysFS 将内核变量暴露给用户空间,借助脚本在系统关机时保存数据,启动时自动加载数据。此方法需用户空间配合,适用于软重启场景[^1]。 **实现步骤:** 1. **创建 SysFS 属性文件** 在驱动中定义 `show` 和 `store` 方法: ```c static ssize_t my_var_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%d\n", my_var); } static ssize_t my_var_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { sscanf(buf, "%d", &my_var); return count; } static struct kobj_attribute my_attr = __ATTR(my_var, 0664, my_var_show, my_var_store); ``` 2. **用户空间脚本** 创建 Systemd 服务或 init 脚本,在关机时保存数据文件,启动时读取: ```bash # 保存脚本 echo $(cat /sys/kernel/my_driver/my_var) > /var/lib/my_driver/data ``` ### 二、保留内存(Reserved Memory) 在内核启动参数中预留一块物理内存区域,驱动通过直接内存访问实现持久化。此方法依赖系统启动配置,需避免内存被覆盖。 **实现步骤:** 1. **设备树配置** 添加保留内存区域定义: ```dts reserved-memory { #address-cells = <1>; #size-cells = <1>; ranges; my_region: region@0x10000000 { reg = <0x10000000 0x1000>; // 起始地址和大小 no-map; }; }; ``` 2. **驱动中映射内存** 使用 `ioremap` 或 `memremap` 访问保留内存: ```c void __iomem *mem_region; mem_region = memremap(0x10000000, 0x1000, MEMREMAP_WB); int *persistent_var = (int *)mem_region; ``` ### 三、硬件非易失存储(NVRAM/EEPROM) 直接读写硬件提供的非易失存储器(如 SPI Flash、EEPROM),需硬件支持并实现对应驱动接口。 **实现示例(SPI EEPROM):** ```c struct spi_device *eeprom; u8 buf[4]; // 写入数据 buf[0] = (value >> 8) & 0xFF; buf[1] = value & 0xFF; spi_write(eeprom, buf, 2); // 读取数据 spi_read(eeprom, buf, 2); value = (buf[0] << 8) | buf[1]; ``` ### 四、内核文件操作(高风险,谨慎使用) 在内核中直接操作文件系统存储数据,需确保文件系统已挂载且避免竞争条件。 **示例代码:** ```c struct file *fp; loff_t pos = 0; char data[] = "123"; // 写入文件 fp = filp_open("/persistent/data", O_WRONLY|O_CREAT, 0644); kernel_write(fp, data, sizeof(data), &pos); filp_close(fp, NULL); // 读取文件 fp = filp_open("/persistent/data", O_RDONLY, 0); kernel_read(fp, data, sizeof(data), &pos); filp_close(fp, NULL); ``` ### 对比与建议 | 方法 | 可靠性 | 硬件依赖 | 复杂度 | 适用场景 | |------------------|--------|----------|--------|------------------------| | SysFS+用户脚本 | 中 | 无 | 低 | 软重启,需用户空间协作 | | 保留内存 | 高 | 需配置 | 中 | 内存保留的系统 | | 硬件非易失存储 | 最高 | 需硬件 | 高 | 有专用存储器的设备 | | 内核文件操作 | 低 | 无 | 高 | 不推荐,仅作临时方案 | **推荐方案:** 1. 优先使用 **硬件非易失存储**(如 EEPROM),可靠性最高。 2. 若无硬件支持,可配置 **保留内存**,需在 BootLoader 中确保内存不被清除[^2]。 3. 临时方案可用 **SysFS+脚本**,但需处理意外断电导致的数据丢失。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值