- 随着内核文件系统的复杂性增长,导致用户态文件系统越来越流行,相比于内核态文件系统,用户态文件系统更容易开发。但是性能如何? 用户态文件系统给人直觉上觉得性能肯定是比较糟糕的,但是却很少有人量化进行分析。FUSE性能到底如何,适合哪种workload。
- FUSE每次请求都会携带一个node ID,是一个64位的整型,用于标识内核空间和用户空间的inode。
- Fuse架构
- INIT消息: mount的时候,FUSE会发送INIT消息给用户态daemon,进行协议的协商。支持的能力等,例如是否支持FLOCK、READDIRPLUS等,各种参数的设置,read-ahead size、time granularity
- **DESTROY消息:**在文件系统umount的时候会发送给用户态daemon进程,daemon进程应该最好资源的清理。
- **INTERRUPT消息:**取消之前发出的请求,会给定一个sequence号,FUSE每次发出的请求都会有sequence号,通过指定sequence号来识别要取消的请求。
- BATCH_FORGET/FORGET消息: 删除某个inode,但是有可能这个Inode还被open、create、opendir等操作持有其,因此这里需要通过引用计数来维护inode的生命周期。FORGET只是减少引用计数,只有当计数为0才真正删除inode
- FULSH消息: 每次close掉文件的时候会执行
- OPEN/RELEASE: 打开文件的时候/没有人引用文件fd的时候会调用RELEASE
- OPENDIR/RELEASEDIR: dittio
- READDIRPLUS: 和READDIR一样,但是额外会把entry的元信息也返回,是一种优化手段
- ACCESS: 打开文件后,Fuse会发送ACCESS消息,进行权限检查,因此用户可以自己实现access来实现自定义的权限检查,如果mount FUSE的时候使用default_permissions,那么内核将基于标准的unix属性来做权限检查,不会额外发送ACCESS请求了。
- Fuse内部结构
FUSE内核维护了五个队列、interupts、forgets、pending、processing、background等,一个请求在任何时刻只能属于其中一个队列。forget请求在forgets队列,同步请求(例如metadata)在pending队列,当User Daemon读取/dev/fuse得到时候,请求会从队列传输给User Daemon进程。这些队列之间存在优先级,Interrupts队列优先级最高、其次就是FORGET和NON-FORGET请求,当请求从Pending队列中传输给User Daemon进程,这个请求就会被移动到Processing队列中。这个队列中放的请求是正在被User Daemon进程处理的请求。如果Pending队列为空,那么User Daemon进程读取**/dev/fuse**就会阻塞。当User Daemon进程回复了请求,那么对应的请求就会从Processing队列中移除。Background队列是用来处理异步请求的。 一般情况下只有read请求会放到background队列中,write请求只有在开启writeback cache开启的时候才会放到这个队列中。在这种情况下,write请求会先写到page cache中,最后由内核线程bdflush来flush脏页。这个时候会由FUSE生成一个一异步请求,然后将其放入到background queue中。请求会逐渐从background队列移动到pending队列中。Fuse限制background队列中的最大可以驻留在pending队列的数量。(max_background,默认是12),当少于12个异步请求在pending队列时,background中的请求会被移动到pending队列中,目的就是为了避免突发的background请求导致重要的同步请求被delay。 此外在max_background达到75%这个阀值(congestion_threshold控制),FUSE会通过LInux VFS拥挤,Linux VFS会对用户进程的写入进行节流。