您的位置:首页 > 其它

ceph存储 FUSE原理总结

2014-12-31 20:20 405 查看

1      
概述

Fuse是filesystem in user space,一个用户空间的文件系统框架,允许非特权用户建立功能完备的文件系统,而不需要重新编译内核。fuse模块仅仅提供内核模块的入口,而本身的主要实现代码位于用户空间中。对于读写虚拟文件系统来讲,fuse是个很好的选择。fuse包含包含一个内核模块和一个用户空间守护进程,将大部分的VFS调用都委托一个专用的守护进程来处理。
 

2      
工作原理

Fuse用户空间文件系统与真实的文件系统不同,它的supper block, indoe, dentry等都是由内存虚拟而来,具体在物理磁盘上存储的真实文件结构是什么,它不关心,且对真实数据的请求通过驱动和接口一层层传递到用户空间的用户编写的具体实现程序里来,这样就为用户开发自己的文件系统提供了便利,这也就是所谓的“用户空间文件系统”的基本工作理念。

2.1  
模块架构

FUSE分为三大模块:
Ø FUSE内核模块(内核态)
Ø LibFUSE模块(用户态)
Ø 用户程序模块(用户态)
用户程序在用户空间实现LibFUSE库封装的文件系统操作;
LibFUSE实现文件系统主要框架、对“用户实现的文件系统操作代码“的封装、mount管理、通过字符设备/dev/fuse与内核模块通信;
FUSE内核模块实现VFS接口(实现fuse文件驱动模块的注册、fuse 的(虚拟)设备驱动、提供supper block、dentry、inode的维护),接收来至后者的请求,传递给LibFUSE,LibFUSE再传递给我们用户程序的接口进行实现操作。
图一

2.1.1  
源代码结构及开发库

FUSE内核模块
kernel/inode.c   —> 主要完成fuse文件驱动模块的注册,提供对supper block的维护函数以及其它(驱动的组织开始文件)
kernel/dev.c     —> fuse 的(虚拟)设备驱动
kernel/control.c —> 提供对于dentry的维护及其它
kernel/dir.c   —> 主要提供对于目录inode索引节点的维护
kernel/file.c    —> 主要提供对于文件inode索引节点的维护
LibFUSE模块
lib/helper.c  —> “fuse_main()”调用的主入口
lib/fuse_kern_chan.c—>主要实现fuse应用层访问(读写)fuse driver的功能
lib/mount_util.c –> 提供mount的基础函数调用
lib/mount.c  —> 主要实现设备的“mount”、“umount”等挂接操作
lib/mount_bsd.c —> “Free bsd”下的“mount”、“umount”实现
lib/fuse_mt.c  —> fuse 的mount管理
lib/fuse.c   —> lib库主框架文件,实现了主要框架及对“用户实现的文件系统操作代码“的封装
lib/fuse_lowlevel.c  –> 实现比较底层的函数封装,供fuse.c等使用
lib/fuse_loop.c —> fuse lib循环监视“fuse driver”的通信缓存
lib/fuse_loop_mt.c —> 同上
lib/fuse_session.c —> fuse会话管理
开发库
include/fuse.h      —> the library interface of FUSE (High Level)
include/fuse_common.h    —> common
include/fuse_lowlevel.h    —> Lowlevel API
include/fuse_opt.h  —> option parsing interface of FUSE

2.2  
系统工作流程

fuse_main(lib/helper.c)–fuse用户空间主函数,用户程序调用它时,fuse_main函数解析相关函数(如mountpoint,multithreaded),并调用fuse_mount函数。调用fuse_new()函数,为fuse文件系统数据分配存储空间。调用fuse_loop()函数实现会话的接受与处理。
fuse_mount(lib/mount.c)–创建UNIX本地套接口,创建并运行子进程fusermount.并返回fuse模块文件fd给fuse_main()函数。
fusemount(util/fusermount.c)–确保fuse模块已经加载,通过UNIX套接口返回fuse模块的文件fd给fuse_mount()函数。
fusermount提供一系列挂载选项,如direct_io(跳过页缓存),allow_root(允许root访问挂载的文件系统),allow_other(允许其他用户访问挂载的文件系统),nonempty(允许把文件系统挂载到非空目录),big_writes(支持大于4k的写操作)等
fuse_new(lib/fuse.c)–为fuse创建数据结构空间,用来存储文件系统数据。
fuse_loop(lib/fuse.c)(fuse_loop_mt(lib/fuse_mt.c))–从/dev/fuse读取文件系统调用,调用fuse_operation结构中的处理函数,返回调用结果给/dev/fuse.
如图二:
 

图二

2.3  
命令调用流程分析

fuse处理请求的整个流程如图三所示,以unlink操作为例进行说明。其中“>”表示调用,”<”表示返回,[]表示调用中所做的工作。
 

