FUSE的全称是“Filesystem in Userspace”,即“用户空间的文件系统”,这是一个内核模块,能够让用户在用户空间实现文件系统并且挂载到某个目录,就像在内核实现的文件系统一样。
使用 FUSE 有几个好处:一是因为在用户空间实现,开发和调试都比较方便;二是可以把一些常用的服务以文件系统的形式展现,方便操作,如 ftpfs,sshfs,mailfs 等;另外可以避免一些版权问题,如 Linux 上对 ntfs,zfs 的操作都是通过 FUSE 实现的。
当然用户空间的实现也有缺点,最明显的就是由多次在用户态/内核态切换带来的性能下降。
用户通过FUSE和内核的通信过程如下:
+----------------+ | myfs /tmp/fuse | +----------------+ | ^+--------------+ v || ls /tmp/fuse | +--------------++--------------+ | libfuse | ^ | +--------------+ | v | |+--------------+ +--------------+| glibc | | glibc |+--------------+ +--------------+ ^ | | ^~.~.~.|.~|~.~.~.~.~.~.~.~.|.~.|.~.~.~.~.~.~.~.~. | v v |+--------------+ +--------------+| |----| FUSE || | +--------------+| VFS | ...| | +--------------+| |----| Ext3 |+--------------+ +--------------+
从上图可知,FUSE和ext3一样,是内核里的一个文件系统模块,用FUSE实现了一个文件系统并挂载在/tmp/fuse,当我们对该目录执行ls时,内核里的FUSE从VFS获得参数,然后调用我们自己实现的myfs中相应的函数,得到结果后再通过VFS返回给ls。
相应的在Linux系统下需要安装libfuse-dev,fuse-utils及其它相关依赖。
下面先来个简单的文件系统oufs(oufs.c),仅支持ls操作:
#define FUSE_USE_VERSION 26#include<stdio.h>#include<string.h>#include<fuse.h>staticintou_readdir(constchar* path, void* buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info* fi){ return filler(buf, "hello-world", NULL, 0);}staticintou_getattr(constchar* path, struct stat* st){ memset(st, 0, sizeof(struct stat)); if (strcmp(path, "/") == 0) st->st_mode = 0755 | S_IFDIR; else st->st_mode = 0644 | S_IFREG; return 0;}static struct fuse_operations oufs_ops = { .readdir = ou_readdir, .getattr = ou_getattr,};intmain(int argc, char* argv[]){ return fuse_main(argc, argv, &oufs_ops, NULL);}
对应的Makefile文件内容如下:
CC := gccCFLAGS := -g -Wall -D_FILE_OFFSET_BITS=64OBJS := $(patsubst %.c, %.o, $(wildcard *.c))LIBS := -lfuseTARGET := oufs.PHONY: all cleanall: $(TARGET)$(TARGET): $(OBJS) $(CC) $(CFLAGS) -o $@ $^ $(LIBS).c.o: $(CC) $(CFLAGS) -c $<clean: rm -f $(TARGET) $(OBJS)
执行make编译成功后,会生成可执行文件oufs,然后创建一个目录/tmp/mnt作为挂载点,执行如下命令挂载:
挂载成功后,试着执行“ls /tmp/mnt”,就能看到一个文件“hello-world”。要调试时加上“-d”选项,就能看到FUSE和printf的调试输出。
代码第一行指定了要使用的FUSE API版本。这里使用的是2.6版本。
要实现ls的功能,FUSE需要提供两个函数:readdir()和getattr(),这两个接口是struct fuse_operations里的两个函数指针(定义在 /usr/include/fuse/fuse.h):
/** Function to add an entry in a readdir() operation * * @param buf the buffer passed to the readdir() operation * @param name the file name of the directory entry * @param stat file attributes, can be NULL * @param off offset of the next entry or zero * @return 1 if buffer is full, zero otherwise */typedefint(*fuse_fill_dir_t)(void *buf, constchar *name, const struct stat *stbuf, off_t off);struct fuse_operations { /** Get file attributes. * * Similar to stat(). The 'st_dev' and 'st_blksize' fields are * ignored. The 'st_ino' field is ignored except if the 'use_ino' * mount option is given. */ int (*getattr) (const char *, struct stat *); /** Read directory * * This supersedes the old getdir() interface. New applications * should use this. * * The filesystem may choose between two modes of operation: * * 1) The readdir implementation ignores the offset parameter, and * passes zero to the filler function's offset. The filler * function will not return '1' (unless an error happens), so the * whole directory is read in a single readdir operation. This * works just like the old getdir() method. * * 2) The readdir implementation keeps track of the offsets of the * directory entries. It uses the offset parameter and always * passes non-zero offset to the filler function. When the buffer * is full (or an error happens) the filler function will return * '1'. * * Introduced in version 2.3 */ int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *); ......};
这里要实现getattr()是因为要遍历根目录的内容,要通过getattr()获取根目录权限等信息。getattr() 实现类似stat()的功能,返回相关的信息如文件权限、类型、uid等。参数里的path有可能是文件、目录、软链接或其它,因此除了权限外还要填充类型信息。程序里除了根目录就只有一个文件hello-world,因此只有目录(S_IFDIR)和普通文件(S_IFREG)两种情况。使用“ls -l”查看/tmp/mnt下的内容可以发现,hello-world的link 数、修改时间等都是错误的,那是因为ou_getattr()实现中没有填充这些信息。查看manpage可以知道stat都有哪些字段。
readdir() 的参数fuse_fill_dir_t是一个函数指针,每次往buf中填充一个entry的信息。程序中的ou_readdir()采用了注释中的第一种模式,即把所有的entry一次性放到buffer中。如果entry较多,把readdir()中的offset传给filler即可,buffer满了filler返回1。
最后,在main()函数中调用的是fuse_main(),把文件系统的操作函数和参数(如挂载点/tmp/mnt)传给FUSE。