FUSE概述

FUSE(用户态文件系统)是一个实现在用户空间的文件系统框架,通过FUSE内核模块的支持,使用者只需要根据fuse提供的接口实现具体的文件操作就可以实现一个文件系统。
在fuse出现以前,Linux中的文件系统都是完全实现在内核态,编写一个特定功能的文件系统,不管是代码编写还是调试都不太方便,就算是仅仅在现有传统文件系统上添加一个小小的功能,因为是在内核中实现仍需要做很大的工作量。在用户态文件系统FUSE出现后(2.6内核以后都支持fuse),就会大大的减少工作量,也会很方便的进行调试。编写FUSE文件系统时,只需要内核加载了fuse内核模块即可,不需要重新编译内核。
FUSE 特点

  1.     用户空间文件系统——类Unix OS的框架
  2.     允许非超户在用户空间开发文件系统
  3.     内核的API接口,使用fs-type操作
  4.     支持多种编程语言( c、c++、perl、java 等)
  5.     普通用户也可以挂载FUSE
  6.     不用重新编译内核

FUSE组成

fuse主要由三部分组成:FUSE内核模块、用户空间库libfuse以及挂载工具fusermount。

  1.     fuse内核模块:实现了和VFS的对接,实现了一个能被用户空间进程打开的设备,当VFS发来文件操作请求之后,将请求转化为特定格式,并通过设备传递给用户空间进程,用户空间进程在处理完请求后,将结果返回给fuse内核模块,内核模块再将其还原为Linux kernel需要的格式,并返回给VFS;
  2.     fuse库libfuse:负责和内核空间通信,接收来自/dev/fuse的请求,并将其转化为一系列的函数调用,将结果写回到/dev/fuse;提供的函数可以对fuse文件系统进行挂载卸载、从linux内核读取请求以及发送响应到内核。libfuse提供了两个APIs:一个“high-level”同步API 和一个“low-level” 异步API 。这两种API 都从内核接收请求传递到主程序(fuse_main函数),主程序使用相应的回调函数进行处理。当使用high-level API时,回调函数使用文件名(file names)和路径(paths)工作,而不是索引节点inodes,回调函数返回时也就是一个请求处理的完成。使用low-level API 时,回调函数必须使用索引节点inode工作,响应发送必须显示的使用一套单独的API函数。
  3.     挂载工具:实现对用户态文件系统的挂载

FUSE主要代码文件
in kernel:

  1.     kernel/inode.c —> 主要完成fuse文件驱动模块的注册,提供对supper block的维护函数以及其它(驱动的组织开始文件)
  2.     kernel/dev.c —> fuse 的(虚拟)设备驱动
  3.     kernel/control.c —> 提供对于dentry的维护及其它
  4.     kernel/dir.c —> 主要提供对于目录inode索引节点的维护
  5.     kernel/file.c —> 主要提供对于文件inode索引节点的维护

in userspace:

  1.     lib/helper.c —> “fuse_main()”调用的主入口
  2.     lib/fuse_kern_chan.c—>主要实现fuse应用层访问(读写)fuse driver的功能
  3.     lib/fuse_mt.c —> fuse 的mount管理
  4.     lib/fuse.c —> lib库主框架文件,实现了主要框架及对”用户实现的文件系统操作代码”的封装
  5.     lib/fuse_lowlevel.c –> 实现比较底层的函数封装,供fuse.c等使用
  6.     lib/fuse_loop.c —> fuse lib循环监视”fuse driver”的通信缓存
  7.     lib/fuse_loop_mt.c —> 同fuse_loop.c
  8.     lib/fuse_session.c —> fuse会话管理

Fuse是怎么工作的(fuse-2.9)?
1. fuse库

    1.在用户态程序调用fuse_main() (lib/helper.c)时,先调用fuse_setup_common()该函数先解析用户态程序传递过来的参数,然后调用fuse_mount_common()(该函数是fuse_kern_mount()函数的封装,lib/mount.c)。
    fuse_main()是一个宏定义(include/fuse.h),如下:

 

#define fuse_main(argc, argv, op, user_data)                            \

        fuse_main_real(argc, argv, op, sizeof(*(op)), user_data)

 

    2.fuse_kern_mount()函数中调用fuse_mount_fusermount()使用socketpair()创建一个UNIX域套接字,然后使用创建子进程执行fusermount程序,将FUSE_COMMFD_ENV环境变量中套接字的一端传递给它。
    3.fusermount(util/fusermount.c)确保fuse 模块已经被加载,然后打开/dev/fuse并通过一个UNIX套接字发送文件处理句柄。父进程等待子进程执行完毕回收,然后返回fuse_mount_fusermount()函数。
    4.fuse_kern_mount()通过/dev/fuse返回文件句柄给fuse_kern_chan_new()负责处理内核数据,然后返回到fuse_mount_common()函数。
    5.fuse_setup_common()函数调用fuse_new_common(lib/fuse.c),fuse_new_common()函数分配fuse数据结构,存储并维护一个文件系统数据镜像缓存cached,返回到fuse_main()。
    6.最后,fuse_main()调用fuse_loop(lib/fuse.c)或者fuse_loop_mt()(lib/fuse_mt.c),这两个函数都可以从设备/dev/fuse读取文件系统调用,调用fuse_main()之前调用存储在fuse_operations结构体中的用户态函数。这些调用的结果回写到/dev/fuse设备(这个设备可以转发给系统调用)。

