分类: 服务器与存储
2010-08-16 22:07:14
对fuse提供的两个选项direct_io及big_writes困惑已久,以前对内核完全不了解,看不懂fuse内核模块的代码,这两天把fuse的代码重新过了一遍,把整个机制弄清楚了,很多细节方面的东西还在学习中。 指定direct_io挂载文件系统时,系统调用到了fuse层后,会跳过页高速缓存,当指定了direct_io后,读写系统调用会使用fuse_direct_io_file_operations的读写方法。 #fuse/kernel/file.c static const struct file_operations fuse_direct_io_file_operations = { …… .read = fuse_direct_read, .write = fuse_direct_write …… }; fuse_direct_read/fuse_direct_write都是通过fuse_direct_io实现的 static ssize_t fuse_direct_io(struct file *file, const char __user *buf, size_t count, loff_t *ppos, int write) { struct inode *inode = file->f_path.dentry->d_inode; struct fuse_conn *fc = get_fuse_conn(inode); //使用direct_io每次io量为max_write(max_read) size_t nmax = write ? fc->max_write : fc->max_read; …… while (count) { size_t nres; //请求大小与最大io量对比,取小者 size_t nbytes = min(count, nmax); //如果是写将则将用户空间的数据拷贝进来,作为fuse_req中in字段的值; //如果是读,则将用户空间的地址作为fuse_req中out的值 int err = fuse_get_user_pages(req, buf, &nbytes, write); //根据请求类型将请求发送到/dev/fuse,等待其处理 if (write) nres = fuse_send_write(req, file, inode, pos, nbytes,current->files); else nres = fuse_send_read(req, file, inode, pos, nbytes, current->files); …… } 从上面的代码可以看出,只要fuse每次处理读写请求的大小是受到max_read,max_write参数(挂载文件系统时可以指定)限制的,对于vfs传递下来的请求,读请求只要不超过max_read,则只需要一次fuse的read,写请求只要不超过max_write,则只需要一次fuse的write即可。 另外read,write的处理受限于几个宏定义的值: fuse/kernel/fuse_i.h fuse/lib/fuse_kern_chan.c #define MIN_BUFSIZE 0x21000 //用户态与fuse/dev之间channal的缓冲区大小 从这两个值可以看出,fuse处理请求时能使用的最大空间为128K 在指定direct_io的情况下,使用cp拷贝文件,为什么write只有4k,而read能达到128k? 经多次测试,cp使用的缓冲区应该是4096,所以cp中的read(write)系统调用每次传递个vfs的请求大小为4K,而vfs接受到请求后,对于写请求直接发到fuse,fuse处理写请求;对于读请求,由于linux系统的预读机制,如果是顺序读,预读的最大值能达到128k,这个值在inlcude/linux/mm.h中定义了,即#define VM_MAX_READAHEAD 128(单位是KB)。因此对于cp小的缓冲区,fuse的读请求能达到128k,而write只能是4k。 故使用dd等能指定每次请求大小的工具,并加上direct_io参数,能使达到fuse的读写请求达到128k,要想这个值更高,必须对fuse以及系统内核进行修改,主要就是上面提到的几个限制。 如果想io请求达到1M,可做如下修改。 #define FUSE_MAX_PAGES_PER_REQ 256 #define MIN_BUFSIZE 0x101000 //两者改针对fuse,需要重新编译fuse模块。 #define VM_MAX_READAHEAD 1024 //针对系统内核,将预读的最大值设为1M,需重新编译内核 按照我的理解,使用direct_io选项,并且在用户态指定了请求大小时,只需要改fuse的两个值就行,为什么还需要修改内核的VM_MAX_READAHEAD才能使请求提高到1M呢? big_writes是怎么回事? big_writes选项是到fuse-2.8才加的,需要相应fuse内核模块的支持(据fuse的作者说linux-2.6.26以上内核中的fuse才支持。 在以前的fuse内核模块中,对于使用页高速缓存的读写,没有实现writepages方法,即一次只能写一个页,所以在以前中的版本中,不适用direct_io的情况下,写请求最大为4k,我看的是2.6.30内核中的fuse内核代码。 fuse.h中增加FUSE_BIG_WRITES的声明,fuse_i.h里fuse_conn中增加了big_writes字段,在fuse_fill_write_pages中有如下变化。 #in file.c static ssize_t fuse_fill_write_pages(struct fuse_req *req, struct address_space *mapping, struct iov_iter *ii, loff_t pos) { …… do { err = 0; req->pages[req->num_pages] = page; req->num_pages++; //如果没有指定big_writes标志,则只fill一页就就跳出循环; 而指定该标志后,该值会提高到max_write,FUSE_MAX_PAGES_REQ中限定的较小值。 if (!fc->big_writes) break; } while (iov_iter_count(ii) && count < fc->max_write && req->num_pages < FUSE_MAX_PAGES_PER_REQ && offset == 0); return count > 0 ? count : err; } 同样在指定big_writes的情况下,修改上面提到的三个地方,也可以使请求的值提高到更大,两者的区别在于big_writes使得VFS维护了所读(写)文件的页高速缓存,之后的读写效率会很高,而direct_io每次绕过页高速缓存。 以上是对fuse中两个挂载选项的分析,如有问题,烦请指出。 原文链接:http://blog.chinaunix.net/u2/87570/showart_2165920.html
#define FUSE_MAX_PAGES_PER_REQ 32 //fuse每个请求能使用的最大页数