FUSE的big_writes与direct_io选项分析

fuse提供的两个选项direct_iobig_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_reqin字段的值;如果是读,则将用户空间的地址作为fuse_reqout的值

              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_readmax_write参数(挂载文件系统时可以指定)限制的,对于vfs传递下来的请求,读请求只要不超过max_read,则只需要一次fuseread,写请求只要不超过max_write,则只需要一次fusewrite即可。

另外readwrite的处理受限于几个宏定义的值:

fuse/kernel/fuse_i.h
#define FUSE_MAX_PAGES_PER_REQ 32 //fuse
每个请求能使用的最大页数

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中的readwrite)系统调用每次传递个vfs的请求大小为4K,而vfs接受到请求后,对于写请求直接发到fusefuse处理写请求;对于读请求,由于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.hfuse_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中两个挂载选项的分析,如有问题,烦请指出。

 

posted @ 2013-04-19 14:05  ydzhang  阅读(2056)  评论(0编辑  收藏  举报