Linux FUSE源代码分析

网络文《Linux_FUSE源代码分析.doc》


Linux FUSE源代码分析

一、Fuse简要介绍

FUSE(用户空间文件系统)是这样一个框架,它使得FUSE用户在用户态下编写文件系统成为可能,而不必和内核打交道。FUSE由三个部分组成,linux内核模块、FUSE库以及mount工具。用户关心的只是FUSE库和mount工具,内核模块仅仅提供kernel的接入口,给了文件系统一个框架,而文件系统本身的主要实现代码位于用户空间中。FUSE库给用户提供了编程的接口,而mount工具则用于挂在用户编写的文件系统。

FUSE起初是为了研究AVFS(AVirtual Filesystem)而设计的,而现在已经成为SourceForge的一个独立项目,目前适用的平台有Linux,FreeBSD, NetBSD, OpenSolarisMac OSX。官方的linuxkernel版本到2.6.14才添加了FUSE模块,因此2.4的内核模块下,用户如果要在FUSE中创建一个文件系统,需要先安装一个FUSE内核模块,然后使用FUSE库和API来创建。

1.1什么是Fuse

传统的文件系统是操作系统的一部分,放在操作系统内核里面实现。Fuse(Filesystemin Userspace),一个用户空间文件系统框架,提供给我们一组用于实现一个文件系统的API,使我们可以在用户态实现自已的文件系统。目前fuse已集成在Linux2.6以上版本的内核中。

(注:操作系统中的用户态指权限等级中的一般级别,与之相对的是超级用户或者管理员的特权级别。用户态启动的每个进程,根据运行该进程的登录用户,都被系统赋予一定的权限,另外也有一些限制。)

1.2优缺点

  1. 传统文件系统都是定义在操作系统内核层面上的,要操作系统识别一种新的文件系统,必需重写内核,而内核态代码难以调试,生产率较低;但是用户空间编程和调试难度较小,有更多的语言可以选择(目前FUSE已经绑定了很多语言,比如c++java等),还可以复用已有的库),从而能够大幅提高生产率,极大地简少了为操作系统提供新的文件系统的工作量。

  2. 一些服务可以通过统一的文件系统接口来进行访问,比如说ftpsftpsamba

  3. 可以把非文件的服务当做文件来实现,比如把gmail提供的巨大的空间用来进行文件存储的GmailFilesystem

在用户态实现文件系统必然会引入额外的内核态/用户态切换带来的开销,对性能会产生一定影响。





二、FUSE特性

a、库文件和API简单,极大地方便了用户的使用

b、安装简便,不需要加补丁或者重新编译kernel

c、执行安全,使用稳定

d、高效,相对于其它用户态文件系统实例

e、非特权用户可以使用

f、基于 linux2.4.x2.6.x内核,现在可以支持JavaTM绑定,不必限定使用CC++来编写文件系统

三、源代码目录:

./doc 包含FUSE相关文档

./include 包含了FUSEAPI头,对创建文件系统有用,主要用fuse.h

./lib 存放FUSE库的源代码

./util 包含了FUSE工具库的源代码

./example 参考的例子


四、安装

FUSE的源码安装类似于其他软件,只需要在FUSE的源码目录下执行如下命令即可:

./configure

make

makeinstall(以root身份执行)

五、FUSEoperations

FUSE使用fuse_operations来给用户提供编程结构,让用户通过注册自己编写的函数到该结构体来实现自己的文件系统。


六、Fuse文件系统的结构

fuse包括三个模块:用户空间库,内核模块以及mount工具

1)用户空间库给程序员提供编程接口,程序员通过实现fuse提供的两组接口fuse_lowlevel_opsfuse_operations之一即可实现一个用户空间文件系统

2)内核模块实现了一个完整文件系统的框架,但具体操作没有实现(由程序员在用户空间实现)

3mount工具fusermount用于挂载基于fuse的文件系统



6.1 Fuse在用户空间工作的流程图

通过这幅图可以看到三个模块在fuse工作时所起的作用

fuse_main()(lib/helper.c)——fuse用户空间主函数,用户程序调用它时,fuse_main()函数解析相关参数(如mountpointmultithreaded),并调用fuse_mount()函数,接着调用fuse_new()函数,为fuse文件系统数据分配存储空间。最后调用fuse_loop()函数实现会话的接受与处理。

fuse_mount()(lib/mount.c)——创建UNIX本地套接口,创建并运行子进程fusermount

fusermount(util/fusermount.c)——确保fuse模块已经加载,通过UNIX套接口返回fuse模块的文件fdfuse_mount()函数。

