LinuxFUSE源代码分析.doc_第1页
LinuxFUSE源代码分析.doc_第2页
LinuxFUSE源代码分析.doc_第3页
LinuxFUSE源代码分析.doc_第4页
LinuxFUSE源代码分析.doc_第5页
已阅读5页,还剩13页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Linux FUSE源代码分析一、Fuse简要介绍FUSE(用户空间文件系统)是这样一个框架,它使得FUSE用户在用户态下编写文件系统成为可能,而不必和内核打交道。FUSE由三个部分组成,linux内核模块、FUSE库以及mount 工具。用户关心的只是FUSE库和mount工具,内核模块仅仅提供kernel的接入口,给了文件系统一个框架,而文件系统本身的主要实现代码位于用户空间中。 FUSE库给用户提供了编程的接口,而mount工具则用于挂在用户编写的文件系统。FUSE起初是为了研究AVFS(A Virtual Filesystem)而设计的,而现在已经成为SourceForge的一个独立项目,目前适用的平台有Linux, FreeBSD, NetBSD, OpenSolaris和Mac OS X。官方的linux kernel版本到2.6.14才添加了FUSE模块,因此2.4的内核模块下,用户如果要在FUSE中创建一个文件系统,需要先安装一个FUSE内核模块,然后使用FUSE库和API来创建。1.1 什么是Fuse传统的文件系统是操作系统的一部分,放在操作系统内核里面实现。Fuse(Filesystem in Userspace), 一个用户空间文件系统框架,提供给我们一组用于实现一个文件系统的API,使我们可以在用户态实现自已的文件系统。目前fuse已集成在Linux2.6以上版本的内核中。(注:操作系统中的用户态指权限等级中的一般级别,与之相对的是超级用户或者管理员的特权级别。用户态启动的每个进程,根据运行该进程的登录用户,都被系统赋予一定的权限,另外也有一些限制。)1.2 优缺点1) 传统文件系统都是定义在操作系统内核层面上的,要操作系统识别一种新的文件系统,必需重写内核,而内核态代码难以调试,生产率较低;但是用户空间编程和调试难度较小,有更多的语言可以选择(目前FUSE已经绑定了很多语言,比如c+、java等),还可以复用已有的库),从而能够大幅提高生产率,极大地简少了为操作系统提供新的文件系统的工作量。2) 一些服务可以通过统一的文件系统接口来进行访问,比如说ftp、sftp、samba3) 可以把非文件的服务当做文件来实现,比如把gmail提供的巨大的空间用来进行文件存储的Gmail Filesystem。在用户态实现文件系统必然会引入额外的内核态/用户态切换带来的开销,对性能会产生一定影响。二、FUSE特性a、库文件和 API简单,极大地方便了用户的使用b、安装简便,不需要加补丁或者重新编译 kernelc、执行安全,使用稳定d、高效,相对于其它用户态文件系统实例e、非特权用户可以使用f、基于 linux2.4.x 和 2.6.x 内核,现在可以支持JavaTM 绑定,不必限定使用C和C+来编写文件系统三、源代码目录: ./doc 包含FUSE相关文档 ./include包含了FUSE API头,对创建文件系统有用,主要用fuse.h ./lib 存放FUSE库的源代码 ./util 包含了FUSE工具库的源代码 ./example 参考的例子四、安装FUSE的源码安装类似于其他软件,只需要在FUSE的源码目录下执行如下命令即可: ./configuremakemake install(以root身份执行)五、FUSE operations FUSE使用fuse_operations来给用户提供编程结构,让用户通过注册自己编写的函数到该结构体来实现自己的文件系统。六、Fuse文件系统的结构fuse包括三个模块:用户空间库,内核模块以及mount工具1)用户空间库给程序员提供编程接口,程序员通过实现fuse提供的两组接口fuse_lowlevel_ops, fuse_operations之一即可实现一个用户空间文件系统2)内核模块实现了一个完整文件系统的框架,但具体操作没有实现(由程序员在用户空间实现)3)mount工具fusermount用于挂载基于fuse的文件系统6.1 Fuse在用户空间工作的流程图通过这幅图可以看到三个模块在fuse工作时所起的作用fuse_main() (lib/helper.c)fuse用户空间主函数,用户程序调用它时,fuse_main()函数解析相关参数(如mountpoint,multithreaded),并调用fuse_mount()函数,接着调用fuse_new()函数,为fuse文件系统数据分配存储空间。最后调用fuse_loop()函数实现会话的接受与处理。fuse_mount() (lib/mount.c)创建UNIX本地套接口,创建并运行子进程fusermount。fusermount (util/fusermount.c)确保fuse模块已经加载,通过UNIX套接口返回fuse模块的文件fd给fuse_mount()函数。fuse_new() (lib/fuse.c)为fuse创建数据结构空间,用来存储文件系统数据。fuse_loop() (lib/fuse.c)( fuse_loop_mt() (lib/fuse_mt.c)从/dev/fuse (/dev 设备文件存储目录)读取文件系统调用,调用fuse_operations或fuse_lowlevel_ops结构中的处理函数,返回调用结果给/dev/fuse6.2 Fuse内核模块FUSE Kernel模块由两部分组成:第一部分proc文件系统组件:Kernel/dev.c回应io请求到/dev/fuse。fuse_dev_read()函数负责读出文件,并将来自“list of request”结构体的命令返回到调用函数。fuse_dev_write ()负责文件写入,并将写入的数据置放到“reqout”数据结构中。第二部分文件系统调用部分:kernel/file.c,kernel/inode.c,kernel/dir.c调用request_send(),将请求加入到“list of request”结构体中,等待回复(reply)。七、 Fuse调用流程由于fuse处理请求过程涉及的内容较多,如果从采用从外到内逐层深入的方法来讲,虽然符合逻辑但会增加理解难度,因为到最后大家会迷失在一个个的函数调用里,而且也难以抓住其本质与核心。所以我由其核心队列管理讲起,向外扩散;再从最外层的函数调用向内讲;最后瞻前顾后,整个fuse处理请求的流程就明白了。我们先利用下面一幅图简要了解下fuse文件系统工作时的调用路径。在shell里输入命令,请求通过vfs到达fuse,然后通过用户实现的fuse给出的API返回调用。 7.1 Fuse处理请求的核心工作就是进行队列管理1)两个重要的数据结构fc的定义如下/* A Fuse connection. * This structure is created, when the filesystem is mounted, and is destroyed, when the * client device is closed and the filesystem is unmounted. */Struct fuse_conn /* Readers of the connection are waiting on this */ wait_queue_head_t waitq; / 等待执行请求的进程的队列 /* The list of pending requests */ struct list_head pending; / 被挂起的请求 的队列 /* The list of requests being processed */ struct list_head processing; / 正在被处理的请求的 队列/* Pending interrupts */ struct list_head interrupts; / 执行中被中断的请求的 队列.req的定义如下:/* *A request to the client */struct fuse_req/* Used to wake up the task waiting for completion of request*/ wait_queue_head_t waitq; / 请求的等待队列2)队列管理的过程如下 3)队列管理的相关代码(左列一至五行) fuse通过fuse_session_loop来启动守护程序,守护程序最终会调用fuse_dev_readv, fuse_dev_readv调用request_wait,使得进程在fc的waitq队列上睡眠。Static size_t fuse_dev_readv(struct file *file, const struct iovec *iov, unsigned long nr_segs, loff_t *off) . request_wait(fc); ./* Wait until a request is available on the pending list *当前进程一直等待,直到挂起队列中有一个请求*/static void request_wait(struct fuse_conn *fc)DECLARE_WAITQUEUE(wait, current); /定义一个队列节点变量wait,其与当前进程相关联 add_wait_queue_exclusive(&fc-waitq, &wait); /将wait加入到fc-waitq等待队列中 /不断的检查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状态(被唤醒,被赋予CPU使用权) set_current_state(TASK_RUNNING); remove_wait_queue(&fc-waitq, &wait); / 将wait(当前进程)从等待队列中移除/ fc的pending队列及interrupts队列,看是否有请求static int request_pending(struct fuse_conn *fc)return !list_empty(&fc-pending) | !list_empty(&fc-interrupts);(右列一到四)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) 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);/* Called with fc-lock held. Releases, and then reacquires it. */该调用会在req的waitq上睡眠,fuse守护程序处理完请求后,会将其唤醒static void request_wait_answer(struct fuse_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 interrupt this */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 buffers of req. During locked state, there mustnt be any filesystem operation (e.g. page fault), since that 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,它将唤醒相应req中waitq的等待队列元素,从而让文件系统请求完成request_wait_answer,获取到结果。/*Write a single reply to a request. First the header is copied from the write buffer. The request is then *searched on the processing list by the unique ID found in the header. If found, then remove it from the list *and copy the rest of the buffer to the request. The request is finished by calling request_end() */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); ./* * This function is called when a request is finished. Either a reply has arrived or it was aborted (and not yet *sent) or some error occurred during 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 is released Called with fc-lock, unlocks it */static void request_end(struct fuse_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(struct fuse_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 buffern); 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 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 0x21000struct 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 waitq上睡眠 if no data: goto restart 以上的分析对应了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守护程序读取该请求并进行处理,接下来介绍处理请求所作的工作。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在之前的文章/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 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 requestn); return; req-f = f;req-unique = in-unique;/根据opcode调用fuse_ll_ops中相应的方法,fuse_ll_ops的介绍/ /u2/87570/showart_2166461.html fuse_ll_opsin-opcode.func(req, in-nodeid, inarg); 以上代码对应中流程中perform unlink的工作,实际上就是执行开发者实现的一组方法来完成相关的工作,接下来就是把执行完请求后需要的数据返回,最终是通过send_reply实现的,static int send_reply(fuse_req_t req, int error, const void *arg, size_t argsize) struct iovec iov2; int count = 1; if (argsize) iov1.iov_base = (void *) arg; iov1.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使用的队列的管理,在流程图中也做了简单的说明,下一篇文章将详细分析队列的管理。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); /* acquire extra reference, 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 #include #include #include #include static const char *hello_str = Hello World!n;static const char *hello_path = /hello;/*该函数与stat()类似,用于得到文件的属性,将其存入到结构体struct stat中*/static int hello_getattr(const char *path, struct stat *stbuf) int res = 0; memset(stbuf, 0, sizeof(struct stat);/用于初始化结构体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, struct fuse_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);/ 增加. 目录项

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论