Linux Filesystem in Userspace(FUSE,用户空间文件系统)

1. FUSE(Filesystem in Userspace)简介
1.1. 概述
FUSE(用户空间文件系统)作为类UNIX系统平台上可加载的内核模块,允许非特权用户创建功能完备的文件系统,而不需要重新编译内核。FUSE模块仅 仅提供kernel模块的接入口,而本身的主要实现代码位于用户空间中。对于读写虚拟文件系统来说,FUSE是个很好的选择。
FUSE起初是为了研究AVFS(A Virtual Filesystem)而设计的,而现在已经成为SourceForge的一个独立项目,目前适用的平台有Linux, FreeBSD, NetBSD, OpenSolaris和Mac OS X。官方的linux kernel版本到2.6.14才添加了FUSE模块,因此2.4的内核模块下,用户如果要在FUSE中创建一个文件系统,需要先安装一个FUSE内核模 块,然后使用FUSE库和API来创建。
1.2. FUSE具有的特点如下:
 库文件简单
 安装简便,不需要加补丁或者重新编译kernel
 执行安全,使用稳定
 实现用户空间——kernel接口高效
 非特权用户可以使用
 基于linux2.4.x和2.6.x内核,现在可以支持JavaTM 绑定,不必限定使用C和C++来编写文件系统
1.3. 支持的特殊文件系统
NTFS-3G, GlusterFS, SSHFS, GmailFS, EncFS…
1.4. FUSE商业实现与学术实现
 LUFS是一个混合用户空间的文件系统框架,对用于任何应用程序无数的文件系统提供透明支持。大部分LUFS系统包括一个内核模块和一个用户空间守护进程,将大部分VFS调用都委托个一个专用的守护进程处理
 UserFS让用户文件系统可以像普通文件系统一样加载
 Ufo Project是为Solaris提供的一个全局文件系统,允许用户将远程文件真正当作本地文件一样对待
 OpenAFS是Andrew FileSystem的一个开源版本
 CIFS是Common Internet FileSystem的简称
1.5. FUSE展开
 ./doc包含FUSE相关文档
 ./kernel包含了FUSE内核模块的源代码
 ./include包含了FUSE API头,对创建文件系统有用,主要用fuse.h
 ./lib中存放FUSE库的源代码
 ./util包含了FUSE工具库的源代码
 ./example参考的例子