fuse_new()(lib/fuse.c)——fuse创建数据结构空间,用来存储文件系统数据。

fuse_loop()(lib/fuse.c)( fuse_loop_mt() (lib/fuse_mt.c))——/dev/fuse(/dev设备文件存储目录)读取文件系统调用,调用fuse_operationsfuse_lowlevel_ops结构中的处理函数,返回调用结果给/dev/fuse

6.2 Fuse内核模块

FUSEKernel模块由两部分组成:

第一部分——proc文件系统组件:Kernel/dev.c——回应io请求到/dev/fusefuse_dev_read()函数负责读出文件,并将来自“listof request”结构体的命令返回到调用函数。fuse_dev_write()负责文件写入,并将写入的数据置放到“req→out”数据结构中。

第二部分——文件系统调用部分:kernel/file.ckernel/inode.ckernel/dir.c——调用request_send(),将请求加入到“listof request”结构体中,等待回复(reply)

七、Fuse调用流程

由于fuse处理请求过程涉及的内容较多,如果从采用从外到内逐层深入的方法来讲,虽然符合逻辑但会增加理解难度,因为到最后大家会迷失在一个个的函数调用里,而且也难以抓住其本质与核心。所以我由其核心——队列管理讲起,向外扩散;再从最外层的函数调用向内讲;最后瞻前顾后,整个fuse处理请求的流程就明白了。

我们先利用下面一幅图简要了解下fuse文件系统工作时的调用路径。

shell里输入命令,请求通过vfs到达fuse,然后通过用户实现的fuse给出的API返回调用。


7.1 Fuse处理请求的核心工作就是进行队列管理

1)两个重要的数据结构

fc的定义如下

/* A Fuse connection.

* This structure is created, when thefilesystem is mounted, and is destroyed, when the

* client device is closed and thefilesystem is unmounted.

*/

Struct fuse_conn

{

/** Readers of the connection arewaiting on this */

wait_queue_head_t waitq; //等待执行请求的进程的队列

/** The list of pending requests */

struct list_head pending; //被挂起的请求 的队列

/** The list of requests beingprocessed */

struct list_head processing; //正在被处理的请求的 队列

/** Pending interrupts */

struct list_head interrupts; //执行中被中断的请求的 队列

...

}

req的定义如下:

/*

*A request to theclient

*/

struct fuse_req

{

/** Used to wake up the task waitingfor completion of request*/

wait_queue_head_t waitq; //请求的等待队列

}

2)队列管理的过程如下


3)队列管理的相关代码

(左列一至五行)fuse通过fuse_session_loop来启动守护程序,守护程序最终会调用fuse_dev_readv,fuse_dev_readv调用request_wait,使得进程在fcwaitq队列上睡眠。

Static size_t fuse_dev_readv(structfile *file, const struct iovec *iov, unsigned long nr_segs, loff_t*off)

{

….

request_wait(fc);

….

}


/* Wait untila request is available on the pending list

*当前进程一直等待,直到挂起队列中有一个请求

*/

static void request_wait(structfuse_conn *fc)

{

DECLARE_WAITQUEUE(wait, current); //定义一个队列节点变量wait,其与当前进程相关联

add_wait_queue_exclusive(&fc->waitq, &wait); //wait加入到fc->waitq等待队列中

//不断的检查fcpending队列及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状态(被唤醒,被赋予CPU使用权)

set_current_state(TASK_RUNNING);

remove_wait_queue(&fc->waitq,&wait); // wait(当前进程)从等待队列中移除

}


//fcpending队列及interrupts队列,看是否有请求

static int request_pending(structfuse_conn *fc)

{

return !list_empty(&fc->pending)|| !list_empty(&fc->interrupts);

}


(右列一到四)request_send是用户请求经过vfs(如上面的图),再到fuseoperation中被调用的,它向/dev/fuse发送请求

void request_send(struct fuse_conn*fc, struct fuse_req *req)

{

……

queue_request(fc, req);

request_wait_answer(fc, req);

……

}


static voidqueue_request(struct fuse_conn *fc, struct fuse_req *req)

{

list_add_tail(&req->list,&fc->pending); //将请求加入到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);

}


/* Calledwith fc->lock held. Releases, and then reacquires it. */

//该调用会在reqwaitq上睡眠,fuse守护程序处理完请求后,会将其唤醒

static void request_wait_answer(structfuse_conn *fc, struct fuse_req *req)

