由于以前有项目是用到FUSE,将S3等对象存储映射为文件存储的,但我不是负责那一块,所以一直只是知道FUSE是个什么东西,而没有用过。刚好趁着没工作的这段时间,学习Golang,顺便把FUSE也了解下,实现了一个简易版的libfuse: https://github.com/mingforpc/fuse-go和一个可以将HDFS Mount到本地文件夹的程序: https://github.com/mingforpc/hadoop-fs。
由于刚学并整理,如有遗漏或者错误!请拼命指出,谢谢!
什么是FUSE
FUSE的全称是Filesystem in Userspace,即用户空间文件系统,是系统内核提供的一个功能,使得可以在用户态下实现一个自定义的文件系统。比如CEPH和GlusterFS等都有使用到FUSE。
而libfuse则是提供给用户态下的开发包。
FUSE是怎么交互的
FUSE是通过读写/dev/fuse
让用户态的文件系统进程和内核通信的。
程序需要先打开/dev/fuse
,然后通过mount()
将/dev/fuse
的fd,进程的用户id和组id传入,进行文件系统的挂载。
PS: libfuse通过自己写的fusermount程序(编译安装libfuse后会在/bin/
下),可以让我们实现的文件系统程序在非root权限下挂载,这部分我还不是很了解,我自己实现的go版libfuse也只是依赖于这个fusermount。
FUSE的指令号、对应的函数、请求格式与响应
程序从/dev/fuse
中读取请求,不需要担心像TCP有半包粘包等问题,一次读取一条请求。buffer不够大,会报异常。
请求和响应,都是以二进制字节表示的,下文中的结构体只是为了方便看。
请求头
每个命令的前 40 bytes为请求头,转为Golang的结构体如下:
- // Each query starts with a FuseInHeader
- type FuseInHeader struct {
- Len uint32
- Opcode uint32
- Unique uint64
- Nodeid uint64
- Uid uint32
- Gid uint32
- Pid uint32
- Padding uint32
- }
- Len: 是整个请求的字节数长度,包括请求头后的具体内容
- Opcode: 请求的类型
- Unique: 该请求唯一标识,响应中要对应着该Unique
- Nodeid: 该请求针对的文件nodeid,目标文件或者文件夹的nodeid
- Uid: 对该文件/文件夹操作的进程的用户ID
- Gid: 对该文件/文件夹操作的进程的用户组ID
- Pid: 对该文件/文件夹操作的进程的进程ID
响应头
程序写入/dev/fuse
的每个响应的前 16 bytes为响应头,转为Golang的结构体如下:
- type FuseOutHeader struct {
- Len uint32
- Error int32
- Unique uint64
- }
- Len: 是整个响应的字节数长度,包括响应头后的具体内容
- Error: 一个负数的错误码,成功返回0,其他对应着系统(
error.h
)的错误代码,但是为负数,每个操作的错误返回可以查看linux man中相应的函数 - Unique: 对应者请求的唯一标识
请求类型和具体结构
数字对应着请求中的Opcode
FUSE_LOOKUP = 1
lookup()
函数,Look up a directory entry by name and get its attributes.
如解析所说,获取代请求头Nodeid文件夹下该名字的文件的属性,包含着 inode id等。
请求头后的实体
- type FuseLookupIn struct {
- Name string // 字符串结尾的`\0`会计算到长度中,解析时需注意
- }
- Name: 文件名
响应头的实体
- type FuseEntryOut struct {
- NodeId uint64 /* Inode ID */
- Generation uint64 /* Inode generation: nodeid:gen must be unique for the fs's lifetime */
- EntryValid uint64 /* Cache timeout for the name */
- AttrValid uint64 /* Cache timeout for the attributes */
- EntryValidNsec uint32
- AttrValidNsec uint32
-
- Attr FuseAttr
- }
-
- type FuseAttr struct {
- Ino uint64
- Size uint64
- Blocks uint64
- Atime uint64
- Mtime uint64
- Ctime uint64
- AtimeNsec uint32
- MtimeNsec uint32
- CtimeNsec uint32
- Mode uint32
- Nlink uint32
- Uid uint32
- Gid uint32
- Rdev uint32
- Blksize uint32
- Padding uint32
- }
- NodeId: 文件的Inode ID
- Generation: 同一个文件, nodeid和gen的组合,必须在整个文件系统的生命周期中唯一
- EntryValid: 对于文件的Name的缓存时间,单位是秒
- EntryValidNsec: 同上,但是该属性表示毫秒部分
- AttrValid: 对于文件的属性的缓存时间,单位是秒
- AttrValidNsec: 同上,但是该属性表示毫秒部分
- Attr: 该文件的属性,可以对应属性的意义可以参考文件属性
stat
FUSE_FORGET = 2
forget()
函数,Forget about an inode
不需要返回任何响应的操作。
请求头后的实体
- // forget (should not send any reply)
- type FuseForgetIn struct {
- Nlookup uint64
- }
FUSE_GETATTR = 3
getattr()
函数,Get file attributes.
请求头后的实体
- type FuseGetattrIn struct {
- GetattrFlags uint32
- Dummy uint32
- Fh uint64
- }
响应头的实体
- type FuseAttrOut struct {
- AttrValid uint64 /* Cache timeout for the attributes */
- AttrValidNsec uint32
- Dummp uint32
-
- Attr FuseAttr
- }
-
- type FuseAttr struct {
- Ino uint64
- Size uint64
- Blocks uint64
- Atime uint64
- Mtime uint64
- Ctime uint64
- AtimeNsec uint32
- MtimeNsec uint32
- CtimeNsec uint32
- Mode uint32
- Nlink uint32
- Uid uint32
- Gid uint32
- Rdev uint32
- Blksize uint32
- Padding uint32
- }