0%

【Linux技术分享】Linux下fuse的实现原理

fuse实现原理

本文是对httpdirfs源码的一个学习,源码在https://github.com/fangfufu/httpdirfs.实现在linux系统下利用fuse技术,把http文件服务器挂载至本地路径.

fuse原理

  1. 流程介绍
    图1

    图中可以看出,fuse和ext3,ntfs都是文件系统模块.我们使用fuse实现文件系统并挂在至/tmp/fuse上,当我们对此目录执行ls命令时,内核的fuse从vfs中获取参数,调用我们自己实现ls的函数,得到结果再从vfs返回至ls.简单来说就是ls->fuse挂载文件夹->VFS->libfuse->自己实现的函数->结果返回至ls,demo的源码在https://github.com/MecryWork/Learn-C-to-implement-fuse

  2. 简单demo

    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
    #define FUSE_USE_VERSION 26

    #include <stdio.h>
    #include <string.h>
    #include <fuse.h>

    static int ou_re addir(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 = {//fuse设置响应函数
    .readdir = ou_readdir,
    .getattr = ou_getattr,
    };

    int main(int argc, char* argv[])
    {
    return fuse_main(argc, argv, &oufs_ops, NULL);
    }

    cmake编译成功后会看到生成的可执行文件 fusehello。建立一个挂载点 /tmp/mnt,然后运行

    ./fusehello /tmp/mnt
    成功后试试“ls /tmp/mnt”,就能看到一个文件“hello-world”。要调试的时候可以加上“-d”选项,这样就能看到 FUSE 和自己 printf 的调试输出。代码第一行指定了要使用的 FUSE API 版本。这里使用的是 2.6 版本。

  3. 简单demo2,实现创建/删除普通文件的功能,加了ou_create,和ou_unlink两个函数,在fuse上被调用.

    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
    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;
    }
  • 总结:具体还是使用的linux中的fuse的库文件,来实现我们自己的fuse文件系统,我们只需要参照fuse.h文件的函数指针.需要实现哪个功能,就开始自己进行实现,最后把结构体传入至fuse,就可以成功编写一个属于我们自己的文件系统了.

fuse+http的实现

