1. 插曲
阅读Linux内核源码,可以知道read 和 write 这两个系统调用陷入内核实际执行的是 sys_read 和 sys_write 这两个函数,但是这两个函数没有使用 EXPORT_SYMBOL 导出,也就是说其他模块不能使用。
read系统调用的调用号定义:
//includeuapiasm-genericunistd.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() 来解决。有如下定义:
//includeasm-genericuaccess.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) "sfl: " 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! "; static char read_buf[100]; 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 ", __func__); fp = filp_open(FILE_PATH, O_RDWR | O_CREAT, 0644); if (IS_ERR(fp)){ pr_info("open file error "); 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 ", read_buf); filp_close(fp,NULL); set_fs(fs); return 0; } void __exit kernel_file_op_exit(void) { pr_info("kernel file operation module exit. "); } 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] sfl: enter kernel_file_op_init [390465.664072] sfl: 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] sfl: kernel file operation module exit. root@ubuntu:/work/7.kernel_file_op#