{

if (!fc->no_interrupt)

{

/* Any signal may interrupt this*/

wait_answer_interruptible(fc,req);

if (req->aborted)

goto aborted;

if (req->state ==FUSE_REQ_FINISHED)

return;

req->interrupted = 1;

if (req->state ==FUSE_REQ_SENT)

queue_interrupt(fc, req);

}

if (req->force) {

spin_unlock(&fc->lock);

wait_event(req->waitq, req->state== FUSE_REQ_FINISHED);

spin_lock(&fc->lock);

} else {

sigset_t oldset;

/* Only fatal signals may interruptthis */

block_sigs(&oldset);

wait_answer_interruptible(fc, req);

restore_sigs(&oldset);

}

if (req->aborted)

goto aborted;

if (req->state ==FUSE_REQ_FINISHED) return;

req->out.h.error = -EINTR;

req->aborted = 1;

aborted:

if (req->locked) {

/* This is uninterruptible sleep,because data is

being copied to/from the buffersof req. During

locked state, there mustn't be anyfilesystem

operation (e.g. page fault), sincethat could lead

to deadlock */

spin_unlock(&fc->lock);

wait_event(req->waitq,!req->locked);

spin_lock(&fc->lock);

}

if (req->state == FUSE_REQ_PENDING){

list_del(&req->list);

__fuse_put_request(req);

} else if (req->state ==FUSE_REQ_SENT) {

spin_unlock(&fc->lock);

wait_event(req->waitq, req->state== FUSE_REQ_FINISHED);

spin_lock(&fc->lock);

}

}

}


(左列七行)fuse守护程序处理完请求,最终通过fuse_dev_writev写回/dev/fuse,它将唤醒相应reqwaitq的等待队列元素,从而让文件系统请求完成request_wait_answer,获取到结果。

/**Write asingle reply to a request. First the header is copied from the writebuffer. The request is then *searched on the processing list by theunique ID found in the header. If found, then remove it from thelist *and copy the rest of the buffer to the request. The request isfinished by calling request_end()

*/

static ssize_t fuse_dev_writev(structfile *file, const struct iovec *iov, unsigned long nr_segs, loff_t*off)

{

……..

req = request_find(fc, oh.unique);

request_end(fc, req);

….

}


/* * Thisfunction is called when a request is finished. Either a reply hasarrived or it was aborted (and not yet *sent) or some error occurredduring communication with userspace, or the device file was closed. The *requester thread is woken up (if still waiting), the 'end'callback is called if given, else the reference to the *request isreleased Called with fc->lock, unlocks it

*/

static void request_end(structfuse_conn *fc, struct fuse_req *req)

{

….

wake_up(&req->waitq); //唤醒req上的等待队列

……

}



7.2 fuse处理请求流程

unlink操作为例,根据流程图结合源代码分析fuse处理请求流程

处理unlink操作的整个流程如下图所示。其中“>”表示调用,”<”表示返回,[]表示调用中所做的工作。


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

int fuse_session_loop(structfuse_session *se) //fuse_main中会被调用,或其多线程版本

{

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;

// /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;

}

intfuse_chan_recv(struct fuse_chan **chp, char *buf, size_t size)

{

struct fuse_chan *ch = *chp;

if (ch->compat)

return ((structfuse_chan_ops_compat24 *) &ch->op)->receive(ch, buf, size);

else

return ch->op.receive(chp,buf, size); //由下面的一段代码可以发现,receive最终是通过

// fuse_kern_chan_receive实现的,代码片段3分析该请求

}

#defineMIN_BUFSIZE 0x21000

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() +0x1000;

bufsize = bufsize < MIN_BUFSIZE? MIN_BUFSIZE : bufsize;

return fuse_chan_new(&op, fd,bufsize, NULL);

}

static intfuse_kern_chan_receive(struct fuse_chan **chp, char *buf, size_tsize)

{

struct fuse_chan *ch = *chp;

int err;

ssize_t res;

struct fuse_session *se =fuse_chan_session(ch);

assert(se != NULL);

// 一直轮询,直到读到请求为止

restart:

//fuse_chan_fd获取到/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 theuserspace filesystem's buffer. This function waits until a requestis available,

// then removes it from the pendinglist and copies request data to userspace buffer.

//fuse_dev_read又调用request_wait,使得进程在fc->waitq上睡眠

if no datagoto restart

………

}


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

structfuse_session *fuse_lowlevel_new_common(struct fuse_args *args,

const struct fuse_lowlevel_ops *op,

size_t op_size, void *userdata)

{

//fuse_lowlevel_ops在之前的文章

http://blog.chinaunix.net/u2/87570/showart_2166461.html中已经介绍//过了,开发者实现了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,

};

…….

}