图三
fuse通过fuse_session_loop(或对应多线程的方法)来启动fuse守护程序,守护程序不断的从/dev/fuse上读取请求,并处理。

2.3.1  
重要数据结构

l  struct fuse_req(fuse_i.h) —> 发往客户端的请求
struct fuse_req {
    struct fuse_ll *f;
   uint64_t unique;
    int ctr;
    pthread_mutex_t lock;
    struct fuse_ctx ctx;
    struct fuse_chan *ch;
    int interrupted;
    union {
        struct {
            uint64_t unique;
        } i;
        struct {
            fuse_interrupt_func_t func;
            void *data;
        } ni;
    } u;
    struct fuse_req *next;
    struct fuse_req *prev;
};
 
l  struct fuse_session(fuse_session.c) —> 客户端管理会话的结构体,其包含“struct fuse_chan”结构
 
struct fuse_session {
    struct fuse_session_ops op;
    void *data;
    volatile int exited;
   struct fuse_chan *ch;
};
 
l  struct fuse_chan(fuse_session.c) —> 定义客户端与fuse内核连接隧道的结构体
 
struct fuse_chan {
    struct fuse_chan_ops op;
    struct fuse_session *se;
    int fd;
    size_t bufsize;
    void *data;
    int compat;
};
 
说明:这里的连接隧道不是什么具体的网络连接,而是客户端通过fuse设备驱
动来读写设备缓存(以与设备交互的一条概念上的隧道
 
l  fc数据结构:
struct fuse_conn
{
 ……….
 
 wait_queue_head_t waitq;
 
struct list_head pending;
 
struct list_head processing;
……….
};

2.3.2  
代码片段1

//在fuse_main中会被调用,或其多线程版本
int fuse_session_loop(struct fuse_session *se) 
{
    int res = 0;
    struct fuse_chan *ch = fuse_session_next_chan(se, NULL);
    size_t bufsize = fuse_chan_bufsize(ch);
    char *buf = (char *) malloc(bufsize);
//为channel分配好缓冲区
    if (!buf) {
        fprintf(stderr, “fuse: failed to allocate read buffer\n”);
        return -1;
    }
 
        //fuse daemon, loops
    while (!fuse_session_exited(se)) {
        struct fuse_chan *tmpch = ch;
//分析见代码片段2,从/dev/fuse读请求,会等待一直到有请求为止
       res = fuse_chan_recv(&tmpch, buf, bufsize);
        if (res == -EINTR)
            continue;
        if (res <= 0)
            break;
      fuse_session_process(se, buf, res, tmpch);   //处理读到的请求
    }
 
    free(buf);
    fuse_session_reset(se);
    return res < 0 ? -1 : 0;
}
 

2.3.3  
代码片段2

int fuse_chan_recv(struct fuse_chan **chp, char *buf, size_t size)
{
    struct fuse_chan *ch = *chp;
    if (ch->compat)
        return ((struct fuse_chan_ops_compat24 *) &ch->op)
            ->receive(ch, buf, size);
    else
        return
ch->op.receive(chp, buf, size);
        //由下面的一段代码可以发现,receive最终是通过fuse_kern_chan_receive实现的,代码片段3分析该请求
}
 
#define MIN_BUFSIZE 0×21000
struct fuse_chan *fuse_kern_chan_new(int fd)
{
  //channel的读写方法
   struct fuse_chan_ops op = {
        .receive = fuse_kern_chan_receive,
        .send = fuse_kern_chan_send,
        .destroy = fuse_kern_chan_destroy,
};
//设置bufsize大小
    size_t bufsize = getpagesize() + 0×1000;
    bufsize = bufsize < MIN_BUFSIZE ? MIN_BUFSIZE : bufsize;
    return fuse_chan_new(&op, fd, bufsize, NULL);
}
 

2.3.4  
代码片段3

static int fuse_kern_chan_receive(struct fuse_chan **chp, char *buf,
                                 
size_t size)
{
    struct fuse_chan *ch = *chp;
    int err;
    ssize_t res;
    struct fuse_session *se = fuse_chan_session(ch);
    assert(se != NULL);
  // 一直轮询,直到读到请求为止
restart:
  //fuse_chan_fs获取到/dev/fuse的文件描述符,调用read系统调用从设备读取请求
res = read(fuse_chan_fd(ch), buf, size);
//根据fuse设备驱动程序file结构的实现(dev.c),read将调用fuse_dev_read,该方法最终通过fuse_dev_readv实现,根据代码中的注释,fuse_dev_read做了如下工作:
Read a single request into the userspace filesystem’s buffer.  This
function waits until a request is available, then removes it from
the pending list and copies request data to userspace buffer.
  
    if no data: goto restart
    ………
}

2.3.5  
一阶段小结

以上的分析对应了fuse filesystem daemon做的第一部分工作。当用户从控制台输入“rm /mnt/fuse/file”时,通过VFS(sys_unlink),再到fuse(dir.c中实现的inode_operations,file.c中实现的file_operations中的方法都会最终调用request_send,后面会介绍),这个请求最终被发到了/dev/fuse中,该请求的到达会唤醒正在等待的fuse守护程序,fuse守护程序读取该请求并进行处理,接下来介绍处理请求所作的工作。
 

2.3.6  
代码片段4

struct fuse_session *fuse_lowlevel_new_common(struct fuse_args *args,
                                      const struct fuse_lowlevel_ops *op,
                                      
size_t op_size, void *userdata)
{
  //开发者实现了fuse_lowlevel_ops并传递给fuse_lowlevel_common
    struct fuse_ll *f;
    struct fuse_session *se;
struct fuse_session_ops sop = {
    //最终调用的处理方法
       .process = fuse_ll_process, //分析见代码片段5
        .destroy = fuse_ll_destroy,
    };
 
  …….
}
 

2.3.7  
代码片段5                        

static void fuse_ll_process(void *data, const char *buf, size_t len,
                     struct fuse_chan *ch)
{
    struct fuse_ll *f = (struct fuse_ll *) data;
    struct fuse_in_header *in = (struct fuse_in_header *) buf;
    const void *inarg = buf + sizeof(struct fuse_in_header);
struct fuse_req *req;
   //创建并初始化一个请求
    req = (struct fuse_req *) calloc(1, sizeof(struct fuse_req));
    if (req == NULL) {
        fprintf(stderr, “fuse: failed to allocate request\n”);
        return;
    }
 
    req->f = f;
req->unique = in->unique;
……
//根据opcode调用fuse_ll_ops中相应的方法
    fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg);
    }
}
 

2.3.8  
二阶段小结

以上代码对应中流程中perform unlink的工作,实际上就是执行开发者实现的一组方法来完成相关的工作,接下来就是把执行完请求后需要的数据返回,最终是通过send_reply实现的,
 

2.3.9  
代码片段6

static int send_reply(fuse_req_t req, int error, const void *arg,
                      size_t argsize)
{
    struct iovec iov[2];
    int count = 1;
    if (argsize) {
        iov[1].iov_base = (void *) arg;
        iov[1].iov_len = argsize;
        count++;
    }
    return
send_reply_iov(req, error, iov, count);
}
 
static int send_reply_iov(fuse_req_t req, int error, struct iovec *iov,
                          int count)
{
    ……
  res = fuse_chan_send(req->ch, iov, count);
    free_req(req);
    return res;
}
 
static int fuse_kern_chan_send(struct fuse_chan *ch, const struct iovec iov[],
                               size_t
count)
{
    if (iov) {
       //将数据写到/dev/fuse上,最终会调用fuse_dev_write
      ssize_t res = writev(fuse_chan_fd(ch), iov, count);
    ……
    return 0;
}
 
另外fuse接收到VFS的请求时,通过request_send将请求发送到/fuse/dev,并调用request_wait_answer等待返回结果。至于fuse使用的队列的管理,在流程图中也做了简单的说明,下一章将详细分析队列的管理。
 

2.3.10          
代码片段7

void
request_send(struct
fuse_conn *fc, struct fuse_req *req)
{
         req->isreply = 1;
         spin_lock(&fc->lock);
         if (!fc->connected)
                   req->out.h.error = -ENOTCONN;
         else if (fc->conn_error)
                   req->out.h.error = -ECONNREFUSED;
         else {
       //将请求加入请求队列
                  queue_request(fc, req);
                  
                   __fuse_get_request(req);
       //等待结果
                  request_wait_answer(fc, req);
         }
         spin_unlock(&fc->lock);
}
 

2.4  
队列管理

fuse通过fuse_session_loop来启动守护程序,守护程序最终会调用fuse_dev_readv,fuse_dev_readv调用request_wait,使得进程在fc的waitq队列上睡眠。

2.4.1  
代码片段1

static ssize_t fuse_dev_readv(struct file *file, const struct iovec *iov,
                                 
unsigned long nr_segs, loff_t *off)
{
         …..
        request_wait(fc);
         ….
}
 
 
static void request_wait(struct fuse_conn *fc)
{
   //定义一个队列节点变量wait,其与当前进程相关联
         DECLARE_WAITQUEUE(wait, current);
  
//将wait加入到fc->waitq等待队列中,当有请求发到fuse文件系统时(通过request_send),这个等待队列上的进程会被唤醒,某一个进程会被赋予CPU使用权
        add_wait_queue_exclusive(&fc->waitq, &wait);
 
    //不断的检查fc的pending队列及interrupts队列,看是否有请求,没有请求会一直while循环
         while (fc->connected &&
!request_pending(fc)) {
                   set_current_state(TASK_INTERRUPTIBLE);
                   if (signal_pending(current))
                            break;
 
                   spin_unlock(&fc->lock);
                  schedule(); //选择一个进程运行
                   spin_lock(&fc->lock);
         }
   //有请求,将进程设为TASK_RUNNING状态
         set_current_state(TASK_RUNNING);
    //将wait从等待队列中移除
        remove_wait_queue(&fc->waitq, &wait);
}
 
static int request_pending(struct fuse_conn *fc)
{
         return !list_empty(&fc->pending) || !list_empty(&fc->interrupts);
}
                              

2.4.2  
代码片段2

request_send是用户请求经过vfs,再到fuse operation中被调用的,它向/dev/fuse发送请求
 
void request_send(struct fuse_conn *fc, struct fuse_req *req)
{
                                 
……
                                 queue_request(fc, req);   
                                 request_wait_answer(fc, req);
                                 
……
}
 
static void queue_request(struct
fuse_conn
*fc, struct fuse_req *req)
{
                                 //将请求加入到pending队列
                                 list_add_tail(&req->list, &fc->pending);
                                 
req->state = FUSE_REQ_PENDING;
                                 
if (!req->waiting) {
                                    
req->waiting = 1;
                                    
atomic_inc(&fc->num_waiting);
                                 
}
              //唤醒等待等列
                                 wake_up(&fc->waitq);
                                 
kill_fasync(&fc->fasync, SIGIO, POLL_IN);
}
 
 
static void request_wait_answer(struct fuse_conn *fc, struct fuse_req *req)
{
        //该调用会在req的waitq上睡眠,fuse守护程序处理完请求后,会将其唤醒
}
 
fuse守护程序处理完请求,最终通过fuse_dev_writev写回/dev/fuse,它将唤醒相应req中waitq的等待队列元素,从而让文件系统请求完成request_wait_answer,获取到结果。
 
 
static ssize_t fuse_dev_writev(struct file *file, const struct iovec *iov,
                                                     
unsigned long nr_segs, loff_t *off)
{
                                 req = request_find(fc, oh.unique);
                                 
request_end(fc, req);
                         
}
 
static void
request_end(struct
fuse_conn *fc, struct fuse_req *req)
{
              //唤醒req上的等待队列
                                 wake_up(&req->waitq);
}
 
fuse设备其的主要工作其实就是进行队列的管理,对fuse设备的读(写)其实就是从相应的队列移除(添加)请求(或响应),request_send将请求加入pending队列,唤醒fuse守护程序,并在req的waitq上等待请求结果,守护程序通过fuse_dev_readv从pending队列中移除请求并处理,处理完成后,守护程序唤醒req的waitq上的进程,该进程读取结果,并返回给用户。总的来说,一个请求从发起到完成会经过4步:
ü   fuse守护程序在fc的waitq上等待请求.
ü   fuse deamon process wait
ü   用户的请求唤醒fc的waitq,从该waitq上移除一个请求进行处理,并在req的waitq上等待请求结果;
ü   fuse守护程序被唤醒,读取请求,处理请求,返回结果,唤醒对应req上的waitq队列。
ü   请求被唤醒,读取fuse守护程序返回的结果,返回给用户。
 

3      
开发API

参见http://fuse.sourceforge.net/doxygen/index.html
或PPT《Building fs with FUSE》

4      
参考资料


Linux
文件系统剖析

FUSE: Filesystem in Userspace

用户空间文件系统-fuse – FUSE – bpingchang

fuse-2.7.3.tar.gz开源代码学习心得_牛牛_新浪博客

Fuse用户空间文件系统安装学习笔记客

Filesystem in Userspace – Wikipedia, the free encyclopedia

vfs fuse 的多线程场景分析 – 2010.5 –
哈哈的雷

fuse中的read操作是并行的 – program – 写给自己的Blog

FUSE – 我的文章 – bpingchang

张友东FUSE总结

linux文件系统之路径查找与文件系统的挂载 – zz – 4月

fuse默认是并发处理I/O请求的 – program – 写给自己的Blog

MooseFS介绍
mfs权威指南(moosefs)分布式文件系统一站式解决方案

fuse 学习(一) – fuse – zishang-hhg

Fuse study – FUSE – bpc2003

file system,
fuse

 

相关文章推荐:

* 用户空间文件系统-fuse

This entry was posted on 2010/11/22, 21:41 and is filed under
FUSE.
You can follow any responses to this entry through
RSS 2.0.
You can
leave a response,
or trackback
from your own site.
http://my.debugman.net/program/fuse-180.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: