由于以前有项目是用到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
Paddi