fuse文件系统
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
2、Fuse文件系统 (3)
2.1、简介 (3)
2.2、组成及功能实现 (3)
2.3、FUSE的接口函数 (5)
2.4、Fuse在用户态实现过程 (6)
2.4.1、fuse_main()的处理流程 (6)
2.4.2、内核FUSE文件系统和用户态文件系统的通信 (7)
2.4.3、FUSE用户态文件系统的挂载过程 (8)
2.4.4、FUSE用户态模块的业务逻辑结构 (8)
2.4.5、FUSE内核模块的业务逻辑结构 (9)
2.4.6、内核中/dev/fuse读写详细流程 (10)
1) 前提介绍 (10)
2) 读取/dev/fuse中的请求 (10)
3) 将请求发送到/dev/fuse中 (10)
2.3、总结 (11)
3、NTFS中的rm命令流程 (12)
2、Fuse文件系统
2.1、简介
FUSE,全称Filesystem in Userspace,也就是用户空间的文件系统。
准确说来,FUSE是为开发用户空间的文件系统提供的一个框架,具体来说,就是一个软件包,一些接口,再加上一个内核模块。
FUSE模块仅仅提供kernel模块的接入口,而本身的主要实现代码位于用户空间中。
对于读写虚拟文件系统来说,FUSE是个很好的选择。
它可以为用户提供编写用户态文件系统的接口。
使用FUSE,用户可以不必熟悉Kernel代码,使用标准C库、FUSE库以及GNU C库便可设计出自己需要的文件系统。
2.2、组成及功能实现
FUSE由三个部分组成:FUSE内核模块、FUSE库以及一些挂载工具。
FUSE内核模块实现了和VFS的对接,它看起来像一个普通的文件系统模块;另外,FUSE内核模块实现了一个可以被用户空间进程打开的设备,当VFS发来文件操作请求之后,它将该请求转化为特定格式,并通过设备传递给用户空间进程,用户空间进程在处理完请求后,将结果返回给FUSE内核模块,内核模块再将其还原为Linux kernel需要的格式,并返回给VFS。
如下图所示:
图2 -1 FUSE内核模块
FUSE库负责和内核空间的通信,它接收来自/dev/fuse的请求,并将其转化为一系列的函数调用,并将结果写回到/dev/fuse。
FUSE的作用可通过下面两幅图说明:
图2 -2 Ext4文件系统的文件操作流程
上图为Ext4文件系统的文件操作流程,当系统用户在输入ls /home/kelvin命令之后,最终会调用到Ext4文件系统内的相关函数来对文件进行处理,并将结果返回。
图2 -3 用户态文件系统的文件操作流程
上图是基于FUSE所写的一个用户态文件系统hello的文件操作流程,FUSE 的工作原理如图 2 -3所示。
假设基于FUSE的用户态文件系统hello挂载在/tmp/fuse目录下。
当应用层程序要访问/tmp/fuse下的文件时,通过busybox中的
函数进行系统调用,处理这些系统调用的VFS中的函数会调用FUSE在内核中的文件系统;内核中的FUSE文件系统将用户的请求,发送给用户态文件系统hello;用户态文件系统收到请求后,进行处理,将结果返回给内核中的FUSE文件系统;最后,内核中的FUSE文件系统将数据返回给用户态程序。
FUSE给用户提供了fuse_operations结构体,用户可实现具体的钩子函数,然后将这些钩子函数挂载到该结构体。
main()函数只需调用fuse_main()就可以了,其他的工作交给FUSE去做。
2.3、FUSE的接口函数
Fuse的接口函数主要有一下一些。
static const struct inode_operations fuse_dir_inode_operations = {
.lookup = fuse_lookup,
.mkdir = fuse_mkdir,
.symlink = fuse_symlink,
.unlink = fuse_unlink,
.rmdir = fuse_rmdir,
.rename = fuse_rename,
.link = fuse_link,
.setattr = fuse_setattr,
.create = fuse_create,
.mknod = fuse_mknod,
.permission = fuse_permission,
.getattr = fuse_getattr,
.setxattr = fuse_setxattr,
.getxattr = fuse_getxattr,
.listxattr = fuse_listxattr,
.removexattr = fuse_removexattr,
};
static const struct file_operations fuse_dir_operations = {
.llseek = generic_file_llseek,
.read = generic_read_dir,
.readdir = fuse_readdir,
.open = fuse_dir_open,
.release = fuse_dir_release,
.fsync = fuse_dir_fsync,
};
static const struct inode_operations fuse_common_inode_operations = { .setattr = fuse_setattr,
.permission = fuse_permission,
.getattr = fuse_getattr,
.setxattr = fuse_setxattr,
.getxattr = fuse_getxattr,
.listxattr = fuse_listxattr,
.removexattr = fuse_removexattr,
};
static const struct inode_operations fuse_symlink_inode_operations = { .setattr = fuse_setattr,
.follow_link = fuse_follow_link,
.put_link = fuse_put_link,
.readlink = generic_readlink,
.getattr = fuse_getattr,
.setxattr = fuse_setxattr,
.getxattr = fuse_getxattr,
.listxattr = fuse_listxattr,
.removexattr = fuse_removexattr,
};
2.4、Fuse在用户态实现过程
2.4.1、fuse_main()的处理流程
Fuse_operations是libfuse提供给用户层文件系统的机构,供用户定义自己的文件操作函数;fuse_main()是libfuse提供给用户文件系统最重要的接口,通过这个函数,用户层文件系统可以将自己定义的fuse_operation注册为文件系统的处理函数,并挂载该文件系统。
Fuse_main()的处理流程
图2 -4 fuse_main()函数的处理流程
fuse_main()被调用后,它调用fuse_mount(),创建新的进程fusermount,来检查FUSE内核模块是否加载,并返回文件描述符给fuse_main()。
fuse_new()为文件系统分配数据空间。
fuse_loop()从/dev/fuse读取文件系统调用,调用fuse_operations结构中的处理函数,返回调用结果给/dev/fuse。
2.4.2、内核FUSE文件系统和用户态文件系统的通信
在加载fuse模块的过程中,要在内核中注册fuse文件系统,并生成fuse设备/dev/fuse。
/dev/fuse是内核里的fuse文件系统和用户态文件系统的通信媒介。
用户态文件系统通过读取/dev/fuse的内容,获取内核中fuse文件系统发来的请求;而内核中的fuse文件系统,则把请求写入/dev/fuse,等待用户态文件系统处理。
在用户态文件系统和内核中的fuse模块通过/dev/fuse进行通信时,不可避免的会有内存拷贝。
fuse中没有使用传统的copy_from_user/copy_to_user,而是用
get_user_pages和memcpy来代替。
2.4.3、FUSE用户态文件系统的挂载过程
挂载用户态文件系统,只需运行其生成的程序。
在hello中,运行编译hello.c 生成的hello即可。
fuse_main将完成一切的注册和挂载过程,其中主要分为两个步骤:
1、在内核中挂载新的用户态文件系统
a)打开设备文件“/dev/fuse”,获得文件描述符fd;
b)挂载FUSE文件系统(内核里的fuse文件系统),将fd作为参数
传给内核里的挂载函数;
c)内核中的fuse文件系统在初始化super_block的过程中,将创建
一个新的fuse_conn,它就是内核fuse和用户态文件系统通信的
工具;
d)将该fuse_conn设置为在用户态打开的/dev/fuse的私有数据;同
时,该文件系统的super_block的s_fs_info也指向该fuse_conn。
2、注册用户态文件系统的处理函数,病创建进程处理该文件系统的请
求
a)创建一个新的fuse_chan,用来与内核中的fuse通信,它的处理函数是fuse_chan_ops;
b)创建一个fuse_session,并注册用户态文件系统的处理函数,用来等待并处理该文件系统的请求
c)等待用户态文件系统的请求,并处理它。
2.4.4、FUSE用户态模块的业务逻辑结构
在用户态文件系统的main()函数中调用文件fuse_loop.c中的函数void fuse_session_add_chan(struct fuse_session *se, struct fuse_chan *ch),将通道地址赋值给该会话结构体struct fuse_session se, 然后通过调用同一文件中的函数int fuse_session_loop(struct fuse_session *se)来做信息的接收与发送工作。
struct fuse_session {
struct fuse_session_ops op; //会话的操作
void *data;
volatile int exited;
struct fuse_chan *ch;
};
在函数int fuse_chan_recv(struct fuse_chan **chp, char *buf, size_t size)中调用结构体struct fuse_chan 中绑定的操作函数static int fuse_kern_chan_receive(struct fuse_chan **chp, char *buf, size_t size)直接调用read()系统函数res = read(fuse_chan_fd(ch), buf, size); 来从/dev/fuse中读取消息。
//通道操作的动作绑定结构体
struct fuse_chan_ops op = {
.receive = fuse_kern_chan_receive,
.send = fuse_kern_chan_send,
.destroy = fuse_kern_chan_destroy,
};
在函数void fuse_session_process(struct fuse_session *se, const char *buf, size_t len, struct fuse_chan *ch)中调用已绑定的se->op.process()函数转向文件./lib/fuse_lowlevel.c中被绑定函数static void fuse_ll_process(void *data, const char *buf, size_t len, struct fuse_chan *ch)。
struct fuse_session_ops sop = {
.process = fuse_ll_process,
.destroy = fuse_ll_destroy,
};
在这个函数处理过程中,首先要申明一个请求结构体指针struct fuse_req *req, 然后通过在read(fuse_chan_fd(ch), buf, size); 函数执行中读取的数据buf,且将其强制转化为fuse_in_header类型(struct fuse_in_header *in = (struct fuse_in_header *) buf;),并初始化请求结构体req。
struct fuse_req {
}
通过解析出in->opcode的数据和用户态文件系统定义的已绑定的接口函数相比较,得到相应的处理函数,最后执行这个函数完成请求。
2.4.5、FUSE内核模块的业务逻辑结构
FUSE 作为用户态文件系统与内核交互的桥梁,拥有着2个模块。
其一是与用户态文件系统直接交互模块,如上图中的libfuse接口。
其二是内核中挂载到VFS上的FUSE内核模块。
FUSE信息处理流程:
当用户发起一个请求ls –l /tmp/fuse 时,操作系统调用内核函数接口glibc 将该请求发送到内核态VFS;VFS根据请求判断出需要调用的文件系统(FUSE 已经挂到VFS的文件系统列表上)并将此请求发送到FUSE内核模块;由内核模块再将此请求发送到特殊文件/dev/fuse的请求队列中;而用户态的libfuse 则不停的循环请求/dev/fuse文件中的请求队列,如获得请求则解析该请求,并发送给挂在其上的用户态文件系统,由它来执行请求。
在用户态文件系统执行完该请求后,按照逆方向返回执行结果给用户。
当用户发出请求,若请求为:”rm /mnt/fuse/file”时,操作系统直接调用VFS 函数sys_unlink()。
通过VFS的转向,调用已注册的“FUSE文件系统”(FUSE内核模块)的函数fuse_unlink()。
FUSE内核模块将接收到的请求保存到结构体struct fuse_conn fc的fc->unused_list中,通过函数request_send()函数将其写入fc->pending,同时使用函数fuse_dev_writev()将此请求写到特殊文件/dev/fuse中。
2.4.6、内核中/dev/fuse读写详细流程
1) 前提介绍
当VFS要下发命令到FUSE或者从FUSE获取恢复时,首先找到与FUSE绑定的接口函数结构体(在dev.c中)。
struct file_operations fuse_dev_operations = {}
2)读取/dev/fuse中的请求
在内核中要读取/dev/fuse中的请求数据时,最终要调用文件dev.c中的函数static ssize_t fuse_dev_readv(struct file *file, const struct iovec *iov, unsigned long nr_segs, loff_t *off),此函数首先通过传入的参数file来初始化一个struct fuse_conn *fc结构体。
然后调用请求等待函数static void request_wait(struct fuse_conn *fc),先申明并初始化一个进程等待列表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)) {}
如果有请求,则break出来将当前进程状态设置为TASK_RUNNING状态,set_current_state(TASK_RUNNING);并将其从等待队列中移出,remove_wait_queue(&fc->waitq, &wait) 。
在fc->pending列表为非空时,证明已经有数据到达,那么作一些相应的状态修改,初始化等工作后,判断这个请求是否合法,如果是合法请求,这从fc->pending列表中移除并拷贝其请求数据到用户空间的缓冲区。
如果该请求是不需要回复的、意外终止、出现错误或者正常结束的情况下调用函数static void request_end(struct fuse_conn *fc, struct fuse_req *req)来作一些处理后结束本次请求读取过程。
3)将请求发送到/dev/fuse中
首先申明一个结构体fc, struct fuse_conn *fc = fuse_get_conn(file); 然后检查当前的设备状态,如果没问题则初始化一个拷贝状态。
static void fuse_copy_init(struct fuse_copy_state *cs, struct fuse_conn *fc,
然后在当前的处理列表中查找写缓冲区的请求。
如果找到,然后从列表中删除它并复制缓冲区的其余部分。
完成该请求通过调用函数static void request_end(struct fuse_conn *fc, struct fuse_req *req)。
2.3、总结
FUSE内核模块主要工作就是进行队列的管理,对fuse设备的读(写)其实就是从相应的队列移除(添加)请求(或响应),request_send将请求加入pending队列,唤醒fuse守护程序,并在req的waitq上等待请求结果,守护程序通过fuse_dev_readv从pending队列中移除请求并处理,处理完成后,守护程序唤醒req的waitq上的进程,该进程读取结果,并返回给用户。
总的来说,一个请求从发起到完成会经过4步:
1)fuse守护程序在fc的waitq上等待请求;
2)用户的请求唤醒fc的waitq,从该waitq上移除一个请求进行处理,并在req的waitq上等待请求结果;
3)fuse守护程序被唤醒,读取请求,处理请求,返回结果,唤醒对应req 上的waitq队列。
4)请求被唤醒,读取fuse守护程序返回的结果,返回给用户。
3、NTFS中的rm命令流程Rm命令的整体流程,如图3 -1所示。
图3 -1 rm命令的整体流程图Rm命令的详细流程,如图3 -2所示。
根据magic值的不同,有两种返回值,一种返回0,为正常情况,一种返回非0,为错误情况。