2020-03-27
关键字:
在 Linux 内核开发,通常是嵌入式领域的内核开发过程中,难免会有需要访问文件系统中的文件的需求。
但 Linux 内核中可没有像在用户态那样有文件IO和标准IO可以直接对文件进行 open()/fopen() , read()/fread() , write()/fwrite() , close()/fclose() 操作。
不过所幸,在 ./kernel/include/linux/fs.h 中提供了有相对应的函数供我们对文件系统中的普通文件进行IO操作。
这些函数为:
1、filp_open()
2、filp_close()
3、vfs_read()
4、vfs_write()
可以将这套函数理解成是在内核态的“文件IO”接口。
1、filp_open()函数
函数原型如下:
struct file *filp_open(const char *, int, umode_t);
参数1是要打开的文件的路径。直接填文件系统中的路径就行了,最好填绝对路径。
参数2是文件的读写模式。常用的值有 O_RDONLY , O_WDONLY , O_RDWR , O_CREAT。这个参数的值与文件IO中的一样,它们被定义在 ./kernel/include/uapi/asm-generic/fcntl.h 中。
参数3则是文件的权限了,即 0666 , 0755 形式的八进制数值。如果是只读模式,直接填 0 即可。
返回值是指向所打开文件的结构体指针。这个结构体被定义在 ./kernel/include/linux/fs.h 中。
2、filp_close()函数
函数原型如下:
int filp_close(struct file *, fl_owner_t id);
参数1就是filp_open()函数的返回值。
参数2一般填0即可。
返回值表示这个文件的关闭结果,值0表示成功关闭。
3、vfs_read()函数
函数原型如下:
ssize_t vfs_read(struct file *, char __user *, size_t, loff_t *);
参数1是filp_open()函数的返回值。
参数2是用来存放所读取的内容的数组。这里需要注意默认情况下这个参数是要用在用户态下申请的字符数组的。如果非要用在内核态下申请的字符数组,则要进行另一番操作,这个操作在记录在下面。
参数3表示期望读取的最大大小。
参数4表示读取位置,即用来记录本次读取时所读过的数据长度的。可以将它理解成一个定位,一个标尺,是为了能在下一次读取时接在上一次读取的末尾而设定的。
返回值就是实际读取到的数据大小。
如果参数2要直接使用在内核态申请的字符数组,则要在调用这个函数之前先执行一下以下代码:
mm_segment_t old_fs; old_fs = get_fs(); set_fs(KERNEL_DS);
并在读取完毕后再执行一下以下代码:
set_fs(old_fs);
如果不这样做又直接给参数2传递在内核态申请的空间的话,vfs_read() 函数会直接返回一个 -14 的错误码回来。这个错误码被定义在 ./kernel/include/uapi/asm-generic/errno-base.h 中。
4、vfs_write()函数
函数原型如下:
ssize_t vfs_write(struct file *, const char __user *, size_t, loff_t *);
这些参数与返回值与 vfs_read() 是如出一辙的了,就不再赘述了。
在内核态读取普通文件系统的实例:
以下贴出一个被编译成 ko 形式的内核驱动程序读取文件系统中的文件的演示代码。
需要强调的是,这份代码是以 ko 形式,在 Android 系统开发板稳定运行以后 insmod 到系统中运行的。笔者并没有尝试过将这份驱动程序直接打包到内核镜像中随系统启动而运行的模式下是否能正常运行。
#include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/delay.h> #include <linux/string.h> #include <linux/uaccess.h> #include <linux/slab.h> static struct file *fp; static struct file *wfp; static int __init init() { printk("%s()\n", __FUNCTION__); #define FN "/sdcard/wanna" /* filp_open()是一个异步执行函数。它会异步打开指定的文件。 如果打开了文件后面没干其它事直接就结束这个函数,那么就很有可能出现后面的打印不显示的现象。 */ fp = filp_open("/sdcard/wanna", O_RDONLY, 0); //参数3的文件模式对于读文件而言作用不大。 printk("fs file address:0x%p\n", fp); msleep(100); if(IS_ERR(fp))//要用这个 IS_ERR() 来检查指针是否有效,而不能直接判断指针是否为NULL。 { printk("cannot open fs.\n"); goto FS_END; } char *out_file_name; const int of_len = strlen(FN) + 5; out_file_name = kmalloc(of_len, GFP_KERNEL); if(out_file_name == NULL) { printk("cannot malloc.\n"); goto FS_END; } memset(out_file_name, 0, of_len); snprintf(out_file_name, of_len, "%s%s", FN, "_out"); printk("out_file_name:%s\n", out_file_name); wfp = filp_open(out_file_name, O_WRONLY|O_CREAT, 0666); msleep(100); if(IS_ERR(wfp)) { printk("cannot open the write file.\n"); wfp = NULL; } mm_segment_t old_fs; old_fs = get_fs(); set_fs(KERNEL_DS); int size = 0; char rbuf[6]; loff_t pos = 0; loff_t wpos = 0; while(1) { memset(rbuf, 0, 6); /* 参数2要求是 __user 空间的内存地址, 如果要直接使用内核中创建的数组, 则在使用之前应先将文件系统状态切换到 KERNEL_DS 态。即上面的 set_fs(KERNEL_DS) 调用。 参数3是读取指针的位置,对于普通文件而言,它必须传入一个有效的 loff_t 指针以实现“断点续读”的功能。 */ size = vfs_read(fp, rbuf, 3, &pos); printk("read ret:%d, pos:%ld\n", size, pos); if(size < 1) { printk("read end.\n"); break; } printk("\t%s\n", rbuf); if(wfp) { //将前面读出来的文件内容复制到另外一个文件中去。 size = vfs_write(wfp, rbuf, size, &wpos); printk("write ret:%d, pos:%ld\n", size, wpos); } } set_fs(old_fs); msleep(50); FS_END: return 0; } static void __exit exit() { if(!IS_ERR(fp)) { printk("closing fs file.\n"); int ret = filp_close(fp, NULL); printk("close ret:%d\n", ret); } if(wfp && !IS_ERR(wfp)) { printk("closing wfp.\n"); int ret = filp_close(wfp, 0); printk("close wfp ret:%d\n", ret); } msleep(100); } module_init(init); module_exit(exit); MODULE_LICENSE("GPL");
顺便贴一下 Makefile:
obj-m += mymodule.o KDIR := /home/chorm/workspace/my_android_src/kernel PWD ?= $(shell pwd) all: make -C $(KDIR) M=$(PWD) modules clean: