用户态文件系统 - FUSE

2024-04-12 02:08
文章标签 用户 文件系统 fuse

本文主要是介绍用户态文件系统 - FUSE,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

用户态文件系统 - FUSE

 

所谓“用户态文件系统”,是指一个文件系统的data和metadata都是由用户态的进程提供的(这种进程被称为"daemon")。对于micro-kernel的操作系统来说,在用户态实现文件系统不算什么,但对于macro-kernel的Linux来说,意义就有所不同。

虽然叫做用户态文件系统,但不代表其完全不需要内核的参与,因为在Linux中,对文件的访问都是统一通过VFS层提供的内核接口进行的(比如open/read),因此当一个进程(称为"user")访问由daemon实现的文件系统时,依然需要途径VFS。

当VFS接到user进程对文件的访问请求,并且判断出该文件是属于某个用户态文件系统(根据mount type),就会将这个请求转交给一个名为"fuse"的内核模块。而后,"fuse"将该请求转换为和daemon之间约定的协议格式,传送给daemon进程。

图 1

可见,在这个三方关系中,"fuse"这个内核模块起的是一个转接的作用,它帮助建立了VFS(也可以说是user进程)和daemon之间的交流通道,通俗点说,它的角色其实就是一个「代理」。

这一整套框架的实现在Linux中即为FUSE (Filesystem in Userspace)。如图1所示,红框的部分才是FUSE类型文件系统的具体实现,才是用户态文件系统的设计者可以发挥的空间。目前,已有不下百种基于FUSE实现的文件系统(一些基于内核的文件系统也可以porting成用户态文件系统,比如ZFS和NTFS),而本文将选用一个现成的fuse-sshfs来进行演示。

首先安装fuse-sshfs的软件包,使用如下的命令进行文件系统的mount(将远端机器的"remote-dir"目录挂载到本机的"local-dir"目录):

sshfs <remote-ip>:<remote-dir> <local-dir>

之后,在"/sys/fs"目录下,将生成一个名为"fuse"的文件夹,同时可以看到"fuse"内核模块已被加载(其对应的设备为"/dev/fuse"),并且本机的挂载目录的类型已成为"fuse.sshfs":

生成设备节点的目的是方便用户态的控制,但是对于文件系统这种级别的应用来说,直接使用 ioctl() 来访问设备还是显得麻烦,因为呈现了太多的细节,所以libfuse作为一个中间层应运而生,daemon进程实际都是通过libfuse提供的接口来操作fuse设备文件的。

你来我往

接下来,以在"fuse.sshfs"文件系统中通过"touch"命令新建一个文件为例,查看fuse内核模块和daemon进程(即"sshfs")具体的交互流程(代码部分基于内核5.2.0版本):

【第一轮】

最开始是permission的校验,不过这里的校验并不等同于VFS的权限校验,它的主要目的是为了避免其他user访问到了自己私有的fuse文件系统。

然后就是根据文件路径查找文件的inode。由于是新建的文件,inode并不在内核的inode cache中,所以需要向daemon发送"lookup"的请求:

这些请求会被放入一个pending queue中,等待daemon进程的回复,而user进程将陷入睡眠:

作为daemon,sshfs进程通过读取"/dev/fuse"设备文件来获得数据,如果pending queue为空,它将陷入阻塞等待:

当pending queue上有请求到来时,daemon进程将被唤醒并处理这些请求。被处理的请求会被移入processing queue,待daemon进程向fuse内核模块做出reply之后,user进程将被唤醒,对应的request将从processing queue移除。

【第二轮

接下来就是执行"touch"命令时所触发的其他系统调用,如果是之前访问过的data/metadata,那很可能存在于cache中,再次访问这部分data/metadata的时候,fuse内核模块就可以自行解决,不需要去用户空间往返一趟,否则还是需要上报daemon进程进行处理。