利用fuse的机制,再获取http接口,实现把http文件系统的文件挂载至本机指定文件夹中,虽然此程序是用纯C编写的,但是我们已知具体实现功能,并了解部分fuse实现原理,所以我们只需要解读关键函数的实现就能大致了解实现流程

  1. link.c和link.h加载http中的Link中的路径转换至磁盘本地路径.

    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
    LinkTable *LinkTable_disk_open(const char *dirn)//从磁盘加载链接表
    {
    char *metadirn = path_append(META_DIR, dirn);
    char *path;
    //http中的路径进行选择截取
    if (metadirn[strnlen(metadirn, MAX_PATH_LEN)] == '/') {
    path = path_append(metadirn, ".LinkTable");
    } else {
    path = path_append(metadirn, "/.LinkTable");
    }
    //打开路径开始获取目标文件
    FILE *fp = fopen(path, "r");
    fprintf(stderr, "path<%s>\n",path);

    free(metadirn);

    if (!fp) {
    free(path);
    return NULL;
    }

    LinkTable *linktbl = CALLOC(1, sizeof(LinkTable));

    fread(&linktbl->num, sizeof(int), 1, fp);
    linktbl->links = CALLOC(linktbl->num, sizeof(Link *));
    //遍历link获取所需内容,并返回
    for (int i = 0; i < linktbl->num; i++) {
    linktbl->links[i] = CALLOC(1, sizeof(Link));
    fread(linktbl->links[i]->linkname, sizeof(char), MAX_FILENAME_LEN, fp);
    fread(linktbl->links[i]->f_url, sizeof(char), MAX_PATH_LEN, fp);
    fread(&linktbl->links[i]->type, sizeof(LinkType), 1, fp);
    fread(&linktbl->links[i]->content_length, sizeof(size_t), 1, fp);
    fread(&linktbl->links[i]->time, sizeof(long), 1, fp);
    if (feof(fp)) {
    /* reached EOF */
    fprintf(stderr,
    "LinkTable_disk_open(): reached EOF!\n");
    LinkTable_free(linktbl);
    LinkTable_disk_delete(dirn);
    return NULL;
    }
    if (ferror(fp)) {
    fprintf(stderr, "LinkTable_disk_open(): encountered ferror!\n");
    LinkTable_free(linktbl);
    LinkTable_disk_delete(dirn);
    return NULL;
    }
    }
    if (fclose(fp)) {
    fprintf(stderr,
    "LinkTable_disk_open(): cannot close the file pointer, %s\n",
    strerror(errno));
    }
    return linktbl;
    }
  2. network.c 和network.h,获取http文件服务器内容,详情请参考https://curl.haxx.se/libcurl/c/threaded-ssl.html这位作者也是参考此源码完成.

    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
    void NetworkSystem_init(void)
    {
    /* ------- Global related ----------*/
    //初始化http的curl,如果非CURL_GLOBAL_ALL则初始化失败退出
    if (curl_global_init(CURL_GLOBAL_ALL)) {
    fprintf(stderr, "network_init(): curl_global_init() failed!\n");
    exit_failure();
    }

    /* -------- Share related ----------*/
    //返回curl句柄,获取相应我curl
    CURL_SHARE = curl_share_init();
    if (!(CURL_SHARE)) {
    fprintf(stderr, "network_init(): curl_share_init() failed!\n");
    exit_failure();
    }

    //给目标穿参
    curl_share_setopt(CURL_SHARE, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
    curl_share_setopt(CURL_SHARE, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
    curl_share_setopt(CURL_SHARE, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
    //thread的初始化
    if (pthread_mutex_init(&curl_lock, NULL) != 0) {
    fprintf(stderr, "network_init(): curl_lock initialisation failed!\n");
    exit_failure();
    }
    curl_share_setopt(CURL_SHARE, CURLSHOPT_LOCKFUNC, curl_callback_lock);
    curl_share_setopt(CURL_SHARE, CURLSHOPT_UNLOCKFUNC, curl_callback_unlock);

    //处理多个curl
    /* ------------- Multi related -----------*/
    curl_multi = curl_multi_init();
    if (!curl_multi) {
    fprintf(stderr, "network_init(): curl_multi_init() failed!\n");
    exit_failure();
    }
    curl_multi_setopt(curl_multi, CURLMOPT_MAX_TOTAL_CONNECTIONS,
    CONFIG.max_conns);
    curl_multi_setopt(curl_multi, CURLMOPT_MAX_HOST_CONNECTIONS,
    CONFIG.max_conns);

    /* ------------ Initialise locks ---------*/
    if (pthread_mutex_init(&transfer_lock, NULL)) {
    fprintf(stderr,
    "network_init(): transfer_lock initialisation failed!\n");
    exit_failure();
    }
    crypto_lock_init();
    }
  3. fuse_local.c和fuse_local.h,搭建本地fuse文件系统,本文上半部分已经详细介绍原理.

    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
    static int fs_readdir(const char *path, void *buf, fuse_fill_dir_t dir_add,off_t offset, struct fuse_file_info *fi)
    {
    (void) offset;
    (void) fi;

    Link *link;
    LinkTable *linktbl;

    if (!strcmp(path, "/")) {
    linktbl = ROOT_LINK_TBL;
    } else {
    linktbl = path_to_Link_LinkTable_new(path);//如果非根目录,则从link中重新拉去当前目录所有文件夹,和文件从而实现文件夹的存储.缺点是要试试刷新多文件会有较高延迟
    if(!linktbl) {
    return -ENOENT;
    }
    }

    /* start adding the links */
    dir_add(buf, ".", NULL, 0);
    dir_add(buf, "..", NULL, 0);
    for (int i = 1; i < linktbl->num; i++) {
    link = linktbl->links[i];
    if (link->type != LINK_INVALID) {
    dir_add(buf, link->linkname, NULL, 0);
    }
    }

    return 0;
    }
  • 总结:通过http(curl库),和fuse(fuse库).点击当前文件夹就会从http拉去当前文件夹所有内容,从而实现了把http文件系统服务器挂在至本机磁盘中.makefile直接使用make生成可执行文件httpdirfs,然后调用如下命令即可使用

    ./httpdirfs -f –cache $URL $MOUNT_POINT