fuse文件系统

fuse文件系统

跟着简叔学的,可以B站搜索 简说linux

1. 基于fuse实现的文件系统

用户空间文件系统(Filesystem in Userspace),是Linux 中用于挂载某些网络空间,如SSH,到本地文件系统的模块,在SourceForge上可以找到相关内容。fuse文件系统依赖底层libfuse库来实现。

内核驱动在编写的时候调用内核的一些函数和头文件,编译的时候需要编译到内核空间里边去运行,通过insmod的方式加载到内核空间里边去运行。而用户空间驱动是运行在用户空间的,就像开发普通的应用程序,更加灵活但是有一些底层的功能支持并不完整。比如中断,锁等机制。之所以有用户空间驱动是因为内核驱动的功能比较固定而用户的需求又比较多变。

以SSH服务为例,我们正常可以通过ssh连接远端去访问文件资源,也可以通过fuse文件系统的方式把远端目录挂载到本地的文件目录。

1678260671375

1678272408329

使用sshfs命令 ,可以将远端的某目录挂载到本地的目录。VFS是Linux文件系统对外的接口。任何要使用文件系统的程序都必须经由这层接口来使用它。如果VFS发现访问的是fuse文件系统,就把请求交给/dev/fuse驱动去处理,主要是做一个中转,最终实现是交由libfuse去实现请求的处理逻辑。

1678261083674

1678272204252

2.libfuse代码阅读

2.1试着运行hello这个最简单的文件系统

所谓的“用户态文件系统”,是指一个文件系统的data(内容)和metadata(描述文件的数据)都是由用户态的进程提供的(这种进程被称为“daemon”)。datametadata通过这两个东西才能完整的描述一个文件或者目录。

先下载libfuse库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
git clone https://github.com/libfuse/libfuse.git
cd libfuse
mkdir build; cd build
meson ..
//如果提示没有meson的话
apt install meson
//如果meson提示Did not find pkg-config by name 'pkg-config'的话
apt-get install pkg-config

ninja
//如果/usr/bin/python3: No module named pytest
apt install python3-pip
pip3 install pytest -i https://pypi.tuna.tsinghua.edu.cn/simple
python3 -m pytest test/

1678263392378

1
2
##如果报错这个问题就需要修改根目录的权限
chmod O+rx ...

问题解决之后继续执行

1
ninja install

注意上述命令都是在build下执行的然后进入到build/example目录下,

1
./hello + 要挂载文件系统的目录

比如1678264933414

查看挂载信息

1678265272054

之所以有这么多条是因为当时胡乱操作了一通,可以看到类型都是 fuse.hello 正常的文件系统类型都是nsfs什么的,内核里面通过fuse关键字匹配fuse文件系统,进而在匹配我们自定义的hello文件系统。

2.2hello文件系统的实现

