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