2. fuse内核队列

实现内存零拷贝 。在默认情况下,fuse 必须通过read()从/dev/fuse读取请求,通过write()将请求回复写入/dev/fuse 。每次读写系统调用都需要进行一次内核-用户空间的内存拷贝 。这样对读写的性能损耗十分严重,因为一次内存拷贝需要处理大量数据 。为了缓解这个问题,fuse支持了Linux内核提供的功能 。允许用户空间在两个内核内存缓冲区之间传输数据,而无需将数据复制给用户空间 。如果fuse 实现了()方法,则 FUSE 从/dev/fuse读取数据,并以包含文件描述符的缓冲区的形式将数据直接传递给此方法处理,从而省去了一次内存申请与拷贝 。
多线程模式 。在多线程模式下,fuse 以一个线程开始,如果内核队列中有两个以上的,则会自动生成其他线程 。默认最大支持10个线程同时处理请求 。
2. fuse内核队列
图片摘自《To FUSE or Not to FUSE:of User-Space File 》
fuse在内核中维护了五个队列,分别为:、、、、 。一个请求在任何时候只会存在于一个队列中 。
a) : 队列用于暂存异步请求 。在默认情况下,只有读请求进入队列;当 cache启用时,写请求也会进入队列 。当开启 cache时,来自用户进程的写请求会先在页缓存中累积,然后当 线程被唤醒时会下刷脏页 。在下刷脏页时,FUSE会构造异步请求,并将它们放入队列中 。
【2. fuse内核队列】b) :同步请求(例如,元数据)放在队列中,并且队列会周期性接收来自 的请求 。但是队列中异步请求的个数最大为(最大为12),当队列的异步请求未达到12时,队列的请求将被移动到队列中 。这样做的目的是为了控制队列中异步请求的个数,防止在突发大量异步请求的情况下,阻塞了同步请求 。
c) :当队列中的请求被转发到fuse 的同时,也被移动到队列 。所以队列中的请求,表示正在被处理fuse 处理的请求 。当fuse 真正处理完请求,通过/dev/fuse下发reply时,该请求将从队列中删除 。
d) :用于存放中断请求,比如当发送的请求被用户取消时,内核会发送一个请求,来取消已被发送的请求 。中断请求的优先级最高,中的请求会最先得到处理 。
e) :请求用于删除中缓存的inode 。
3. /dev/fuse 读写调用流程

2. fuse内核队列

文章插图
Fuse 加载过程中注册了对/dev/fuse的操作接口 。/分别对应fuse 从内核读取请求,以及处理完请求后写回reply的函数调用 。我们分别看下具体的代码片段
当 、、队列都没有请求时,读进程进入休眠 。一旦有请求到达,这个等待队列上的进程将被唤醒 。和 的请求优先级高于队列 。当请求的数据内容被拷贝至用户空间后,该请求会被移至队列,并且req->flags会保存当前请求的状态 。
当fuse 处理完请求后,会将结果写回到/dev/fuse 。写数据保存在 中,并且会根据 id在fc()中找到对应的req,并将写回的参数从拷贝至req->out 。
最后我们以为例,看下fuse整体是如何工作的: