使用用户空间的文件系统(FUSE),无需理解文件系统的内幕,也不用学习内核模块编程的知识,就可以开发用户空间的文件系统框架。
步骤1 FUSE的下载与安装
1)首先上fuse官网下载最新安装包:http://sourceforge.net/projects/fuse/files/fuse-2.X/
我下载的是2.8.5版。
2)安装fuse,根据官网教程:
①首先解压: tar zxvf fuse-2.8.5.tar.gz
②然后到解压后的文件夹fuse-2.8.5下,输入: ./configure
③编译: make
④make完后再输入: make install(注意:这一步要在root用户模式下做)
⑤查看fuse是否挂载成功: lsmod|grep fuse
若没成功则可通过:“modprobe fuse”命令挂载fuse。
到此,fuse就已成功安装到虚拟机上了~
步骤2 FUSE的使用
安装完fuse后,其子文件夹example下有一些做好的文件系统实例,例如fusexmp、hello等,下面以fusexmp为例,教你挂载和使用该文件系统,并修改相应函数。
1)将fusexmp文件系统挂载到/mnt/fuse目录(fuse文件夹需要先在mnt下自己建),在example文件夹下输入:./ fusexmp /mnt/fuse –d
这样在/mnt/fuse文件夹下就可以看到所有根目录文件夹了,不过/mnt/fuse文件夹下是fusexmp文件系统!
另外有两点值得注意:
①该命令不会自动结束,使用过程中也不可以结束,所以如何再在终端中输入命令呢?推荐一种方法,按快捷键:Shift+Ctrl+T,会在新标签页新开一个终端,这样就有两个终端了,当然你也可以重新开一个终端窗口。如果的确需要退出第一个终端,则可按Ctrl+C。
②可用“df”命令查看是否挂在上该文件系统。
2)现在转到/mnt/fuse目录下,就可使用fusexmp这个文件系统所实现的命令了~
fusexmp实现的命令可以在example文件夹下的fusexmp.c文件中看到,也可以很方便的修改,比如xmp_mkdir函数增加一条输出提示语句:
static int xmp_mkdir(const char *path, mode_t mode)
{
int res;
write(0,"You are using 'mkdir'!",24);
res = mkdir(path, mode);
if (res == -1)
return -errno;
return 0;
}
要使该修改有效还需要以下几步:
①回到第一个终端(就是输入./ fusexmp /mnt/fuse –d的那个终端),按Ctrl+C结束;
②重新编译:make;
③重新挂载:./ fusexmp /mnt/fuse –d
④在第二个终端中再进入/mnt/fuse目录下(若已在该目录或其子目录下得先退出后再进才有效),尝试mkdir命令,会发现在第一个终端中相应系统输出会有我们新增的输出语句~
首先,从FUSE提供的例子入手。经过了make之后, FUSE自带的例子都编译出了可执行程序。比如hello这个例子,在fuse/example/目录下新建目录tmp/然后运行 ./hello tmp/ 这样就将这个hello文件系统挂载到tmp/这个目录上了。打开这个目录(在终端中使用ls命令或者使用图形化的文件管理工具都可以),可以看到tmp目录下有一个hello文件,用文本编辑器打开这个文件,可以看到里面的字符串“helloworld”。由于这仅仅是一个最简单的示例性的文件系统,有些基本功能并不完备,比如hello文件中的内容不能被修改(修改了也没有办法保存,因为没有实现相应的操作。实际上,hello这个文件中的内容是在源码中直接写进去的)。如果要尝试更多的操作,可以使用example中fusexmp这个例子。这个例子是把根目录挂载到挂载点上,里面的操作也提供的比较完备。
遇到的问题,解决方案和注意事项
使用FUSE的程序的编译
通过configure生成的makefile文件,直接make即可将FUSE中所有的example都进行编译。但是如果要单独编译其中的某个例子,或者编译自己写好的文件系统,按照文档的说法是这样的:
Gcc -Wall `pkg-config fuse --cflags --libs` hello.c -o hello
注意“ ` ”是键盘左上角,数字1左边的那个按键,而不是单引号。但实际上使用这个命令编译时会报链接错误:“fuse_main_real()函数找不到实现……”之类的。这是一个很典型的链接错误,说明编译器只看到了函数的声明而没有找到这个函数的实现。实际上这个函数的实现在helper.c中,并且被编译到了相应的链接库中。究其原因,其实是gcc的命令行参数传入的顺序问题。`pkg-config fuse --cflags --libs`这个东西,可以把它理解成一个在fuse安装时定义的变量,可以在终端中输入echo `pkg-config fuse --cflags --libs`即可看到这个变量展开之后所表示的gcc的命令行参数,应该是这种形式“-D_FILE_OFFSET_BITS64 -I/usr/includefuse -pthread -lfuse -lrt=”。其中包含了一个-lfuse命令,它的作用是链接fuse相应的链接库,也就是告诉编译器去哪里能找到fuse_main_real()的实现。事实上gcc要求链接库的命令行参数要放在源代码文件之后,所以将上面的命令改成:
Gcc -Wall hello.c -o hello `pkg-config fuse --cflags --libs`
即可顺利通过编译。关于gcc的参数说明,具体详情参见gcc的官方文档,此处不再引用。
使用g++编译FUSE程序
FUSE除了提供C语言的API,还为多种语言提供了编程接口,比如Java,C#,Python等(详见FUSE主页上language binding)。对于C++来说,FUSE也提供了C++风格的API,但实际上由于C和C++的兼容性,C++也可以直接使用C的API,但是需要做出少许的修改。例如像结构体的指定初始化这种C99标准中的语法(也就是hello.c中fuse_operation结构体函数指针成员赋值的晦涩语法),g++是不能编译通过的。将其修改成在全局定义一个fuse_operation对象,然后在main函数中对这个fuse_operation对象的成员依次赋值即可。其余的命令行参数都和gcc相同了。
FUSE的一个非常有用的命令行参数-d
通过实现一些自定制的函数和FUSE提供的API对接起来从而可以完成整个文件系统的实现。那么怎样知道FUSE的某个API什么时候会被调用呢?或者说实现了一个API之后怎样测试它是否正确?这里就用到了FUSE的一个命令行参数-d。在使用FUSE程序进行挂载的时候,同时使用-d参数,这样在对自己的文件系统进行操作的时候就会在终端中打印一些调试信息。此时最好使用终端通过命令行的方式操作文件系统,如果使用图形化文件管理工具的话,可能一个操作就会打印几十甚至上百条调试信息,分析起来会很麻烦。调试信息中主要包括这样的信息:一个当前操作的序号,操作的名称(虽然和提供的API并不是一一对应,但大部分是和API具有相同名字),输入输出的数据量的大小以及该操作失败时的错误返回码。关于这些调戏信息的具体规格,并没有找到相关的详细说明,所幸大部分信息都不难理解。
FUSE程序的调试
关于FUSE的调试确实没有想到什么很好的方法。由于FUSE程序的特殊性质以及自身缺少系统级别的编程经验,没用成功的使用gdb对FUSE的代码直接进行跟踪。FUSE的官网上也推荐了一些系统调试工具,由于精力有限也没有一一尝试。一个简洁有效的方法是通过使用cerr打印一些调试信息,并且配合assert进行定位。另一方面,对自己的文件系统的接口部分进行充分的单元测试,这样可以大大降低和FUSE对接之后的调试成本。另外在当FUSE的接口只完成一部分的时候,对这些接口单独测试可能会出现一些很莫名奇妙的bug,比如我就遇到了这样的问题:实现了getattr和readdir命令后,在终端中使用ls命令,打印的文件和目录名有时候会多出一些多余的符号,有时候又会报“IO错误”。幸运的是,当我对FUSE的其他API进一步完善之后,这个问题神奇的自动消失了。好吧,首先要承认带着bug还继续添加代码是一件非常糟糕的事情,但是FUSE的所有API是一个整体,而终端在执行命令的过程中究竟在后面都做了什么事情,我们也不得而知。总之,我要说明的是,如果使用FUSE的过程中出现了一些和预期不符的东西,首先排除它不是由文件系统自身带来的,然后再确定对FUSE的使用方式是否恰当,然后确定测试思路是否正确。