Skip to main content
 首页 » 操作系统

Linux内核中读写文件

2022年07月19日50birdshome

1. 插曲

阅读Linux内核源码,可以知道read 和 write 这两个系统调用陷入内核实际执行的是 sys_read 和 sys_write 这两个函数,但是这两个函数没有使用 EXPORT_SYMBOL 导出,也就是说其他模块不能使用。

read系统调用的调用号定义:

//include\uapi\asm-generic\unistd.h 
#define __NR_read 63 
__SYSCALL(__NR_read, sys_read) /*[63] = sys_read*/

read系统调用的实现定义:

//fs/read_write.c 
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count) /*实现sys_read*/ 
{ 
    struct fd f = fdget_pos(fd); 
    ssize_t ret = -EBADF; 
 
    if (f.file) { 
        loff_t pos = file_pos_read(f.file); 
        ret = vfs_read(f.file, buf, count, &pos); 
        if (ret >= 0) 
            file_pos_write(f.file, pos); 
        fdput_pos(f); 
    } 
    return ret; 
}

2. 内核空间与用户空间地址区别

在 vfs_read 和 vfs_write 函数中,其参数 buf 指向的用户空间的内存地址,如果我们直接使用内核空间的指针,则会返回-EFALUT。这是因为使用的缓冲区超过了用户空间的地址范围。一般系统调用会要求你使用的缓冲区不能在内核区。这个可以用 set_fs()、get_fs() 来解决。有如下定义:

//include\asm-generic\uaccess.h 
#define MAKE_MM_SEG(s) ((mm_segment_t) { (s) }) 
#define KERNEL_DS    MAKE_MM_SEG(~0UL) 
#define USER_DS MAKE_MM_SEG(PAGE_OFFSET) 
#define get_ds() (KERNEL_DS)  /*获取地址空间最大范围值*/ 
#define get_fs() (current_thread_info()->addr_limit) /*获取当前进程能访问的地址范围限制*/ 
#define set_fs(fs) (current_thread_info()->addr_limit = fs)  /*设置当前进程能访问的地址范围限制*/

内核中的使用流程如下:

mm_segment_t fs = get_fs(); /*保存当前进程的地址限制,以便事后恢复*/ 
set_fs(KERNEL_DS); /*设置所有地址空间都能访问*/ 
 
vfs_write(); 
... 
vfs_read(); 
 
set_fs(fs); /*恢复进程原地址设置值*/

3. 示例程序

/* file name: kernel_file_operation_test.c */ 
 
#define pr_fmt(fmt) "kfop: " fmt 
 
#include <linux/module.h> 
#include <linux/init.h> 
#include <linux/fs.h> 
#include <linux/uaccess.h> 
 
#define FILE_PATH "/work/7.kernel_file_op/hello.bin" 
 
static char write_buf[100] ="Read The Fucking Source Code!\n"; 
static char read_buf[100]; 
 
static int __init kernel_file_op_init(void) 
{ 
    struct file *fp; 
    mm_segment_t fs; /*typedef unsigned long mm_segment_t;*/ 
    loff_t pos; 
 
    pr_info("enter %s\n", __func__); 
    fp = filp_open(FILE_PATH, O_RDWR | O_CREAT, 0644); 
    if (IS_ERR(fp)){ 
        pr_info("open file error\n"); 
        return -1; 
    } 
 
    fs = get_fs(); 
    set_fs(KERNEL_DS); 
 
    pos =0; 
    vfs_write(fp, write_buf, sizeof(write_buf), &pos); 
    pos =0; 
    vfs_read(fp, read_buf, sizeof(read_buf), &pos); 
 
    pr_info("read: %s\n", read_buf); 
    filp_close(fp,NULL); 
 
    set_fs(fs); 
 
    return 0; 
} 
 
static void __exit kernel_file_op_exit(void) 
{ 
    pr_info("kernel file operation module exit.\n"); 
} 
  
module_init(kernel_file_op_init); 
module_exit(kernel_file_op_exit); 
  
MODULE_LICENSE("GPL");
# file name: Makefile 
 
obj-m += kernel_file_operation_test.o 
 
all: 
    make -C /lib/modules/`uname -r`/build M=$(PWD) 
    rm -rf *.o *.o.cmd *.ko.cmd *.order *.symvers *.mod.c .tmp_versions 
 
clean: 
    rm -rf *.ko

测试结果:

root@ubuntu:/work/7.kernel_file_op# ls 
hello.bin  kernel_file_operation_test.c  kernel_file_operation_test.ko  Makefile 
root@ubuntu:/work/7.kernel_file_op# insmod kernel_file_operation_test.ko  
root@ubuntu:/work/7.kernel_file_op# dmesg -c 
[390465.664047] kfop: enter kernel_file_op_init 
[390465.664072] kfop: read: Read The Fucking Source Code! 
[390465.664072]  
root@ubuntu:/work/7.kernel_file_op# cat hello.bin  
Read The Fucking Source Code! 
root@ubuntu:/work/7.kernel_file_op# rmmod kernel_file_operation_test  
root@ubuntu:/work/7.kernel_file_op# dmesg -c 
[390615.379792] kfop: kernel file operation module exit. 
root@ubuntu:/work/7.kernel_file_op#

4. 新版本内核(4.19以后)已经定义了kernel_write函数

ssize_t kernel_write(struct file *file, const void *buf, size_t count, loff_t *pos) //fs/read_write.c 
{ 
    mm_segment_t old_fs; 
    ssize_t res; 
 
    old_fs = get_fs(); 
    set_fs(get_ds()); 
    /* The cast to a user pointer is valid due to the set_fs() */ 
    res = vfs_write(file, (__force const char __user *)buf, count, pos); 
    set_fs(old_fs); 
 
    return res; 
} 
EXPORT_SYMBOL(kernel_write);

本文参考链接:https://www.cnblogs.com/hellokitty2/p/14655970.html