用户态文件系统fuse学习_fuse
2.内核模块

内核模块由2个部分组成:

第一个是proc文件系统组件(在kernel/dev.c中);

第二个是文件系统调用(kernel/file.c、kernel/inode.c、kernel/dir.c)。
kernel/file.c、kernel/inode.c、kernel/dir.c中的所有系统调用要么调用request_send(),要么调用request_send_noreply()或者request_send_nonblock()。大部分都是调用request_send()函数,它添加请求到“list of request”结构体(fc->pending),然后等待一个响应。request_send_noreply()和request_send_nonblock()与request_send()函数相似,除了是非阻塞的和不响应一个回复。
kernel/dev.c中的proc文件系统组件响应文件IO请求,fuse_dev_read()处理文件读,并从请求列表结构体(list of requests)返回命令到调用程序。fuse_dev_write()处理文件写, 完成数据写并放入req->out结构体(它能返回系统调用通过请求列表结构体和request_send()函数),

用户态文件系统fuse学习_fuse_02
用户进程和操作系统进行交互(read文件为例):

该fuse文件系统挂载在现有ext4文件系统之上.

1.一个用户进程发出read文件请求;
2.该请求被转换为一个内核系统调用,内核VFS层调用fuse文件系统内核模块;
3.fuse 内核模块通过/dev/fuse,将read请求传递到fuse 用户态进程;
4.fuse daemon根据用户实现的read接口,产生新的系统调用,最终调用ext4文件系统的read操作函数,从存储介质中提取读操作要求的数据(page cache中有,直接从其中获取,否则读磁盘);
5.内核将数据返回给fuse文件系统;
6.用户级文件系统再次调用内核操作,把数据返回给用户进程;
7.内核将数据传给用户进程完成操作。
库函数fuse_main()具体处理流程:

1.先打开设备文件/dev/fuse;
2.然后挂载FUSE文件系统;
3.产生FUSE文件系统指针;
4.初始化FUSE文件系统的操作函数集:
5.初始化信号处理函数集;
6.进入等待循环:
从设备文件/dev/fuse中读取来自内核模块的请求;
运行相应的操作函数,并获取返回结果;
将返回给内核的应答结果写入设备文件/dev/fuse中;
注意

fuse本质上(数据处理时)是处于现有文件系统之上的(具体实现是和现有文件系统处于同一个层次的),fuse不参与底层磁盘数据的存取,只负责处理对读取和写入的数据在逻辑上的操作而已。
安装使用fuse

软件包下载https://github.com/libfuse/libfuse/releases:
下载软件包,解压后,编译安装。

 

./configure
make
make install

 

解压后的目录中有名为example的目录,其中有fuse自带的几种fuse用户态实现例子,可以运行其进行测试。
fuse安装完后,该目录中的文件也已经编译成功,只需运行即可(目录dir为挂载点,挂载成功后,在该目录中对文件的操作就会调用fuse自己实现的操作函数):

 

   

    
//example/hello.c 部分代码,主要实现文件系统的取文件属性、打开目录、读文件、打开文件   

static struct fuse_operations hello_oper = {    //文件操作函数,为回调函数

        .getattr        = hello_getattr,
        .readdir        = hello_readdir,
        .open           = hello_open,
        .read           = hello_read,
};  
int main(int argc, char *argv[])
{
        return fuse_main(argc, argv, &hello_oper, NULL); //主函数,会调用文件操作结构体

}
 

ty@ubuntu:~/program/fuse/fuse-2.9.6/example/dir$ mkdir dir    //创建挂载点
ty@ubuntu:~/program/fuse/fuse-2.9.6/example/dir$ ./hello dir   //将hello fuse文件系统挂载在dir目录上

 

根据目前实现的四个功能进行测试如下:

ty@ubuntu:~/program/fuse/fuse-2.9.6/example/dir$ ls
hello 
ty@ubuntu:~/program/fuse/fuse-2.9.6/example/dir$ cat hello
Hello World!
ty@ubuntu:~/program/fuse/fuse-2.9.6/example/dir$ ls -l hello
-r--r--r-- 1 root root 13 12月 31  1969 hello
ty@ubuntu:~/program/fuse/fuse-2.9.6/example/dir$ rm -fr hello
rm: cannot remove ‘hello’: Function not implemented
    
ty@ubuntu:~/program/fuse/fuse-2.9.6/example/dir$ touch test
touch: cannot touch ‘test’: Function not implemented
ty@ubuntu:~/program/fuse/fuse-2.9.6/example/dir$ mkdir test
mkdir: cannot create directory ‘test’: Function not implemented

       

 

由测试可以发现在dir目录中只能对文件进行实现的四个功能的操作,其他的操作都无法完成,会提示用户函数没有实现。

ty@ubuntu:~/program/fuse/fuse-2.9.6/example/$ dirfusermount -u dir //卸载fuse文件系统dir