内核态下实现direct IO

来源:互联网 发布:淘宝气味博物馆 编辑:程序博客网 时间:2024/05/16 10:50

       在用户态打开文件时,指定O_DIRECT便可以轻松实现direct IO,然而在内核态打开文件时指定O_DIRECT却出现错误,发现无论是对文件执行读操作还是写操作都会失败。那么,如何在内核态下绕过内核cache,实施direct IO呢?     

       首先分析为什么在内核态下使用O_DIRECT会出现读写操作都失败的原因。通过跟踪源代码发现,如果指定O_DIRECT,那么vfs_write或者vfs_read最终会调用do_direct_IO,该函数在dio_get_page过程中通过get_user_pages_fast来获取用户态传递进来的地址对应的page。但是此时内核态传进来的为内核空间地址,导致get_user_pages_fast失败,最终导致vfs_read或者vfs_write的失败。分析了错误的原因,下面就来谈谈解决的方案:

1.      不用O_DIRECT,直接echo 3 > /proc/sys/vm/drop_caches这个命令来清空缓存;

2.      自己写函数替换get_user_pages_fash;

3.      在用户态空间申请缓存供内核使用,这样就不用担心get_user_pages_fast这个函数报错了。      

以上三种方法,第一种不能算是解决方案的解决方案,也不符合通过代码实现direct IO的要求;第二种方法本人能力有限,还无法实现;相比之下第三种方法比较简单,我就给大家介绍以下第三种方法。第三种方法的关键是申请用户空间的缓冲区,这里我们通过do_mmap_pgoff这个函数来实现。

do_mmap_pgoff在include/linux/mm.h头文件里被定义,它的实现在mm/mmap.c文件里。这个函数的主要作用就是实现内存的映射。 


在这里我给大家简单地介绍一下各个参数的作用,具体的代码实现大家可以去看源码。

1.      structfile *file 表示要映射的文件,也就是filp_open的返回值,如果不需要映射文件,只是纯粹的申请一个缓冲区,可以设为NULL;

2.      unsignedlong addr 虚拟空间中的一个地址,表示从这个地址开始查找一个空闲的虚拟区;一般设为0,即从0x00000000开始查找,成功的话就将这个起始地址作为返回值返回;

3.      unsignedlong len 要映射的文件部分的长度(即缓冲区的长度);

4.      unsignedlong prot 这个参数指定对这个虚拟区所包含页的存取权限。可能的标志有PROT_READ、PROT_WRITE、PROT_EXEC和PROT_NONE。前三个分别表示读、写和执行,PROT_EXEC表示进程没有以上三个存取权限中的任意一个;

5.      unsignedlong pgoff 文件内的偏移量,因为我们并不是一下子全部映射一个文件,可能只是映射文件的一部分,pgoff就表示那部分的起始位置,如果没有映射文件,一般设为0;

6.      unsignedlong *populate 这个参数我也没搞懂;

7.      unsignedlong flags 这个参数指定虚拟区的其它标志:

1)     MAP_GROWSDOWN 用于stack;

2)     MAP_LOCKED 同时进行mlock;

3)     MAP_DENYWRITE和MAP_EXECUTABLE 不再使用了;

4)     MAP_SHARED和 MAP_PRIVATE 前一个标志指定虚拟区中的页可以被许多进程共享;后一个标志作用相反。这两个标志都涉及vm_area_struct中的VM_SHARED标志;

5)     MAP_ANONYMOUS 表示这个虚拟区是匿名的,与任何文件无关;

6)     MAP_FIXED 这个区间的起始地址必须是由参数addr所指定的;

7)     MAP_NORESERVE 函数不必预先检查空闲页面的数目

源代码如下:

#include <linux/init.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/mm.h>#include <linux/fs.h>#include <linux/time.h>#include <asm/uaccess.h>#include <asm/mman.h>#define LEN 1024*64#define COUNT 1024*1024/32#define DIR_OUT "/home/wpp/Desktop/wpp.txt"extern unsigned long do_mmap_pgoff(struct file *file, unsigned long addr, unsigned long len, unsigned long prot, unsigned long flags, unsigned long pgoff, unsigned long *populate);unsigned long run_time(struct timeval end, struct timeval start){unsigned long sec, usec;if(end.tv_usec >= start.tv_usec){sec = end.tv_sec - start.tv_sec;usec = end.tv_usec - start.tv_usec;}else{sec = end.tv_sec - start.tv_sec - 1;usec = end.tv_usec + 1000000 - start.tv_usec;}printk("the run time is %lus%luus\n", sec, usec);return sec*1000000 + usec;}static int __init temp_init_module(void){struct file *fp;char *buf;unsigned long i, populate, rate, time;struct timeval start, end;mm_segment_t old_fs;loff_t pos;fp = filp_open(DIR_OUT, O_RDWR | O_CREAT | O_DIRECT, 0);if(IS_ERR(fp)){printk("open error\n");return -1;}buf = NULL;buf = (char *)do_mmap_pgoff(NULL, 0, LEN, PROT_READ | PROT_WRITE, MAP_SHARED, 0, &populate);if(NULL == buf){printk("do_mmap_pgoff error\n");return -1;}memset(buf, 'w', LEN-1);buf[LEN-1] = '\n';old_fs = get_fs();set_fs(KERNEL_DS);pos = 0;do_gettimeofday(&start);for(i=0; i<COUNT; i++)vfs_write(fp, (char *)buf, LEN, &pos);do_gettimeofday(&end);time = run_time(end, start);rate = (2*1024*1000000) / time;printk("the rate of write no cache is %luM/s\n", rate);set_fs(old_fs);filp_close(fp, NULL);return 0;}static void __exit temp_exit_module(void){printk("Goodbye\n");}module_init(temp_init_module);module_exit(temp_exit_module);MODULE_LICENSE("GPL");

Makefile:

obj-m := temp.oKDIR := /lib/modules/$(shell uname -r)/buildPWD  := $(shell pwd)defualt:$(MAKE) -C $(KDIR) M=$(PWD) modulesclear:rm -rf .tmp_versions *~ *.o *ko *.mod.c .*.cmd *.markers *.order *sysmvers *.symvers .*.swp

make的时候可能会出现下面的问题,这会导致模块添加不到内核中去:


为什么会出现这样的问题我也是不太清楚,上网查了一些资料,在大神的帮助下,终于解决了:



在mm/mmap.c文件中,do_mmap_pgoff的实现函数后面加上图中的语句,然后重新编译内核,进入新内核后再make就没有问题了。

参考文献:
http://bbs.chinaunix.net/thread-4156623-1-1.html
《深入分析Linux内核源代码》第六章


0 0
原创粉丝点击