Chinaunix首页 | 论坛 | 博客
  • 博客访问: 195070
  • 博文数量: 55
  • 博客积分: 2330
  • 博客等级: 大尉
  • 技术积分: 504
  • 用 户 组: 普通用户
  • 注册时间: 2010-07-20 22:55
文章分类

全部博文(55)

文章存档

2013年(6)

2012年(7)

2011年(7)

2010年(35)

我的朋友

分类: 服务器与存储

2010-08-16 22:07:14

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中两个挂载选项的分析,如有问题,烦请指出。


原文链接:http://blog.chinaunix.net/u2/87570/showart_2165920.html

阅读(1103) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~