广州网站搭建快速提升网站排名,荧光字网站,网站宽度960,网站利润分析文件系统小册#xff08;FusePosixK8s csi#xff09;【1 Fuse#xff1a;用户空间的文件系统】 Fuse(filesystem in userspace),是一个用户空间的文件系统。通过fuse内核模块的支持#xff0c;开发者只需要根据fuse提供的接口实现具体的文件操作就可以实现一个文…文件系统小册FusePosixK8s csi【1 Fuse用户空间的文件系统】 Fuse(filesystem in userspace),是一个用户空间的文件系统。通过fuse内核模块的支持开发者只需要根据fuse提供的接口实现具体的文件操作就可以实现一个文件系统。由于其主要实现代码位于用户空间中而不需要重新编译内核这给开发者带来了众多便利。 虽然Fuse简化了文件系统的实现给开发者带来了便利。但是其额外的内核态/用户态切换带来的性能开销不能被忽视所以fuse性能问题一直是业界绕不开的话题。下面说到的splice、多线程、writeback cache都是为了改善其性能问题。 1 架构设计执行流程 用户程序挂载到fuse文件系统比如此时执行ls命令VFS虚拟文件系统检测到挂载到fuse文件系统上的用户程序发送的请求会将其转发给fuse driverfuse driver接受到request请求会将其保存到queue中同时暂停用户程序ls会卡主等待返回结果同时唤醒fuse daemon处理请求fuse daemon守护进程通过/dev/fuse读取queue中的request经过处理后将其转发给内核底层文件系统EXT4等。内核文件系统处理完成后将结果返回给fuse daemonfuse daemon将结果写回/dev/fusefuse driver将该request标记为completed并唤醒用户进程返回对应执行结果。ls执行结束终端展示文件列表 2 相关组件
①VFS转发请求给fuse driver VFS虚拟文件系统检测到挂载到fuse文件系统上的用户程序发送的请求会将其转发给fuse driver ② FUSE drvierqueue接受请求保存到queue fuse driver接受到request请求会将其保存到queue中同时暂停用户程序ls会卡主等待返回结果 ③/dev/fuse(桥梁)fuse daemon通过/dev/fuse读取queue中的请求 FUSE 驱动程序fuse driver处理请求并将其加入队列然后通过 /dev/fuse 文件FUSE 守护程序无法读取该文件中的特定连接实例将请求提交给负责处理该 FUSE 文件系统的 FUSE 守护程序。 fuse daemon通过/dev/fuse来读取request queue中的请求 ④fuse daemon中间人从queue中读取请求转发给底层文件系统 fuse daemon守护进程通过/dev/fuse读取queue中的request经过处理后将其转发给内核底层文件系统EXT4等。 ⑤fuse lib提供接口和内核fuse模块通信 fuse的lib库封装好了对应接口。fuse的lib库提供接口和内核fus模块通信 ⑥内核文件系统如EXT4 内核层面的文件系统真正操作文件的系统。 3 实现细节
① fuse用户空间流程
1. fuse mount通过mount函数将path挂载到/dev/fuse设备 Fuse的挂载通过mount函数将指定的fuse_path挂载到/dev/fuse设备上。之后对于fuse_path下的文件操作都会通过fuse文件系统并通过/dev/fuse被fuse daemon读取处理。 2. fuse threadfuse daemon创建的服务线程 Fuse daemon还会创建一个服务线程基于libfuse库来处理文件操作请求。这里主要关注fuse_session_new和fuse_session_loop_mt。通过fuse_session_new在libfuse中注册了fuse daemon实现的fuse_lowlevel_ops之后通过fuse的所有的文件操作都会通过libfuse回调到fuse daemon进行处理。fuse_session_loop_mt在libfuse中实现了一个多线程模式来读取请求相比单线程在请求处理上效率更高。 fuse daemon创建的服务线程基于libfuse库处理请求可多线程模式通过fuse_session_newnew一个session与内核fuse模块通信fuse_session_loop_mt多线程处理请求 3. libfusefuse的lib库提供接口和内核fus模块通信 fuse_session_loop_mtfuse thread基于多线程方式处理请求 splice实现内存零拷贝。在默认情况下fuse daemon必须通过read()从/dev/fuse读取请求通过write()将请求回复写入/dev/fuse。每次读写系统调用都需要进行一次内核-用户空间的内存拷贝。这样对读写的性能损耗十分严重因为一次内存拷贝需要处理大量数据。为了缓解这个问题fuse支持了Linux内核提供的 splice 功能。splice 允许用户空间在两个内核内存缓冲区之间传输数据而无需将数据复制给用户空间。如果fuse daemon实现了write_buf()方法则 FUSE 从/dev/fuse读取数据并以包含文件描述符的缓冲区的形式将数据直接传递给此方法处理从而省去了一次内存申请与拷贝。[提供缓冲区传数据避免用户空间与内核空间来回切换耗时]多线程模式。在多线程模式下fuse daemon以一个线程开始如果内核队列中有两个以上的request则会自动生成其他线程。默认最大支持10个线程同时处理请求。 [多线程队列request2自动生成新线程最大支持10并发] ②fuse内核队列维护了5个队列 fuse在内核中维护了五个队列分别为Backgroud、Pending、Processing、Interrupts、Forgets。一个请求在任何时候只会存在于一个队列中。 Backgroud存异步请求Pending存同步请求Processing存处理中的请求Interrupts存中断请求如用户ctrlC取消请求优先级最高Forgets存forget请求清理cache中的inode 1. Backgroud暂存异步请求 Backgroudbackground 队列用于暂存异步请求。在默认情况下只有读请求进入 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存储正在处理的请求
Processing当pending队列中的请求被转发到fuse daemon的同时也被移动到processing队列。所以processing队列中的请求表示正在被处理fuse daemon处理的请求。当fuse daemon真正处理完请求通过/dev/fuse下发reply时该请求将从processing队列中删除。
4. Interrupts存放中断请求用户取消请求如ctrlC
Interrupts用于存放中断请求比如当发送的请求被用户取消时内核会发送一个Interrupts请求来取消已被发送的请求。中断请求的优先级最高Interrupts中的请求会最先得到处理。
5. Forgets记录清理cache中inode的请求
Forgets存储forgets请求forget请求用于删除cache中缓存的inode。
③/dev/fuse 读写调用流程 Fuse driver加载过程中注册了对/dev/fuse的操作接口fuse_dev_operations。fuse_dev_do_read/fuse_dev_do_write分别对应fuse daemon从内核读取请求以及处理完请求后写回reply的函数调用。 pending 、interrups、forgets队列为空时读进程休眠。一旦有request到达对应等待队列上的进程被唤醒Interrups 和 forgets优先级高于pending队列请求当请求数据内容被拷贝到用户空间后fuse daemon在进行处理了该请求被移动到processing队列标识该请求已被处理。req-flags会保存当前请求的状态fuse daemon处理完请求后fuse daemon与内核底层FS打交道fuse daemon将结果写回到/dev/fuse。 其中写数据保存在struct fuse_copy_state中并且会根据unique id在fc(fuse_conn)中找到对应的req并将写回的参数从fuse_copy_state拷贝至req-out。 源码逻辑 当pending 、interrups、forgets队列都没有请求时读进程进入休眠。一旦有请求到达这个等待队列上的进程将被唤醒。Interrups 和 forgets的请求优先级高于pending队列。当请求的数据内容被拷贝至用户空间后该请求会被移至processing队列并且req-flags会保存当前请求的状态。 当fuse daemon处理完请求后会将结果写回到/dev/fuse。写数据保存在struct fuse_copy_state中并且会根据unique id在fc(fuse_conn)中找到对应的req并将写回的参数从fuse_copy_state拷贝至req-out。 4案例以unlink为例 fuse daemon会阻塞在读/dev/fuse,当app进程在fuse挂载点下面有新的文件操作unlink这时系统调用会调用fuse内核接口并生成request同时唤醒阻塞的fuse daemonfuse daemon读到request后在libfuse中进行解析根据request的opcode来执行对应的ops完成后会把处理结果返回给/dev/fuse。此时vfs调用阻塞的行为将被唤醒最后返回vfs调用。 5 实战go-fuse 相关仓库地址 https://github.com/hanwen/go-fusehttps://github.com/bazil/fusehttps://github.com/libfuse/libfuse/ Golang操作fuse的库主要有go-fuse、libfuse。这里主要讲解go-fuse
①概述 Go-Fuse 是一个开源的库由 Han-Wen Nienhuys 创建并维护。该库提供了对 Linux FUSEFilesystem in Userspace接口的支持使得开发人员可以使用 Go 语言构建自己的文件系统。 功能 构建自定义文件系统使用 Go-Fuse您可以根据需要构建自己的文件系统。这可能包括加密、压缩、优化性能等功能。支持各种平台由于 Go-Fuse 基于 FUSE因此它可以跨多个操作系统如 Linux、macOS 和 Windows运行。高度自定义通过实现特定的接口方法您可以控制文件系统的每个细节。这为实现复杂的文件系统行为提供了极大的灵活性。 ②环境准备 我准备在我本地macos上构建因此需要fuse命令。 macoshttps://github.com/osxfuse/osxfuse/releases下载dmg安装配置ubuntu sudo apt-get -y update sudo apt-get install -y fusecentossudo yum -y update sudo yum install -y fuse 安装好之后需要确保当前用户需要有执行fuse命令的权限 # 如果当前用户没有权限可以进行提权或者切换用户或者修改fuse配置
vim /etc/fuse.conf打开user_allow_other③全部代码解析
//安装依赖
go get github.com/hanwen/go-fuse/v2/fs
go get github.com/hanwen/go-fuse/v2/fusepackage mainimport (contextflaglogsyscallgithub.com/hanwen/go-fuse/v2/fsgithub.com/hanwen/go-fuse/v2/fuse
)type HelloRoot struct {fs.Inode
}func (r *HelloRoot) OnAdd(ctx context.Context) {ch : r.NewPersistentInode(ctx, fs.MemRegularFile{Data: []byte(file.txt data),Attr: fuse.Attr{Mode: 0644,},}, fs.StableAttr{Ino: 2})r.AddChild(file.txt, ch, false)
}func (r *HelloRoot) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {out.Mode 0755return 0
}var _ (fs.NodeGetattrer)((*HelloRoot)(nil))
var _ (fs.NodeOnAdder)((*HelloRoot)(nil))//./yi-fuse test
func main() {debug : flag.Bool(debug, false, print debug data)flag.Parse()if len(flag.Args()) 1 {log.Fatal(Usage:\n ./yi-fuse MOUNTPOINT)}opts : fs.Options{}opts.Debug *debugserver, err : fs.Mount(flag.Arg(0), HelloRoot{}, opts)if err ! nil {log.Fatalf(Mount fail: %v\n, err)}server.Wait()
}我们通过go-fuse库创建了一个用户空间文件系统该文件系统只包含一个名为file.txt的文件。context用于处理上下文可以在异步操作中取消请求。flag处理命令行参数。log日志记录。syscall系统调用接口。fs 和 fuse来自github.com/hanwen/go-fuse/v2的库用于实现用户空间文件系统。HelloRoot 结构体 表示文件系统的根节点实现了NodeGetattrer和NodeOnAdder接口。OnAdd 方法当文件系统被加载时调用创建一个包含file.txt的持久化节点。Getattr 方法获取文件属性将file.txt的权限设置为0755。main 函数 处理命令行参数设置调试标志。 检查至少有一个挂载点参数。 创建fs.Options启用调试模式。 调用fs.Mount挂载文件系统。 如果挂载失败打印错误信息并退出。 server.Wait()阻塞直到文件系统卸载。 ④测试
//编译可执行文件到linux
GOOSlinux GOARCHamd64 go build -o yi-fuse main.go
//创建挂载目录
mkdir -p /root/test
//执行挂载如果不加nohup默认前台运行
nohup ./yi-fuse /root/test //预期返回我们代码里写的file.txt文件
ls -l /root/test//读取file.txt文件内容
cat /root/test/file.txt//卸载挂载
umount /root/test参考文章 https://www.cnblogs.com/Linux-tech/p/14110335.html https://blog.csdn.net/gitblog_00007/article/details/136569849