2. FUSE的安装
可以在FUSE主页上下载稳定的开源源代码(http://fuse.sourceforge.net/),由于编译内核兼容的原因,我下载的是fuse-2.4.2。
2.1. 安装办法1
在linux服务器上安装:
1. 解压包:tar –zxvf fuse-2.4.2
2. 在fuse目录中运行configure脚本:. /configure,这会创建所需要的makefile等文件。configure后还可以加一些选项参数,通过./configure –h(help)可以看到相关设置。如果需要编译内核,请加上./configure –enable-kernel-module,如果你的内核版本低于2.6.14的话,这项操作就是必须的啦。
3. 运行./make来编译库、二进制文件和内核模块。如果编译了FUSE kernel模块,可以看到./kernel/fuse.ko(fuse.o)。lib目录下有fuse.o、mount.o和helper.o。
4. 运行./make install完成FUSE的安装
2.2. 安装办法2
在我最近从事的项目中,涉及到ntfs-3g,而ntfs-3g.soureforge上说明不需要安装FUSE相关库文件及组件,于是将kernel部分移植进了avs730内核模块。
1. 将FUSE内核源代码拷贝到linux-2.4.x\fuse中
2. 拷贝FUSE相关头文件到fuse目录
3. 编写makefile:

4. 修改系统内核文件系统下的makefile,添加fuse.o模块编译链接
5. 重新编译内核
3. FUSE流程
3.1. FUSE Userspace
3.1.1. 具体流程
fuse_main() (lib/helper.c)——fuse用户空间主函数,用户程序调用它时,fuse_main()函数解析相关参数(如 mountpoint,multithreaded),并调用fuse_mount()函数。调用fuse_new()函数,为fuse文件系统数据分配 存储空间。调用fuse_loop()函数实现会话的接受与处理。
fuse_mount() (lib/mount.c)——创建UNIX本地套接口,创建并运行子进程fusermount。并返回fuse模块文件fd给fuse_main()函数。
fusermount (util/fusermount.c)——确保fuse模块已经加载,通过UNIX套接口返回fuse模块的文件fd给fuse_mount()函数。
fuse_new() (lib/fuse.c)——为fuse创建数据结构空间,用来存储文件系统数据。
fuse_loop() (lib/fuse.c)( fuse_loop_mt() (lib/fuse_mt.c))——从/dev/fuse 读取文件系统调用,调用fuse_operations结构中的处理函数,返回调用结果给/dev/fuse
具体流程图如图表 1 fuse 用户空间流程图:

3.1.2. fuse_operation结构
struct fuse_operations {
int (*getattr) (const char *, struct stat *);
int (*readlink) (const char *, char *, size_t);
int (*mknod) (const char *, mode_t, dev_t);
int (*mkdir) (const char *, mode_t);
int (*unlink) (const char *);
int (*rmdir) (const char *);
int (*symlink) (const char *, const char *);
int (*rename) (const char *, const char *);
int (*link) (const char *, const char *);
int (*chmod) (const char *, mode_t);
int (*chown) (const char *, uid_t, gid_t);
int (*truncate) (const char *, off_t);
int (*utime) (const char *, struct utimbuf *);
int (*open) (const char *, struct fuse_file_info *);
int (*read) (const char *, char *, size_t, off_t, struct fuse_file_info *);
int (*write) (const char *, const char *, size_t, off_t, struct fuse_file_info *);
int (*statfs) (const char *, struct statvfs *);
int (*flush) (const char *, struct fuse_file_info *);
int (*release) (const char *, struct fuse_file_info *);
int (*fsync) (const char *, int, struct fuse_file_info *);
int (*setxattr) (const char *, const char *, const char *, size_t, int);
int (*getxattr) (const char *, const char *, char *, size_t);
int (*listxattr) (const char *, char *, size_t);
int (*removexattr) (const char *, const char *);
int (*opendir) (const char *, struct fuse_file_info *);
int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *);
int (*releasedir) (const char *, struct fuse_file_info *);
int (*fsyncdir) (const char *, int, struct fuse_file_info *);
void *(*init) (struct fuse_conn_info *conn);
void (*destroy) (void *);
int (*access) (const char *, int);
int (*create) (const char *, mode_t, struct fuse_file_info *);
int (*ftruncate) (const char *, off_t, struct fuse_file_info *);
int (*fgetattr) (const char *, struct stat *, struct fuse_file_info *);
int (*lock) (const char *, struct fuse_file_info *, int cmd, struct flock *);
int (*utimens) (const char *, const struct timespec tv[2]);
int (*bmap) (const char *, size_t blocksize, uint64_t *idx);
};
这些操作并非都是必需的,但是一个文件系统要正常工作,就需要其中很多函数。读者可以实现一个具有特殊目的的.flush、.release或者.fsync方法的功能完备的文件系统。
下面是fuse_operation的具体介绍:
 getattr: int (*getattr) (const char *, struct stat *);
这个函数与 stat() 类似。st_dev 和 st_blksize 域都可以忽略。st_ino 域也会被忽略,除非在执行 mount 时指定了 use_ino 选项。
 readlink: int (*readlink) (const char *, char *, size_t);
这个函数会读取一个符号链接的目标。缓冲区应该是一个以 null 结束的字符串。缓冲区的大小参数包括这个 null 结束字符的空间。如果链接名太长,不能保存到缓冲区中,就应该被截断。成功时的返回值应该是 “0”。
 getdir: int (*getdir) (const char *, fuse_dirh_t, fuse_dirfil_t);
