FUSE的全称是”Filesystem in Userspace”,即“用户空间的文件系统”,这是一个内核模块,能够让用户在用户空间实现文件系统并且挂载到某个目录,就像在内核实现的文件系统一样。使用FUSE有几个好处:一是因为在用户空间实现,开发和调试都比较方便;二是可以把一些常用的服务以文件系统的形式展现,方便操作,如ftpfs,sshfs,mailfs等;另外可以避免一些版权问题,如linux上对ntfs,zfs的操作都是通过FUSE实现的。当然用户空间的实现也有缺点,最明显的就是由多次在用户态/内核态切换带来的性能下降。
根据参考资料[1]的介绍,用户通过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。
以下实验的环境是debian 6,需要安装libfuse-dev,fuse-utils及其它相关依赖。
hello, world
下面是一个简单的文件系统oufs,只支持ls操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | #define FUSE_USE_VERSION 26 #include <stdio.h> #include <string.h> #include <fuse.h> static int ou_readdir(const char* path, void* buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info* fi) { return filler(buf, "hello-world", NULL, 0); } static int ou_getattr(const char* 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, }; int main(int argc, char* argv[]) { return fuse_main(argc, argv, &oufs_ops, NULL); } |
和编译程序的Makefile:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | CC := gcc CFLAGS := -g -Wall -D_FILE_OFFSET_BITS=64 OBJS := $(patsubst %.c, %.o, $(wildcard *.c)) LIBS := -lfuse TARGET := oufs .PHONY: all clean all: $(TARGET) $(TARGET): $(OBJS) $(CC) $(CFLAGS) -o $@ $^ $(LIBS) .c.o: $(CC) $(CFLAGS) -c $< clean: rm -f $(TARGET) $(OBJS) |
编译成功后会看到生成的可执行文件oufs。建立一个挂载点/tmp/mnt,然后运行
./oufs /tmp/mnt |
成功后试试”ls /tmp/mnt”,就能看到一个文件hello-world。要调试的时候可以加上-d选项,这样就能看到FUSE和自己printf的调试输出。
代码第一行指定了要使用的FUSE API版本。这里使用的是2.6版本。
要实现ls的功能,FUSE需要提供两个函数:readdir()和getattr(),这两个接口是struct fuse_operations里的两个函数指针:
/** 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 */ typedef int (*fuse_fill_dir_t) (void *buf, const char *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。
创建/删除文件
接下来让我们扩充上面的程序,增加创建/删除普通文件的功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | #define FUSE_USE_VERSION 26 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <fuse.h> #include "list.h" #define MAX_NAMELEN 255 struct ou_entry { mode_t mode; struct list_node node; char name[MAX_NAMELEN + 1]; }; static struct list_node entries; static int ou_readdir(const char* path, void* buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info* fi) { struct list_node* n; filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); list_for_each (n, &entries) { struct ou_entry* o = list_entry(n, struct ou_entry, node); filler(buf, o->name, NULL, 0); } return 0; } static int ou_getattr(const char* path, struct stat* st) { struct list_node* n; memset(st, 0, sizeof(struct stat)); if (strcmp(path, "/") == 0) { st->st_mode = 0755 | S_IFDIR; st->st_nlink = 2; st->st_size = 0; list_for_each (n, &entries) { struct ou_entry* o = list_entry(n, struct ou_entry, node); ++st->st_nlink; st->st_size += strlen(o->name); } return 0; } list_for_each (n, &entries) { struct ou_entry* o = list_entry(n, struct ou_entry, node); if (strcmp(path + 1, o->name) == 0) { st->st_mode = o->mode; st->st_nlink = 1; return 0; } } return -ENOENT; } static int ou_create(const char* path, mode_t mode, struct fuse_file_info* fi) { struct ou_entry* o; struct list_node* n; if (strlen(path + 1) > MAX_NAMELEN) return -ENAMETOOLONG; list_for_each (n, &entries) { o = list_entry(n, struct ou_entry, node); if (strcmp(path + 1, o->name) == 0) return -EEXIST; } o = malloc(sizeof(struct ou_entry)); strcpy(o->name, path + 1); /* skip leading '/' */ o->mode = mode | S_IFREG; list_add_prev(&o->node, &entries); return 0; } static int ou_unlink(const char* path) { struct list_node *n, *p; list_for_each_safe (n, p, &entries) { struct ou_entry* o = list_entry(n, struct ou_entry, node); if (strcmp(path + 1, o->name) == 0) { __list_del(n); free(o); return 0; } } return -ENOENT; } static struct fuse_operations oufs_ops = { .getattr = ou_getattr, .readdir = ou_readdir, .create = ou_create, .unlink = ou_unlink, }; int main(int argc, char* argv[]) { list_init(&entries); return fuse_main(argc, argv, &oufs_ops, NULL); } |
代码中用到的链表操作list.h见附录。本来想用c++的list,但是用g++编译时出错了,搜错误信息的时候发现了一个gcc的bug。多嘴插一句,clang3非常好用,错误提示非常人性化,谁用谁知道。
新增了两个函数ou_create()和ou_unlink(),分别用于创建和删除文件;增加了一个全局变量entries用于保存所有的entry;ou_readdir()和ou_getattr()也做了相应的修改。由于这些函数相当于实现VFS中的接口,因此不能在错误时返回-1了事,而是要根据不同的错误类型返回不同的值。关于错误代码头文件为errno.h,值和含义在/usr/include/asm-generic/errno-base.h和/usr/include/asm-generic/errno.h有定义。
编译挂载后试试执行”touch /tmp/mnt/abc”,然后ls看看是否多了一个文件abc?
其它
参考资料[1]中有一个helloworld的例子,参考资料[2]是一个FUSE教程,有兴趣的可以看看;关于FUSE的API可以参考头文件,所有接口的说明都在里面。
参考资料
[1] Filesystem in Userspace
[2] Writing a FUSE Filesystem: a Tutorial
[3] FUSE Design Doc version 0.6
附录
程序中用到的list.h(相关原理可以参考这里):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | #ifndef __LIST_H__ #define __LIST_H__ /* doubly linked list implementation from linux kernel */ #define offset_of(type, member) \ ((unsigned long)(&(((type*)0)->member))) #define container_of(ptr, type, member) \ ((type*)((unsigned long)(ptr) - offset_of(type, member))) #define list_entry(ptr, type, member) container_of(ptr, type, member) struct list_node { struct list_node *prev, *next; }; static inline void list_init(struct list_node* list) { list->prev = list; list->next = list; } static inline void list_add_prev(struct list_node* node, struct list_node* next) { node->next = next; node->prev = next->prev; next->prev = node; node->prev->next = node; } static inline void __list_del(struct list_node* node) { node->prev->next = node->next; node->next->prev = node->prev; } static inline void list_del(struct list_node* node) { __list_del(node); list_init(node); } #define list_for_each(p, head) \ for ((p) = (head)->next; (p) != (head); (p) = (p)->next) #define list_for_each_safe(p, n, head) \ for ((p) = (head)->next, (n) = (p)->next; \ (p) != (head); \ (p) = (n), (n) = (p)->next) #endif |
你好,问个问题哈?
我用你的代码一直编译不成功,这是什么原因?
有一大版错误, 在libio.h stdio.h 里面,还有在 像 fuse_fill_dir_t 很多声明也没找到…
ubuntu12.10 gcc 编译~~~
先把依赖都装上
sudo apt-get install libfuse-dev fuse-utils build-essential make
编译时要把与fuse相关的编译选项都加上(参考上面的Makefile)
已经弄好了~~谢谢哈~~主要是相应的相关编译项有些闹不明白还要再看看,自己直接GCC了~~~T_T