用户空间文件系统(FUSE)( 四 )


low_level接口的使用主要有三个部分:

  • fuse_mount:挂载文件系统 , 创建和内核通信的通道;
首先会用读写的方式打开杂项字符设备`/dev/null` , 将返回的文件描述符作为作为参数传递给mount系统调用 , 进入内模块后会执行上文3.3节部分析的流程 。 如果是普通用户进行挂载 , 就会出现权限问题 , 此时会重新使用挂载工具fusermount进行重试 , 区别于mount系统调用的挂载的方式 , fusermount是一个二进制文件 , 所以libfuse会fork创建一个子进程来执行程序 , 挂载成功后使用管道将杂项字符设备的文件描述符进行返回;
  • fuse_lowlevel_new:注册文件系统回调 , 创建用户空间session结构;
这个步骤会创建文件系统接口管理的结构fuse_ll , 它会保存用户空间实现的回调方法 , 并保存文件系统的额外的配置信息 , 接下来会创建session , 并注册session的请求接收方法fuse_ll_receive_buf和请求处理方法fuse_ll_receive_buf , 请求被接收后 , 由处理方法再分发到用户空间实现的各个回调方法中去;
  • fuse_session_loop或fuse_session_loop_mt:轮询内核模块请求 , 处理后进行回复;
通过以上两个步骤 , 文件系统已挂载完毕 , 文件系统回调也初始化完成 , 剩下的需要做的就是不断的从内核中读取请求进行处理并返回处理结果 , libfuse使用fuse_session_loop来完成这部分工作 , 那fuse_session_loop_mt呢?mt就是multi-thread的缩写 , 多线程处理情况下 , 每当收到一个请求 , 如果没有监听杂项设备的线程 , 则创建就一个新线程去重新进行监听 , 否则不创建新线程 , 这样就可以进行请求的并发处理 , 提高处理速度 , 默认最多能够开启10个线程监听线程 , 请求处理完成的线程在检查监听线程数量存在多于后会自动退出;
5
挂载工具为了能够让普通用户进行文件系统的挂载 , libfuse实现了相应的工具fusermount , 同时它也能对文件系统进行卸载 。 内部实现上还是使用了mount系统调用 , 只是fusermount加入了SUID权限 , 在进入系统调用之前进行了权限提升 , 从而绕过了安全检查机制;因此 , 在部分版本的libfuse库中会存在安全漏洞 , 使用时需要注意避免;
6
简易文件系统实践libfuse开源库在example目录中自带了几个用户空间文件系统的实例 , 这里选用hello_ll进行实践 , 它实现了lookup、getattr、readdir、open和read五个接口:
  • 下载源码 , 切换到到2_9
$ git clone $ cd libfuse/$ git checkout fuse_2_9_2
  • 编译libfuse
$ ./makeconf.sh$ ./configure$ make$ ll example/hello_ll-rwxrwxr-x 1 test test 7726 Oct 20 09:04 example/hello_ll
  • 挂载文件系统 , 首先创建/tmp/fuse作为挂载点
$ ./example/hello_ll -o fsname=hello,subtype=ll /tmp/fuse/$ cat /etc/mtab | grep hellohello /tmp/fuse fuse.ll rw,nosuid,nodev,relatime,user_id=45878,group_id=45878 0 0$ ll /tmp/fuse/total 0-r--r--r-- 1 root root 13 Jan11970 hello$ stat /tmp/fuse/helloFile: ‘/tmp/fuse/hello’Size: 13Blocks: 0IO Block: 4096regular fileDevice: 2eh/46dInode: 2Links: 1Access: (0444/-r--r--r--)Uid: (0/root)Gid: (0/root)Access: 1970-01-01 08:00:00.000000000 +0800Modify: 1970-01-01 08:00:00.000000000 +0800Change: 1970-01-01 08:00:00.000000000 +0800 Birth: -$ cat /tmp/fuse/helloHello World!7
FUSE优化
  • 增加单个请求的最大值
FUSE内核模块中 , 定义了IO操作的最大值 , 默认为FUSE_MAX_PAGES_PER_REQ(32)页 , 大于该值的IO都会切分为多个 , 对于带宽有需求的应用可以将该值调整到更大的值 , 修改后重新编译FUSE内核模块即可;
# fs/fuse/fuse_i.h/** Max number of pages that can be used in a single read request */#define FUSE_MAX_PAGES_PER_REQ 32
  • libfuse增大单个IO的最大值(max_write)
libufuse在响应初始化请求时会设置max_write属性 , 默认值为132K , 对于带宽有需求的应用需要结合第一个优化 , 增大用户空间缓存;修改后重新编译libfuse即可;
# lib/fuse_kern_chan.c #define MIN_BUFSIZE 0x21000
  • 增大文件系统预读
可以修改内核的默认预读值 , 调整文件include/linux/mm.h中的宏定义VM_MAX_READAHEAD进行修改;如果不想重新编译内核 , 也可以在sysfs中相应的bdi设备下修改文件系统的默认预读大小;
  • 增加内核缓存的过期时间
对于创建文件或则查询文件等操作 , 可以在文件系统实现中增大返回参数entry_timeout和attr_timeout的值 , 这会增加对应文件在内核缓存中的过期时间 , 从而减少元数据的交互次数;