这个函数会读取一个目录中的内容。这个操作实际上是在一次调用中执行 opendir()、readdir()、…、closedir() 序列。对于每个目录项来说,都应该调用 filldir() 函数。
 mknod: int (*mknod) (const char *, mode_t, dev_t);
这个函数会创建一个文件节点。此处没有 create() 操作;mknod() 会在创建非目录、非符号链接的节点时调用。
 mkdir: int (*mkdir) (const char *, mode_t);
 rmdir: int (*rmdir) (const char *);
这两个函数分别用来创建和删除一个目录。
 unlink: int (*unlink) (const char *);
 rename: int (*rename) (const char *, const char *);
这两个函数分别用来删除和重命名一个文件。
 symlink: int (*symlink) (const char *, const char *);
这个函数用来创建一个符号链接。
 link: int (*link) (const char *, const char *);
这个函数创建一个到文件的硬链接。
 chmod: int (*chmod) (const char *, mode_t);
 chown: int (*chown) (const char *, uid_t, gid_t);
 truncate: int (*truncate) (const char *, off_t);
 utime: int (*utime) (const char *, struct utimbuf *);
这 4 个函数分别用来修改文件的权限位、属主和用户、大小以及文件的访问/修改时间。
 open: int (*open) (const char *, struct fuse_file_info *);
这是文件的打开操作。对 open() 函数不能传递创建或截断标记(O_CREAT、O_EXCL、O_TRUNC)。这个函数应该检查是否允许执行给定的标记的操作。另外,open() 也可能在 fuse_file_info 结构中返回任意的文件句柄,这会传递给所有的文件操作。
 read: int (*read) (const char *, char *, size_t, off_t, struct fuse_file_info *);
这个函数从一个打开文件中读取数据。除非碰到 EOF 或出现错误,否则 read() 应该返回所请求的字节数的数据;否则,其余数据都会被替换成 0。一个例外是在执行 mount 命令时指定了 direct_io 选项,在这种情况中 read() 系统调用的返回值会影响这个操作的返回值。
 write: int (*write) (const char *, const char *, size_t, off_t, struct fuse_file_info *);
这个函数将数据写入一个打开的文件中。除非碰到 EOF 或出现错误,否则 write() 应该返回所请求的字节数的数据。一个例外是在执行 mount 命令时指定了 direct_io 选项(这于 read() 操作的情况类似)。
 statfs: int (*statfs) (const char *, struct statfs *);
这个函数获取文件系统的统计信息。f_type 和 f_fsid 域都会被忽略。
 flush: int (*flush) (const char *, struct fuse_file_info *);
这表示要刷新缓存数据。它并不等于 fsync() 函数 —— 也不是请求同步脏数据。每次对一个文件描述符执行 close() 函数时,都会调用 flush();因此如果文件系统希望在 close() 中返回写错误,并且这个文件已经缓存了脏数据,那么此处就是回写数据并返回错误的好地方。由于很多应用程序都会忽略 close() 错误,因此这通常用处不大。
注意:我们也可以对一个 open() 多次调用 flush() 方法。如果由于调用了 dup()、dup2() 或 fork() 而产生多个文件描述符指向一个打开文件的情况,就可能会需要这种用法。我们无法确定哪个 flush 操作是最后一次操作,因此每个 flush 都应该同等地对待。多个写刷新序列相当罕见,因此这并不是什么问题。
 release: int (*release) (const char *, struct fuse_file_info *);
这个函数释放一个打开文件。release() 是在对一个打开文件没有其他引用时调用的 —— 此时所有的文件描述符都会被关闭,所有的内存映射都会被取消。对于每个 open() 调用来说,都必须有一个使用完全相同标记和文件描述符的 release() 调用。对一个文件打开多次是可能的,在这种情况中只会考虑最后一次 release,然后就不能再对这个文件执行更多的读/写操作了。release 的返回值会被忽略。
 fsync: int (*fsync) (const char *, int, struct fuse_file_info *);
