美文网首页我用 LinuxLinux学习|Gentoo/Arch/FreeBSDLinux学习之路
从0到1写一个自己的文件系统-写一个简单的文件系统(2)

从0到1写一个自己的文件系统-写一个简单的文件系统(2)

作者: 东京的雨不会淋湿首尔 | 来源:发表于2019-12-08 16:33 被阅读0次

    1.前言

    首先,我们需要了解fuse的启动。
    这里用官方源码/example/hello做示例。
    在编译完成之后我们会得到hello.o的可执行文件,
    使用./hello -h可以看到一大堆选项

    usage: ./hello[options] <mountpoint>
    
    FUSE options:
        -h   --help            print help
        -V   --version         print version
        -d   -o debug          enable debug output (implies -f)
        -f                     foreground operation
        -s                     disable multi-threaded operation
        -o clone_fd            use separate fuse device fd for 
    .....
    

    其中需要注意的是:

    • option是可选选项,较为常用的有:
      -d debug 模式,输出debug信息用于调试
      -f 前台运行,-f -d一起用的话就可以随时看见debug信息输出
    • mountpoint 挂载点,将你的文件系统挂载到哪一个目录下面。

    再来看看上一节写的 fuse_main 函数

    fuse_main(argc, argv, Operations(), nullptr);

    其中argc, argv就对应传入的option 和 mountpoint。

    需要理解的另一个点,假设我们的文件系统挂载点是在/tmp/user,那么我们实际操作的地方是在哪里呢?这个地方千万不能再是挂载点。因为我们在挂载点进行各种操作的时候,fuse会将各种操作映射到我们的处理函数,如果我们的处理函数中的操作位置还是在挂载点,那么又会继续调用我们的处理函数,从而变成死循环了。

    image.png

    2.编写函数

    所以,我们实际操作的位置不能是挂载点。
    我们先定义一个映射的位置static const string root_path = "/home/ubuntu/hello";那么挂载点中所有的操作都会映射到这个目录。
    还学要解决一个问题就是我们的函数中传入的目录实际上都是相对于挂载点的路径:
    比如:/挂载点/b/a.txt
    函数实际接收到的路径是/b/a.txt
    但是我们实际的操作目录是在/home/ubuntu/hello,因此需要对目录做一个转化。将/挂载点/b/a.txt转换为/home/ubuntu/hello/b/a.txt,这一步很简单,定义一个fullpath函数如下:

    static const string root_path = "/home/ubuntu/hello";
    string fullpath(const char *path) {
        string fpath = root_path + path;
        return fpath;
    }
    

    1)编写bcsfs_getattr

    这个函数主要是用来获取各种属性信息,简单记get attributes。

    这一步就是要去调用实际的linux调用,将实际的信息传递给fuse文件系统。流程如下:

    • /挂载点执行 ls
    • fuse调起我们自定义的bcsfs_getattr函数,传入的path是相对于挂载点,也就是/
    • 调用lstat函数获取真正的目录(/home/ubuntu/hello)信息,返回给fuse,实际上也就想当于在/home/ubuntu/hello下执行了ls命令
    int BCSFS::bcsfs_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi) {
        int res = 0;
        memset(stbuf, 0, sizeof(struct stat));//初始化stbuf
        puts("#getattr call");
        puts(path);
        string fpath = fullpath(path); //获取真实路径
        res = lstat(fpath.c_str(), stbuf);//填充结构体
        if(res<0) res=-errno;
        return res;
    }
    

    参数path为相对路径,stbuf是stat结构体,fi一般是NULL(fuse.h有说明)

    2)编写bcsfs_opendir

    同理 opendir 就是要去对应的 dir 调用 opendir 返回信息。opendir函数返回的打开的文件夹句柄,需要填充到fi->fh字段。(fh可以去fuse_common.h查看,如下:

        /** File handle id.  May be filled in by filesystem in create,
         * open, and opendir().  Available in most other file operations on the
         * same file handle. */
        uint64_t fh;
    
    int BCSFS::bcsfs_opendir(const char *path, struct fuse_file_info *fi) {
        puts("#opendir call");
        DIR *dir;
        string fpath = fullpath(path);
        dir = opendir(fpath.c_str());
        if (dir == nullptr) {
            return -errno;
        }
        fi->fh = reinterpret_cast<uint64_t>(dir);
    
        return 0;
    }
    

    3)编写bcsfs_readdir

    readdir 的写法也是类似,不过需要注意的是,目录中可能含有多个条目,因此我们需要将所有的条目全部返回给fuse。
    关于目录可能用到的结构体可以参考:https://www.cnblogs.com/jikexianfeng/p/7084911.html

    int BCSFS::bcsfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi, enum fuse_readdir_flags) {
        puts("#bcsfs_readdir call");
        DIR *dir;
        struct dirent *de;
        dir = reinterpret_cast<DIR *>(fi->fh);
    
        while ((de = readdir(dir))!= nullptr){
            int ret = filler(buf,de->d_name, nullptr,0,FUSE_FILL_DIR_PLUS);
            if(ret!=0){
                return -errno;
            }
        }
    
        return 0;
    }
    

    目录文件(directory file)的概念:这种文件包含了其他文件的名字以及指向与这些文件有关的信息的指针。
    dirent 结构体:

    struct dirent
    {
      long d_ino; /* inode number 索引节点号 */
        off_t d_off; /* offset to this dirent 在目录文件中的偏移 */
        unsigned short d_reclen; /* length of this d_name 文件名长 */
        unsigned char d_type; /* the type of d_name 文件类型 */
        char d_name [NAME_MAX+1]; /* file name (null-terminated) 文件名,最长255字符 */
    }
    

    filler函数的官方解释如下:

    /** Function to add an entry in a readdir() operation
     *
     * The *off* parameter can be any non-zero value that enables the
     * filesystem to identify the current point in the directory
     * stream. It does not need to be the actual physical position. A
     * value of zero is reserved to indicate that seeking in directories
     * is not supported.
     * 
     * @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
     * @param flags fill flags
     * @return 1 if buffer is full, zero otherwise
     */
    

    是用来在readdir操作中添加entry(条目)的。

    4)编写bcsfs_releasedir

    顾名思义:释放目录,也就是释放资源。只需要调用closedir关闭dir即可。

    int BCSFS::bcsfs_releasedir(const char * path, struct fuse_file_info * fi) {
        puts("#bcsfs_releasedir call");
        string fpath = fullpath(path);
        int ret = closedir(reinterpret_cast<DIR *>(fi->fh));
        if(ret<0){
            return -errno;
        }
        return ret;
    }
    

    3.测试

    我们添加一个main函数用于测试:

    #include "BCSFS.h"
    
    int main(int argc, char *argv[]) {
        BCSFS fs;
        fs.run(argc,argv);
        return 0;
    }
    

    添加makefile文件:

    PROG=BCSFS
    OBJDIR=.obj
    CC=g++
    
    CFLAGS = -Wall --std=c++14 `pkg-config fuse3 --cflags` -I..
    LDFLAGS = `pkg-config fuse3 --libs`
    
    $(shell mkdir -p $(OBJDIR))
    
    OBJS = $(OBJDIR)/BCSFS.o $(OBJDIR)/main.o
    
    $(PROG) : $(OBJS)
        $(CC) $(OBJS) $(LDFLAGS) -o $(PROG)
    
    -include $(OBJS:.o=.d)
    
    $(OBJDIR)/%.o: %.cpp
        $(CC) -c $(CFLAGS) $*.cpp -o $(OBJDIR)/$*.o
        $(CC) -MM $(CFLAGS) $*.cpp > $(OBJDIR)/$*.d
        @mv -f $(OBJDIR)/$*.d $(OBJDIR)/$*.d.tmp
        @sed -e 's|.*:|$(OBJDIR)/$*.o:|' < $(OBJDIR)/$*.d.tmp > $(OBJDIR)/$*.d
        @sed -e 's/.*://' -e 's/\\$$//' < $(OBJDIR)/$*.d.tmp | fmt -1 | \
          sed -e 's/^ *//' -e 's/$$/:/' >> $(OBJDIR)/$*.d
        @rm -f $(OBJDIR)/$*.d.tmp
    
    clean:
        rm -rf $(PROG) $(OBJDIR)
    
    
    

    执行编译:

    make
    

    运行:

    ./BCSFS -d -f /挂载点
    

    为了测试我们先在/home/ubuntu/hello目录下创建一个文件touch /home/ubuntu/hello/file1
    运行文件系统之后我们在挂载点执行ls

    ls -l /挂载点
    

    可以看到结果输出file1。

    ubuntu@Javascript:~$ ls -l mountdir/
    total 0
    -rw-r--r-- 1 ubuntu ubuntu 0 Dec  7 15:51 file1
    

    相关文章

      网友评论

        本文标题:从0到1写一个自己的文件系统-写一个简单的文件系统(2)

        本文链接:https://www.haomeiwen.com/subject/zlvugctx.html