背景
文件系统,是任何OS都不可却少的。想要编写一个属于自己的文件系统很容易,但调试十分不方便。为了方便调试,提高开发效率,可以使用FUSE(Filesystem in userspace)框架进行开发。这是一个内核模块,能够让用户在用户空间实现文件系统并且挂载到某个目录,就像在内核实现的文件系统一样。使用 FUSE 有几个好处:一是因为在用户空间实现,开发和调试都比较方便;二是可以把一些常用的服务以文件系统的形式展现,方便操作,如 ftpfs,sshfs,mailfs 等;另外可以避免一些版权问题,如 Linux 上对 ntfs,zfs 的操作都是通过 FUSE 实现的。当然用户空间的实现也有缺点,最明显的就是由多次在用户态/内核态切换带来的性能下降。
FUSE架构
将文件系统需要处理的读、写、创建、删除等函数,以回调的方式注册到FUSE模块中,当用户访问挂载目录时,FUSE模块回调相应的注册接口。
FUSE安装
ubuntu 16.04 安装
需要安装libfuse2
和libfuse-dev
1 | sudo apt-get install libfuse2 libfuse-dev pkg-config |
版本:
1 | FUSE library version: 2.9.4 |
OSX 10.11.6 安装
需要安装Caskroom/cask/osxfuse
1 | brew cask install osxfuse |
版本:
1 | OSXFUSE 3.5.4 |
FUSE使用
FUSE有两种接口,一种是fuse_operations
另一种是fuse_lowlevel_ops
。
fuse_operations
是较为上层的接口,我们可以使用fuse_main
函数将其传入FUSE中
eg:1
2
3
4
5
6
7
8
9
10static struct fuse_operations cryptfs_ops = {
.init = cfs_init,
.destroy = cfs_destroy,
.open = cfs_open,
.read = cfs_read,
.write = cfs_write,
.release = cfs_release,
.readdir = cfs_readdir,
.getattr = cfs_getattr,
};fuse_lowlevel_ops
是较底层的接口,我们可以使用fuse_session_loop
函数实现
eg:1
2
3
4
5
6
7
8
9
10
11
12
13static struct fuse_lowlevel_ops lowlevel_handler =
{
.lookup = lowlevel_lookup,
.getattr = lowlevel_getattr,
.readdir = lowlevel_readdir,
.mkdir = lowlevel_mkdir,
.rmdir = lowlevel_rmdir,
.open = lowlevel_open,
.read = lowlevel_read,
.write = lowlevel_write,
.unlink = lowlevel_unlink,
.rename = lowlevel_rename,
};fuse_operations
使用简单,容易上手。fuse_lowlevel_ops
灵活性大,需要有FS开发经验。
Helloworld示例
实现ls ./mp
系统提示Hello-world
(./mp
为挂载目录)ls
会用到的方法代码:
1 |
|
fuse_main
将接口注册到FUSE中:
1 | static struct fuse_operations cryptfs_ops = { |
编译
ubuntu 16.04
编译命令:
1 | gcc myfuse.c -o myfuse -DFUSE_USE_VERSION=22 `pkg-config fuse --cflags --libs` -g |
or
1 | gcc myfuse.c -o myfuse -DFUSE_USE_VERSION=22 -D_FILE_OFFSET_BITS=64 -I/usr/include/fuse -lfuse -pthread -g |
使用后者编译时不需要安装包pkg-config
,由于不同操作系统fuse安装位置不一样,所以还是推荐使用pkg-config
的方式
调试
可使用GDB调试,myfuse
会以守护进程方式启动,调试有三种方法。
- 待
myfuse
进程启动后,使用GDB中的attach
方法进行调试 - 使用GDB调试
myfuse
,参数中增加-d
参数,该参数可以使myfuse
不已守护进程方式启动 ( ** Version: 2.9.9上无法实现 ** ) - 在
myfuse
中增加调试打印信息进行调试
挂载FUSE
先创建一个目录(mkdir ./mp
),用于挂载myfuse
文件系统,然后调用myfuse
进行挂载。
eg:
1 | sudo ./myfuse ./mp |
对于mac系统挂载文件系统需要使用参数-o allow_other
,否则挂在后,挂载目录将无权访问。
eg:
1 | sudo ./myfuse ./mp -o allow_other |
使用mount
查看挂载的文件系统。
eg:
1 | mount |
使用ls ./mp
查看挂载点内容,根据实现应提示Hello-world
eg:
1 | ls ./mp/ |
卸载FUSE
使用umount
卸载myfuse
文件系统,需要输入挂载点的全路径
eg:
1 | sudo umount /home/xxxx/xxxx/mp |
FUSE Options说明
fuse_main
函数自带usage信息,只需要将--help
参数传入,便能将usage信息打出来。
1 | ./myfuse --help |
FUSE Operations说明
1 | struct fuse_operations { |
更多详细说明,请见fuse.h
文件。