这个函数用来同步文件内容。如果 datasync 参数为非 0,那么就只会刷新用户数据,而不会刷新元数据。
 setxattr: int (*setxattr) (const char *, const char *, const char *, size_t, int);
 getxattr: int (*getxattr) (const char *, const char *, char *, size_t);
 listxattr: int (*listxattr) (const char *, char *, size_t);
 removexattr: int (*removexattr) (const char *, const char *);
这些函数分别用来设置、获取、列出和删除扩展属性。

3.2. FUSE Kernel 模块
FUSE Kernel模块由两部分组成:
proc文件系统组件:Kernel/dev.c——回应io请求到/dev/fuse。fuse_dev_read()函数负责读出文件,并将来自 “list of request”结构体的命令返回到调用函数。fuse_dev_write ()负责文件写入,并将写入的数据置放到“req→out”数据结构中。
文件系统调用部分:kernel/file.c,kernel/inode.c,kernel/dir.c——调用request_send(),将请求加入到“list of request”结构体中,等待回复(reply)。
下表将举例说明在FUSE中文件系统的操作过程:
表格 1 unlink 在FUSE中的操作过程

3.2.1. fc的结构
struct fuse_conn {
…………………………
/** Readers of the connection are waiting on this */
wait_queue_head_t waitq;
/** The list of pending requests */
struct list_head pending;
/** The list of requests being processed */
struct list_head processing;
…………………………
};

4. NTFS文件系统实现
4.1. LINUX文件系统结构
文件系统是一个存储设备上的数据和元数据进行组织的机制。Linux文件系统接口实现为分层的体系结构,从而将用户接口层、文件系统实现和操作存储设备的驱动程序分隔开。图表2所示体系结构显示了用户空间和内核中与文件系统相关的组要组件之间的关系:

图表 2 Linux文件系统组件的体系结构
用户空间包含一些应用程序和GNU C库,它们为系统调用(打开、读取、写和关闭)提供用户接口。系统调用接口的作用就像是交换器,它将系统调用从用户空间发送到内核空间中的适当端点。 VFS是底层文件系统的主要接口。这个组件导出一组接口,然后将他们抽象到各个文件系统,各个文件系统的行为可能差异很大。inode和dentry是两 个针对文件系统对象的缓存,它们缓存最近使用过的文件系统对象。
每个文件系统实现导出一组通用接口,供VFS使用。缓冲区缓存会缓存文件系统和相关块设备之间的请求。
4.2. NTFS文件系统操作的实现

图表 3 NTFS-FUSE文件系统结构
在ntfs-3g的源代码中已经内嵌了fuse library的相关组件,集中为libfuse。其中提供了Fuse Userspace的功能。于是ntfs-3g的libfuse部分就在挂载的时候建立fuse相关结构体,提供内存空间存储文件系统数据,挂载后,返回 句柄fh给fuse_loop()。当有某个客户端的请求时,FUSE 内核模块将请求传送到/dev/fuse文件中,此时fuse_loop()读取到与fh相应的请求,立即调用fuse_operation进行处理,然 后将处理结果(response)返回到客户端,这样就完成了单个请求的通信过程。
另外,通过图表3可以知道,Fuse内核模块与ntfs-3g的通信的接口,准确的说是Fuse内核模块与内嵌于ntfs-3g的libfuse的通信接 口是一个特殊的文件/dev/fuse,内核模块将客户端的请求送到/dev/fuse并接受处理信息,返回到客户端;而用户空间的libfuse则读取 与/dev/fuse文件句柄,接受系统调用信息后,调用fuse_loop()进行相应的处理,再将处理结果返回给/dev/fuse。
5. 小结
本文主要介绍了Filesystem in Userspace(FUSE)的特点、结构,以及在LINUX平台上安装的方法,另外将FUSE分为两大模块——Userspace模块和Kernel 模块,进行讲解,并选用Linux ntfs文件系统实现的实例分析了FUSE的流程。

发表评论?

1 条评论。

发表评论

Trackbacks and Pingbacks: