使用Fuse编写文件系统

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操作:

#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:

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里的两个函数指针:

/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
 */
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。

创建/删除文件

接下来让我们扩充上面的程序,增加创建/删除普通文件的功能:

#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?

程序中用到的list.h(相关原理可以参考这里):
#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

原文地址:http://ouonline.net/building-your-own-fs-with-fuse-1


  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Fuse(Filesystem in Userspace)是一个允许非特权用户在用户空间中实现自己的文件系统的框架。Fuse可以允许用户创建虚拟文件系统,将不同的物理文件夹组合为单个文件夹等等。 在Linux系统中,Fuse可以通过Fuse API实现自定义文件系统Fuse API提供了一组C语言函数,可以实现文件系统的挂载、卸载、读写、文件创建和删除等基本操作。用户可以通过Fuse API编写自己的文件系统模块,然后将其挂载到本地文件系统中。 创建一个Fuse文件系统的基本步骤是: 1. 安装Fuse库和相关的开发工具。 2. 编写Fuse文件系统程序并通过gcc进行编译。 3. 执行Fuse文件系统程序,将其挂载到Linux本地文件系统中。 4. 通过系统的标准文件操作接口来使用Fuse文件系统。 下面是一个示例程序,它可以创建一个简单的Fuse文件系统: 1. 首先,安装Fuse应用程序和开发包。这里以Ubuntu为例: sudo apt-get install fuse libfuse-dev 2. 编写Fuse文件系统程序。下面的程序实现了一个简单的只读文件系统,它将远程的文件读取并映射到本地的文件系统中: ``` #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"; static int hello_getattr(const char *path, struct stat *stbuf) { int res = 0; memset(stbuf, 0, sizeof(struct stat)); if (strcmp(path, "/") == 0) { stbuf->st_mode = S_IFDIR | 0755; stbuf->st_nlink = 2; } else if (strcmp(path, hello_path) == 0) { stbuf->st_mode = S_IFREG | 0444; stbuf->st_nlink = 1; stbuf->st_size = strlen(hello_str); } else res = -ENOENT; return res; } 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; filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); filler(buf, hello_path + 1, NULL, 0); return 0; } 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; } 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, }; int main(int argc, char *argv[]) { return fuse_main(argc, argv, &hello_oper, NULL); } ``` 3. 编译程序: gcc -Wall hello.c -o hello `pkg-config fuse --cflags --libs` 4. 在本地文件系统中创建一个挂载点: mkdir /tmp/myfuse 5. 运行Fuse文件系统程序: ./hello /tmp/myfuse 6. 使用cat或其他标准文件操作接口,读取`/hello`文件: cat /tmp/myfuse/hello 输出:Hello World! Fuse文件系统提供了一种强大而灵活的方法来扩展和定制Linux文件系统,通过定制Fuse文件系统可以实现各种场景下的文件读写、解压缩、加密解密等操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值