FUSE
一、FUSE简介
FUSE(用户空间文件系统)是这样一个框架,它使得FUSE用户在用户态下编写文件系统成为可能,而不必和内核打交道。FUSE由三个部分组成,linux内核模块、FUSE库以及mount 工具。用户关心的只是FUSE库和mount工具,内核模块仅仅提供kernel的接入口,给了文件系统一个框架,而文件系统本身的主要实现代码位于用户空间中。 FUSE库给用户提供了编程的接口,而mount工具则用于挂在用户编写的文件系统。
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来创建。
二、FUSE特性
- 库文件和 API简单,极大地方便了用户的使用
- 安装简便,不需要加补丁或者重新编译 kernel
- 执行安全,使用稳定
- 高效,相对于其它用户态文件系统实例
- 非特权用户可以使用
- 基于 linux2.4.x 和 2.6.x 内核,现在可以支持JavaTM 绑定,不必限定使用C和C++来编 写文件系统
三、源代码目录
- ./doc 包含FUSE相关文档
- ./include 包含了FUSE API头,对创建文件系统有用,主要用fuse.h
- ./lib 存放FUSE库的源代码
- ./util 包含了FUSE工具库的源代码
- ./example 参考的例子
四、安装
FUSE的源码安装类似于其他软件,只需要在FUSE的源码目录下执行如下命令即可:
./configure
make
make install(以root身份执行)
五、FUSE operations
FUSE使用fuse_operations来给用户提供编程结构,让用户通过注册自己编写的函数到该结构体来实现自己的文件系统。
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);
};
六、hello示例文件系统源码分析
FUSE在源码目录example下有一些示例文件系统,通过阅读这些示例文件系统可以掌握FUSE用户态文件系统的编写规范。下面以hello.c为例分析FUSE的编写规范:
#define FUSE_USE_VERSION 26
#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
static const char *hello_str = "Hello World!\n";
static const char *hello_path = "/hello";
/该函数与stat()类似,用于得到文件的属性,将其存入到结构体struct stat中/
static int hello_getattr(const char *path, struct stat *stbuf)
{
int res = 0;
memset(stbuf, 0, sizeof(struct stat)); //用于初始化结构体stat
if (strcmp(path, "/") == 0) {
stbuf->st_mode = S_IFDIR | 0755; //S_IFDIR 用于说明/为目录,详见S_IFDIR定义
stbuf->st_nlink = 2; //文件链接数
} else if (strcmp(path, hello_path) == 0) {
stbuf->st_mode = S_IFREG | 0444; //S_IFREG用于说明/hello为常规文件
stbuf->st_nlink = 1;
stbuf->st_size = strlen(hello_str); //设置文件长度为hello_str的长度
} else
res = -ENOENT; //返回错误信息,没有该文件或目录
return res; //执行成功返回0
}
/该函数用于读取/目录中的内容,并在/目录下增加了. .. hello三个目录项/
static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi)
{
(void) offset;
(void) fi;
if (strcmp(path, "/") != 0)
return -ENOENT;
/*
fill的定义:
typedef int (*fuse_fill_dir_t) (void *buf, const char *name,const struct stat *stbuf, off_t off);
其作用是在readdir函数中增加一个目录项
*/
filler(buf, ".", NULL, 0);
//在/目录下增加. 这个目录项
filler(buf, "..", NULL, 0);
// 增加.. 目录项
filler(buf, hello_path + 1, NULL, 0);
//增加hello目录项
return 0;
}
/用于打开hello文件/
static int hello_open(const char *path, struct fuse_file_info *fi)
{
if (strcmp(path, hello_path) != 0)
return -ENOENT;
if ((fi->flags & 3) != O_RDONLY)
return -EACCES;
return 0;
}
/读取hello文件时的操作,它实际上读取的是字符串hello_str的内容/
static int hello_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
size_t len;
(void) fi;
if(strcmp(path, hello_path) != 0)
return -ENOENT;
len = strlen(hello_str);
if (offset < len) {
if (offset + size > len)
size = len - offset;
memcpy(buf, hello_str + offset, size);
} else
size = 0;
return size;
}
/注册上面定义的函数/
static struct fuse_operations hello_oper = {
.getattr = hello_getattr,
.readdir = hello_readdir,
.open = hello_open,
.read = hello_read,
};
/用户只需要调用fuse_main(),剩下的事就交给FUSE了/
int main(int argc, char *argv[])
{
return fuse_main(argc, argv, &hello_oper, NULL);
}
终端运行:
~/fuse/example$ mkdir /tmp/fuse //在/tmp下建立fuse目录,用于挂载hello文件系统
~/fuse/example$ ./hello /tmp/fuse //挂载hello文件系统
~/fuse/example$ ls -l /tmp/fuse //执行ls时,会调用到readdir函数,该函数会添加一个hello 文件
总用量 0
-r--r--r-- 1 root root 13 1970-01-01 07:00 hello
~/fuse/example$ cat /tmp/fuse/hello //执行cat hello时,会调用open以及read函数,将
Hello World! 字符串hello_str中的内容读出
~/fuse/example$ fusermount -u /tmp/fuse //卸载hello文件系统
通过上述的分析可以知道,使用FUSE必须要自己实现对文件或目录的操作,系统调用也会最终调用到用户自己实现的函数。用户实现的函数需要在结构体fuse_operations中注册。而在main()函数中,用户只需要调用fuse_main()函数就可以了,剩下的复杂工作可以交给FUSE。