这里 get_fuse_conn() 获取的是在fuse类型的文件系统被mount时创建的"fuse_conn"结构体实例。作为daemon进程和kernel联系的纽带,除非daemon进程消亡,或者对应的fuse文件系统被卸载,否则该connection将一直存在。

在daemon进程这一端,还是类似的操作。需要注意的是区别 fuse_write/read() fuse_dev_write/read() 这两个系列的函数,前者是user进程在访问fuse文件系统上的文件时的VFS读写请求,属于对常规文件的操作,而后者是daemon进程对"/dev/fuse"这个代表fuse内核模块的设备的读写,目的是为了获取request和给出reply。

【第三轮

fuse内核模块和daemon进程的最后一轮交互是在代表fuse文件系统的superblock中获取inode号,并填写这个metadata的相关信息。

硬币的两面

不难发现,在fuse文件系统中,即便执行一个相对简单的"touch"操作,所涉及的用户态和内核态的切换都是比较频繁的,并且还伴随着多次的数据拷贝。相比于传统的内核文件系统,它整体的I/O吞吐量更低,而延迟也更大。

那为什么fuse在操作系统支持的文件系统里面依然占据一席之地呢?说起来,在用户态开发是有很多优势的。一是便于调试,特别适合做一个新型文件系统prototype的快速验证,因此在学术研究领域颇受青睐。在内核里面,你只能用C语言吧,到了用户态,就没那么多限制了,各种函数库,各种编程语言,都可以上。

二是内核的bug往往一言不合就导致整个系统crash(在虚拟化的应用中更为严重,因为宿主机的crash会导致其上面运行的所有虚拟机crash),而用户态的bug所造成的影响相对有限一些。

所以,硬币的正面是便于开发,不过到底有多方便,这毕竟是一种主观的感受,而反面则是性能的影响,这可是能够用客观的实验数据来验证的。那应该用什么方法才能相对准确地衡量fuse所带来的损耗呢?

还是用前面用过的这个fuse-sshfs,不过这里我们不再使用远端挂载,而是采用本地挂载的方式(假设本机的"dir-src"目录位于ext4文件系统):

sshfs localhost:<dir-src> <dir-dst>

当daemon进程收到请求后,它需要再次进入内核,去访问ext4的内核模块(这种文件系统模式被称为"stackable"的):

以user进程向fuse文件系统发出 write() 请求为例,右边红框部分是一次原生的ext4调用路径,而左边多出来的就是因为引入fuse后增加的路径:

根据这篇文档给出的数据,在这一系统调用中使用到的"getxattr"所形成的request,需要2倍的"user-kernel"交互量。对于顺序写,相比起原生的ext4文件系统,I/O吞吐量降低27%,随机写则降低44%。

不过,在fuse文件系统诞生的这么多年里,大家还是为它想出了很多的优化举措。比如,顺序读写的时候,可以设计为向daemon进程批量发送request的形式(但随机读写不适合)。

还有就是使用splicing这种zero-copy技术,由Linux内核提供的splicing机制允许用户空间在转移两个内核的内存buffer的数据时,不需要拷贝,因此尤其适合stackable模式下,从fuse内核模块直接向ext4内核模块传递数据(但splicing通常用于超过4K的请求,小数据量的读写用不上)。

经过这些努力,fuse文件系统的性能可以达到什么样的一种程度呢?根据这篇报告列出的测试结果,相比起原生的ext4,在最理想的情况下,fuse的性能损耗可以控制到5%以内,但最差的情况则是83%。同时,其对CPU的资源占用也增加了31%。

从Android v4.4到v7.0之间存在的sdcard daemon,到最近几年的Ceph和GlusterFS,都曾经采用过或正在采用基于FUSE的实现。FUSE在network filesystem和虚拟化应用中都展现了自己的用武之地,它的出现和发展,并不是要取代在内核态实现的文件系统,而是作为一个有益的补充(理论上,FUSE还可以用于实现根文件系统,但是不建议这么做,"can do"和"should do"是两回事)。

https://zhuanlan.zhihu.com/p/143256077

这篇关于用户态文件系统 - FUSE的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/895813

相关文章

Golang使用minio替代文件系统的实战教程

《Golang使用minio替代文件系统的实战教程》本文讨论项目开发中直接文件系统的限制或不足,接着介绍Minio对象存储的优势,同时给出Golang的实际示例代码,包括初始化客户端、读取minio对... 目录文件系统 vs Minio文件系统不足:对象存储:miniogolang连接Minio配置Min

【Kubernetes】K8s 的安全框架和用户认证

K8s 的安全框架和用户认证 1.Kubernetes 的安全框架1.1 认证:Authentication1.2 鉴权:Authorization1.3 准入控制:Admission Control 2.Kubernetes 的用户认证2.1 Kubernetes 的用户认证方式2.2 配置 Kubernetes 集群使用密码认证 Kubernetes 作为一个分布式的虚拟

vue2实践:el-table实现由用户自己控制行数的动态表格

需求 项目中需要提供一个动态表单,如图: 当我点击添加时,便添加一行;点击右边的删除时,便删除这一行。 至少要有一行数据,但是没有上限。 思路 这种每一行的数据固定,但是不定行数的,很容易想到使用el-table来实现,它可以循环读取:data所绑定的数组,来生成行数据,不同的是: 1、table里面的每一个cell,需要放置一个input来支持用户编辑。 2、最后一列放置两个b

家庭和学生用户笔记本电脑配置方案

2.6.1  家庭和学生用户笔记本电脑配置方案   2.6.1  家庭和学生用户笔记本电脑配置方案   普通家庭用户、学生用户主要用于上网、娱乐、学习等,这类用户要求笔记本电脑的各方面 功能比较均衡。在选购此类笔记本电脑时,主要考虑外观设计方面要比较时尚,而且性能上也要 够强,一些大型复杂的软件以及目前的主流游戏都要能够流畅地运行才行。   对于CPU方面,可以考虑目前主流的第二

使用jetty和mongodb做个简易文件系统

使用jetty和mongodb做个简易文件系统 - ciaos 时间 2014-03-09 21:21:00   博客园-所有随笔区 原文   http://www.cnblogs.com/ciaos/p/3590662.html 主题  MongoDB  Jetty  文件系统 依赖库: 1,jetty(提供http方式接口) 2,mongodb的java驱动(访问mo

Ubuntu ftp搭建--配置不同用户不同权限

一、安装VSFTP sudo apt-get install vsftpd 二、添加FTP用户 sudo mkdir /etc/vsftpdsudo useradd -m -d /home/vsftpd vsftpd --用户名为vsftpd,目录和用户名可以自己更改sudo vi /etc/vsftpd/ftpuser.txt --这个到时与vsftp的配置文件对应建立一

利用PL/SQL工具如何给指定用户分配权限

选中指定的表--右键--编辑--就出现右边的内容了,选择权限,分配用户某个权限就行了;

配置JAVA环境的时候,环境变量中administrator的用户变量和系统变量的区别?

迁移项目到新的服务器上: 需要Java运行环境时,经常要配置环境变量,如图所示 这里有administrator的用户变量(U),和系统变量(S), 那么,它们之间有什么区别呢? 简单的说,一个是当前用户使用,你用其它用户登陆,这个环境变量就不起作用了。 系统变量,是对所有用户都可使用的。 简单的说: 系统变量:不管以哪个用户名登陆到计算机都能使

mysql用户管理 存储过程

1. 查看MySql的当前用户名 select user(); status;看看一下当前的一些状态 2.MYSQL中所有的用户名和其信息 mysql数据库的user表 3. grant 权限 on 应用范围(数据库表,方法等) to 用户(用@隔开,前面是用户名后面是主机名’ 用户名’@’主机名’) identified by 密码 require 要求什么的 with 对用