内核中读写文件

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);

 

posted on 2021-04-13 23:51  Hello-World3  阅读(1397)  评论(0编辑  收藏  举报

导航