static voidfuse_ll_process(void *data, const char *buf, size_t len, structfuse_chan *ch)

{

struct fuse_ll *f = (struct fuse_ll*) data;

struct fuse_in_header *in = (structfuse_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的介绍

//http://blog.chinaunix.net/u2/87570/showart_2166461.html

fuse_ll_ops[in->opcode].func(req,in->nodeid, inarg);

}

}


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

static intsend_reply(fuse_req_t req, int error, const void *arg, size_targsize)

{

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 intsend_reply_iov(fuse_req_t req, int error, struct iovec *iov, intcount)

{

……

res = fuse_chan_send(req->ch,iov, count);

free_req(req);

return res;

}

static intfuse_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使用的队列的管理,在流程图中也做了简单的说明,下一篇文章将详细分析队列的管理。

voidrequest_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);

/* acquire extrareference, since request is still needed after request_end() */

__fuse_get_request(req);

//等待结果

request_wait_answer(fc, req);

}

spin_unlock(&fc->lock);

}





八、hello.c源码分析

FUSE在源码目录example下有一些示例文件系统,通过阅读这些示例文件系统可以掌握FUSE用户态文件系统的编写规范。下面以hello.c为例分析FUSE的编写规范:

#define FUSE_USE_VERSION 26

#include <fuse.h>

#include <stdio.h>

#include <string.h>

#include <errno.h>

#include <fcntl.h>


static const char *hello_str = "HelloWorld!\n";

static const char *hello_path ="/hello";


/*该函数与stat()类似,用于得到文件的属性,将其存入到结构体structstat*/

static int hello_getattr(const char*path, struct stat *stbuf)

{

int res = 0;

memset(stbuf, 0, sizeof(structstat)); //用于初始化结构体stat

if (strcmp(path, "/") ==0) {

stbuf->st_mode = S_IFDIR |0755; //S_IFDIR 用于说明/为目录,详见S_IFDIR定义

stbuf->st_nlink =2; //文件链接数

} else if (strcmp(path, hello_path)== 0) {

stbuf->st_mode = S_IFREG |0444; //S_IFREG用于说明/hello为常规文件

stbuf->st_nlink = 1;

stbuf->st_size =strlen(hello_str); //设置文件长度为hello_str的长度

} else

res =-ENOENT; //返回错误信息,没有该文件或目录

return res; //执行成功返回0

}


/*该函数用于读取/目录中的内容,并在/目录下增加了. .. hello三个目录项*/

static int hello_readdir(const char*path, void *buf, fuse_fill_dir_t filler,

off_t offset, structfuse_file_info *fi)

{

(void) offset;

(void) fi;

if (strcmp(path, "/") !=0)


return -ENOENT;

/*

fill的定义:

typedef int (*fuse_fill_dir_t) (void*buf, const char *name,

const struct stat *stbuf, off_t off);


其作用是在readdir函数中增加一个目录项

*/

filler(buf, ".", NULL,0);

///目录下增加.这个目录项

filler(buf, "..", NULL,0);

// 增加..目录项

filler(buf, hello_path + 1, NULL,0);

//增加hello目录项

return 0;


}



/*用于打开hello文件*/

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;

}


/*读取hello文件时的操作,它实际上读取的是字符串hello_str的内容*/

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_operationshello_oper = {

.getattr = hello_getattr,

.readdir = hello_readdir,

.open = hello_open,

.read = hello_read,

};


/*用户只需要调用fuse_main(),剩下的事就交给FUSE*/

int main(int argc, char *argv[])

{

return fuse_main(argc, argv,&hello_oper, NULL);

}


终端运行:

~/fuse/example$ mkdir /tmp/fuse ///tmp下建立fuse目录,用于挂载hello文件系统

~/fuse/example$ ./hello/tmp/fuse //挂载hello文件系统

~/fuse/example$ ls -l/tmp/fuse //执行ls时,会调用到readdir函数,该函数会添加一个hello文件

总用量 0

-r--r--r-- 1 root root 13 1970-01-0107:00 hello

~/fuse/example$ cat/tmp/fuse/hello //执行cathello时,会调用open以及read函数,将

Hello World! 字符串hello_str中的内容读出

~/fuse/example$ fusermount -u /tmp/fuse //卸载hello文件系统

通过上述的分析可以知道,使用FUSE必须要自己实现对文件或目录的操作,系统调用也会最终调用到用户自己实现的函数。用户实现的函数需要在结构体fuse_operations中注册。而在main()函数中,用户只需要调用fuse_main()函数就可以了,剩下的复杂工作可以交给FUSE



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值