去libfuse/example/hello.c查看

  1. 首先定义了options结构体,然后进行一系列初始化。定义了操作文件的方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    static struct options {
    const char *filename;
    const char *contents;
    int show_help;
    } options;
    #define OPTION(t, p) \
    { t, offsetof(struct options, p), 1 }
    static const struct fuse_opt option_spec[] = {
    OPTION("--name=%s", filename),
    OPTION("--contents=%s", contents),
    OPTION("-h", show_help),
    OPTION("--help", show_help),
    FUSE_OPT_END
    };
  2. 定义文件的一系列操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    off_t offset, struct fuse_file_info *fi,
    enum fuse_readdir_flags flags)
    {
    (void) offset;
    (void) fi;
    (void) flags;

    //如果是根目录就返回错误?
    if (strcmp(path, "/") != 0)
    return -ENOENT;

    //否则就. .. 文件的名字返回回去
    filler(buf, ".", NULL, 0, 0);
    filler(buf, "..", NULL, 0, 0);
    filler(buf, options.filename, NULL, 0, 0);

    return 0;
    }

    1678271691547

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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+1, options.filename) != 0)
    return -ENOENT;

    len = strlen(options.contents); //对应的就是helloworld这个字符串的基址
    if (offset < len) {
    if (offset + size > len)
    size = len - offset;
    memcpy(buf, options.contents + offset, size);
    } else
    size = 0;

    return size;
    }

    1678271804532

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    static int hello_getattr(const char *path, struct stat *stbuf,
    struct fuse_file_info *fi)
    {
    (void) fi;
    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+1, options.filename) == 0) {
    stbuf->st_mode = S_IFREG | 0444;
    stbuf->st_nlink = 1;
    stbuf->st_size = strlen(options.contents);
    } else
    res = -ENOENT;

    return res;
    }

  3. main函数中,最重点的是fuse_main,相当于让我们之前定义的fuse_operations起作用struct fuse_operations可以对应内核文件系统的struct file_operations

    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
    int main(int argc, char *argv[])
    {
    int ret;
    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);

    /* Set defaults -- we have to use strdup so that
    fuse_opt_parse can free the defaults if other
    values are specified */
    options.filename = strdup("hello");
    options.contents = strdup("Hello World!\n");

    /* Parse options 解析参数 fuse提供的直接用就可 */
    if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
    return 1;

    /* When --help is specified, first print our own file-system
    specific help text, then signal fuse_main to show
    additional help (by adding `--help` to the options again)
    without usage: line (by setting argv[0] to the empty
    string) */
    if (options.show_help) {
    show_help(argv[0]);
    assert(fuse_opt_add_arg(&args, "--help") == 0);
    args.argv[0][0] = '\0';
    }


    ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);

    fuse_opt_free_args(&args);
    return ret;
    }

    static const struct fuse_operations hello_oper = {
    .init = hello_init,
    .getattr = hello_getattr,
    .readdir = hello_readdir,
    .open = hello_open,
    .read = hello_read,
    };

    fuse_operations结构体注释翻译

    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
    struct fuse_operations {
    int (*getattr) (const char *, struct stat *);
    /* 这个函数与 stat() 类似。st_dev 和 st_blksize 域都可以忽略。st_ino 域也会被忽略,除非在执行 mount 时指定了 use_ino 选项 */

    int (*readlink) (const char *, char *, size_t);
    /* 这个函数会读取一个符号链接的目标。缓冲区应该是一个以 null 结束的字符串。缓冲区的大小参数包括这个 null 结束字符的空间。如果链接名太长,不能保存到缓冲区中,就应该被截断。成功时的返回值应该是 “0” */

    int (*getdir) (const char *, fuse_dirh_t, fuse_dirfil_t);
    /* 这个函数会读取一个目录中的内容。这个操作实际上是在一次调用中执行 opendir()、readdir()、...、closedir() 序列。对于每个目录项来说,都应该调用 filldir() 函数 */

    int (*mknod) (const char *, mode_t, dev_t);
    /* 这个函数会创建一个文件节点。此处没有 create() 操作;mknod() 会在创建非目录、非符号链接的节点时调用 */

    int (*mkdir) (const char *, mode_t);
    int (*rmdir) (const char *);
    /* 这两个函数分别用来创建和删除一个目录 */

    int (*unlink) (const char *);
    int (*rename) (const char *, const char *);
    /* 这两个函数分别用来删除和重命名一个文件 */

    int (*symlink) (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 *);
    /* 这 4 个函数分别用来修改文件的权限位、属主和用户、大小以及文件的访问/修改时间 */

    int (*open) (const char *, struct fuse_file_info *);
    /* 这是文件的打开操作。对 open() 函数不能传递创建或截断标记(O_CREAT、O_EXCL、O_TRUNC)。这个函数应该检查是否允许执行给定的标记的操作。另外,open() 也可能在 fuse_file_info 结构中返回任意的文件句柄,这会传递给所有的文件操作 */
    int (*read) (const char *, char *, size_t, off_t, struct fuse_file_info *);
    /* 这个函数从一个打开文件中读取数据。除非碰到 EOF 或出现错误,否则 read() 应该返回所请求的字节数的数据;否则,其余数据都会被替换成 0。一个例外是在执行 mount 命令时指定了 direct_io 选项,在这种情况中 read() 系统调用的返回值会影响这个操作的返回值 */

    int (*write) (const char *, const char *, size_t, off_t,struct fuse_file_info *);
    /* 这个函数将数据写入一个打开的文件中。除非碰到 EOF 或出现错误,否则 write() 应该返回所请求的字节数的数据。一个例外是在执行 mount 命令时指定了 direct_io 选项(这于 read() 操作的情况类似) */

    int (*statfs) (const char *, struct statfs *);
    /* 这个函数获取文件系统的统计信息。f_type 和 f_fsid 域都会被忽略 */

    int (*flush) (const char *, struct fuse_file_info *);
    /* 这表示要刷新缓存数据。它并不等于 fsync() 函数 —— 也不是请求同步脏数据。每次对一个文件描述符执行 close() 函数时,都会调用 flush();因此如果文件系统希望在 close() 中返回写错误,并且这个文件已经缓存了脏数据,那么此处就是回写数据并返回错误的好地方。由于很多应用程序都会忽略 close() 错误,因此这通常用处不大 */

    int (*release) (const char *, struct fuse_file_info *);
    /* 这个函数释放一个打开文件。release() 是在对一个打开文件没有其他引用时调用的 —— 此时所有的文件描述符都会被关闭,所有的内存映射都会被取消。对于每个 open() 调用来说,都必须有一个使用完全相同标记和文件描述符的 release() 调用。对一个文件打开多次是可能的,在这种情况中只会考虑最后一次 release,然后就不能再对这个文件执行更多的读/写操作了。release 的返回值会被忽略 */

    int (*fsync) (const char *, int, struct fuse_file_info *);
    /* 这个函数用来同步文件内容。如果 datasync 参数为非 0,那么就只会刷新用户数据,而不会刷新元数据 */

    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 *);
    /* 这些函数分别用来设置、获取、列出和删除扩展属性 */
    ......
    };