当前位置:网站首页>fuse简介

fuse简介

2022-08-10 15:14:00 唏噗

fuse是用户态文件系统。在介绍fuse之前,先笼统地介绍一下文件系统。

文件系统

文件系统是操作系统用于明确存储设备或者分区上的文件的方法和数据结构。用户态文件系统是操作系统提供了一层文件存储的接口,方便用户对文件操作的接口。文件系统的主要功能,一是对文件的操作(打开、关闭、增删改查),二是文件数据的存储。
文件系统分为分布式文件系统和基于操作系统本身的文件系统。分布式文件系统由两部分组成:一是对文件本身做索引,二是包含一个网络服务器,对外提供服务。例子有fastdfs、ceph等。基于操作系统本身的文件系统,不是对文件做索引,而是对磁盘做索引,把一个文件分为多个块进行存储,例如fat32、ntfs、ext3/4等。分布式文件系统是需要基于操作系统本身的文件系统来做的。
linux中实现了vfs,针对不同的文件系统统一实现了文件系统的一系列操作接口,对外屏蔽了不同文件系统内部的区别。

fuse架构

fuse是内核的一个模块,里面有两个文件,file.c用来存储文件的属性如权限、大小等,inode.c用来存储文件具体数据。
可以通过命令ls /dev/fuse查看内核是否支持fuse,如果有就支持。可以在make menuconfig编译内核时选择是否支持fuse。
fuse包含一个内核模块和一个用户空间守护进程(下文称fuse daemon)。内核模块加载时被注册成 linux 虚拟文件系统的一个 fuse 文件系统驱动。此外,还注册了一个/dev/fuse的块设备。该块设备作为fuse daemon与内核通信的桥梁,fuse daemon通过/dev/fuse读取fuse request,处理后将reply写入/dev/fuse。
这里介绍一下三个模块:

  • fs/file:内核里实现的一个模块,运行起来后,就会生成/dev/fuse
  • libfuse:提供出来的fuse开发库
  • /dev/fuse:是块设备文件。

这三者的联系,与mysql实现做个类比,fs/file相当于mysql源码;libfuse相当于mysql的driver,去操作mysql;/dev/fuse相当于mysql应用程序。
在这里插入图片描述
上图详细展示了fuse的构架。当application挂在fuse文件系统上,并且执行一些系统调用时,VFS会将这些操作路由至fuse driver,fuse driver创建了一个fuse request结构体,并把request保存在请求队列中。此时,执行操作的进程会被阻塞,同时fuse daemon通过读取/dev/fuse将request从内核队列中取出,并且提交操作到底层文件系统中(例如 EXT4 或 F2FS)。当处理完请求后,fuse daemon会将reply写回/dev/fuse,fuse driver此时把requset标记为completed,最终唤醒用户进程。

libfuse流程

在这里插入图片描述
这里我们关注两点:

  1. splice实现内存零拷贝。在默认情况下,fuse daemon必须通过read()从/dev/fuse读取请求,通过write()将请求回复写入/dev/fuse。每次读写系统调用都需要进行一次内核-用户空间的内存拷贝。这样对读写的性能损耗十分严重,因为一次内存拷贝需要处理大量数据。为了缓解这个问题,fuse支持了Linux内核提供的 splice 功能。splice 允许用户空间在两个内核内存缓冲区之间传输数据,而无需将数据复制给用户空间。如果fuse daemon实现了write_buf()方法,则 FUSE 从/dev/fuse读取数据,并以包含文件描述符的缓冲区的形式将数据直接传递给此方法处理,从而省去了一次内存申请与拷贝。
  2. 多线程模式。在多线程模式下,fuse daemon以一个线程开始,如果内核队列中有两个以上的request,则会自动生成其他线程。默认最大支持10个线程同时处理请求。

fuse队列

在这里插入图片描述
fuse在内核中维护了五个队列,分别为:Backgroud、Pending、Processing、Interrupts、Forgets。一个请求在任何时候只会存在于一个队列中。

  1. Backgroud:background 队列用于暂存异步请求。在默认情况下,只有读请求进入 background 队列;当writeback cache启用时,写请求也会进入 background 队列。当开启writeback cache时,来自用户进程的写请求会先在页缓存中累积,然后当bdflush 线程被唤醒时会下刷脏页。在下刷脏页时,FUSE会构造异步请求,并将它们放入 background 队列中。
  2. Pending:同步请求(例如,元数据)放在 pending 队列中,并且pending队列会周期性接收来自background 的请求。但是pending队列中异步请求的个数最大为max_background(最大为12),当pending队列的异步请求未达到12时,background队列的请求将被移动到pending队列中。这样做的目的是为了控制pending队列中异步请求的个数,防止在突发大量异步请求的情况下,阻塞了同步请求。
  3. Processing:当pending队列中的请求被转发到fuse daemon的同时,也被移动到processing队列。所以processing队列中的请求,表示正在被处理fuse daemon处理的请求。当fuse daemon真正处理完请求,通过/dev/fuse下发reply时,该请求将从processing队列中删除。
  4. Interrupts:用于存放中断请求,比如当发送的请求被用户取消时,内核会发送一个Interrupts请求,来取消已被发送的请求。中断请求的优先级最高,Interrupts中的请求会最先得到处理。
  5. Forgets:forget请求用于删除dcache中缓存的inode。

当pending 、interrups、forgets队列都没有请求时,读进程进入休眠。一旦有请求到达,这个等待队列上的进程将被唤醒。Interrups 和 forgets的请求优先级高于pending队列。当请求的数据内容被拷贝至用户空间后,该请求会被移至processing队列。
当fuse daemon处理完请求后,会将结果写回到/dev/fuse。写数据保存在struct fuse_copy_state中,并且会根据unique id在fc(fuse_conn)中找到对应的req,并将写回的参数从fuse_copy_state拷贝至req->out。

再来总体看一下执行流程。
在这里插入图片描述
当fuse daemon处理完请求后,会将结果写回到/dev/fuse。写数据保存在struct fuse_copy_state中,并且会根据unique id在fc(fuse_conn)中找到对应的req,并将写回的参数从fuse_copy_state拷贝至req->out。

原网站

版权声明
本文为[唏噗]所创,转载请带上原文链接,感谢
https://blog.csdn.net/m0_65931372/article/details/126253082