赞
踩
fuse即Filesystem in Userspace,用户空间文件系统,可以在应用程序中实现文件系统,能够在用户态使用标准的文件操作,如cat、ls、grep、重定向等功能,虽然效率比内核态要低,但胜在方便。很多场景下用起来还是非常方便的。
fuse本身是内核提供的一个功能,内核开启fuse支持后,会在/dev目录下,生成fuse设备节点,应用层可以通过该设备节点完成用户态文件系统。
libfuse是一个对fuse功能封装的库,提供一系列的api,使用户可以更方便、更简单的使用fuse功能,后面的内容都是基于libfuse来说明的。
目前个人有嵌入式设备上使用fuse的场景,打造一个应用层的proc系统,使用libuse实现起来还是非常方便的,下面主要介绍fuse的基本用法。
libfuse用的还是比较广,它的主页上介绍了一些基于libfuse打造的文件系统,但这个库文档不是很多,文档都是doxygen生成的,也就是说看官方文档,和看头文件差不多,代码里面的注释还是很全面的,实际使用的过程中,看注释也能用,但有些地方还是不容易弄明白。
fuse需要内核支持,编译内核时,需把fuse功能选上,fuse可以编译成模块:
File systems —>
< > FUSE (Filesystem in Userspace ) support
fuse驱动加载完成后,可以在/dev/看到创建了一个名为fuse的设备文件,fuse即通过此设备来中转用户态与内核间的信息。
libfuse-fuse-3.2.6.tar.gz需使用meson-0.47.1.tar.gz进行编译,meson又依赖python等,由于个人需求与libfuse版本关系不大,所有用了libfuse-fuse-2.9.8.tar.gz版本
编译过程:
sw@sw:libfuse-fuse-2.9.8$ ./makeconf.sh
sw@sw:libfuse-fuse-2.9.8$ ./configure --host=arm-linux
sw@sw:libfuse-fuse-2.9.8$ make -j4
编译完成后,在lib/.lib/目录下生成了libfuse.a,头文件在include目录下,示例程序在example目录下。
如要查看编译选项,可以使用make V=1编译,这样就可以看到编译选项。
如果需要增加或修改编译选项(如嵌入式可以把O2改成Os,减少库大小),可以在configure.ac中增加,如:
CFLAGS="-Wall -W -Wno-sign-compare -Wstrict-prototypes -Wmissing-declarations -Wwrite-strings -g -O2 -fno-strict-aliasing -ffunction-sections -fdata-sections "
这里增加了 -ffunction-sections -fdata-sections,为了减少应用程序大小(效果不明显),大家可以根据要求改编译选项。
libfuse提供了几个示例程序,都在example目录下:
验证测试程序,这里只是看一下基本用法,进到example目录下,先确认一下设备存在
sw@sw:example$ ls /dev/fuse
/dev/fuse
sw@sw:example$ mkdir hfs
sw@sw:example$ ./hello hfs
sw@sw:example$ ls -l hfs/
total 0
-r–r--r-- 1 root root 13 1月 1 1970 hello
sw@sw:example$ cat hfs/hello
Hello World!
使用ps可以看到./hello hfs是在后台运行的,此时hfs不能操作(如进到这个目录创建文件),如想删除hfs文件夹,需先umount
sw@sw:example$ sudo umount hfs
如果umount之前,进程被杀,对该文件操作会报错:Transport endpoint is not connected,此时umount一下就可以了。
这个hello程序也可以前台运行,libfuse支持很多选项,先看一下:
sw@sw:example$ ./hello -h
usage: /home/sw/mypro/libfuse-fuse-2.9.8/example/.libs/lt-hello mountpoint [options]general options:
-o opt,[opt…] mount options
-h --help print help
-V --version print versionFUSE options:
-d -o debug enable debug output (implies -f)
-f foreground operation
-s disable multi-threaded operation
-o allow_other allow access to other users
-o allow_root allow access to root
-o auto_unmount auto unmount on process termination
…
libfuse支持很多选项,如
加上参数-d与-f再次运行hello程序,如下:
sw@sw:example$ ./hello hfs -d -f
FUSE library version: 2.9.8
nullpath_ok: 0
nopath: 0
utime_omit_ok: 0
unique: 1, opcode: INIT (26), nodeid: 0, insize: 56, pid: 0
INIT: 7.23
flags=0x0003f7fb
max_readahead=0x00020000
INIT: 7.19
flags=0x00000011
max_readahead=0x00020000
max_write=0x00020000
max_background=0
congestion_threshold=0
unique: 1, success, outsize: 40unique: 2, opcode: GETATTR (3), nodeid: 1, insize: 56, pid: 11375
getattr /
unique: 2, success, outsize: 120
unique: 3, opcode: GETATTR (3), nodeid: 1, insize: 56, pid: 11379
getattr /
unique: 3, success, outsize: 120
unique: 4, opcode: OPENDIR (27), nodeid: 1, insize: 48, pid: 11379
unique: 4, success, outsize: 32
unique: 5, opcode: READDIR (28), nodeid: 1, insize: 80, pid: 11379
readdir[0] from 0
…
可以看到,有一些调试信息,在其他终端cat一下hello文件,会看到各种opcode(operate code操作码),可以根据此调试信息来决定代码中某接口是否需实现。
此时再ctrl+c退出程序,再用ps查看,可以看到程序已退出了,这就是前台运行。
大家如果有调试测试程序的需求(适用gdb跟踪,熟悉api用法,或看源码),要注意了,测试程序都是用libtool编译的,生成的都是脚本,不能直接调试,大家可以make V=1查看编译选项,如下:
/bin/sh …/libtool --tag=CC --mode=link gcc -Wall -W -Wno-sign-compare -Wstrict-prototypes -Wmissing-declarations -Wwrite-strings -g -O2 -fno-strict-aliasing -o hello hello.o …/lib/libfuse.la
改成
gcc -Wall -W -Wno-sign-compare -Wstrict-prototypes -Wmissing-declarations -Wwrite-strings -g -O2 -fno-strict-aliasing -o hello hello.o …/lib/.libs/libfuse.a -lpthread -ldl
这样就可以编译出hello可执行程序了,需链接pthread和dl库。
libfuse提供了3套不同层次的接口,大家可以根据自己情况选用。
前面hello例子中用的fuse_main就是最简单的接口,只需要实现一个struct fuse_operations类型的操作函数,就可以完成一个用户态文件系统。用法简单,此接口退出控制比较麻烦,默认捕获了SIGHUP、SIGINT、SIGTERM、SIGPIPE等几个信号,收到这几个信号就退出,不灵活。一般用来功能验证。
在fuse.h还提供了一套基本接口,此接口能满足一般程序需求,退出控制较完善,只是初始化去初始化不同,操作函数编程与简单接口一致,适用于简单的应用。函数调用方式与说明如下:
/* fuse_parse_cmdline函数不是必须的,mountpoint可以直接写死目录,可以不从参数里获取 */ int fuse_parse_cmdline(struct fuse_args *args, char **mountpoint, int *multithreaded, int *foreground); /* 创建挂载点,args可以为NULL,返回的是一个fuse所谓的channel */ struct fuse_chan *fuse_mount(const char *mountpoint, struct fuse_args *args); /* 创建fuse操作函数,这里可以传入用户数据 */ struct fuse *fuse_new(struct fuse_chan *ch, struct fuse_args *args,const struct fuse_operations *op, size_t op_size,void *user_data); /* 循环,这个是阻塞的,可以通过fuse_exit退出 */ int fuse_loop(struct fuse *f); /* 释放以上函数资源 */ void fuse_exit(struct fuse *f); void fuse_unmount(const char *mountpoint, struct fuse_chan *ch); void fuse_destroy(struct fuse *f); void fuse_opt_free_args(struct fuse_args *args);
示例程序里并没有基本接口的用法,这里在hello的基础上改了一个测试程序,大家可以参考:
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <errno.h> #define FUSE_USE_VERSION 26 #include "fuse.h" int user_data = 2018; static const char *hello_str = "Hello World!"; static const char *hello_path = "/hello"; void thread_fuse(void * arg) { struct fuse * fuse = arg; fuse_loop(fuse); } static int api_getattr(const char *path, struct stat *stbuf) { int res = 0; memset(stbuf, 0, sizeof(struct stat)); if (strcmp(path, "/") == 0) { stbuf->st_mode = S_IFDIR | 0755; stbuf->st_nlink = 2; } else if (strcmp(path, hello_path) == 0) { stbuf->st_mode = S_IFREG | 0444; stbuf->st_nlink = 1; /* 注意,如不清楚具体长度,这个长度需写长一点,否则无法输出 */ stbuf->st_size = 1024; } else res = -ENOENT; return res; } static int api_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { if (strcmp(path, "/") != 0) return -ENOENT; filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); filler(buf, hello_path + 1, NULL, 0); return 0; } static int api_open(const char *path, struct fuse_file_info *fi) { if ((fi->flags & 3) != O_RDONLY) return -EACCES; /* 参考fuse_file_info结构体的注释,可以在open时填充fh的值,这个就是open的handle,后续read、write中都可以用 */ fi->fh = 1234; return 0; } static int api_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { if(strcmp(path, hello_path) != 0) return -ENOENT; /* 通过fuse_get_context可以取出fuse_new传入的用户参数,该函数在fuse_operations所有的函数中都可以调用 */ struct fuse_context * fc = fuse_get_context(); int * data = fc->private_data; int len = 0; /* 长度返回0时,本次read就结束了 */ if( *data % 2 == 0) { len = sprintf(buf,"%s user_data:%d open handle:%lld \n",hello_str,*data,fi->fh); } (*data)++; return len; } static struct fuse_operations api_oper = { .getattr = api_getattr, .readdir = api_readdir, .open = api_open, .read = api_read, }; #define MOUNT_POINT "hfs" int main() { char * mountpoint = NULL; char * argv[] = {"usfs", "-d", MOUNT_POINT, NULL}; struct fuse_args args = FUSE_ARGS_INIT(3, argv); fuse_parse_cmdline(&args, &mountpoint, NULL, NULL); struct fuse_chan * ch = fuse_mount(mountpoint, &args); struct fuse * fuse = fuse_new(ch, &args, &api_oper, sizeof(struct fuse_operations), &user_data); pthread_t tid_fuse; pthread_create(&tid_fuse, NULL, (void *)thread_fuse, fuse); while (getchar() != 'q') { usleep(1000); } fuse_exit(fuse); /* 退出fuse_loop */ fuse_unmount(mountpoint, ch); /* 对应fuse_mount */ fuse_destroy(fuse); /* 对应fuse_new */ fuse_opt_free_args(&args); /* fuse_parse_cmdline里面会申请内存,需释放 */ free(mountpoint); return 0; }
程序没有错误处理,仅供参考,编译方式如下:
gcc test.c -o test -D_FILE_OFFSET_BITS=64 -I./include -L./lib -lfuse -lpthread -ldl
需用到fuse的头文件和库,头文件在include目录下,库在lib/.libs目录下。
上面例子用到了几个特性:
sw@sw:hfs$ cat hello
Hello World! user_data:2024 open handle:1234
sw@sw:hfs$ cat hello
Hello World! user_data:2026 open handle:1234
可以看到user_data两次输出的不一样,在read中输出了open时给的fh值。
此测试程序可以按q退出,完成清理工作。
libfuse提供了low level接口,示例程序有几个例子是用该接口实现的。此类接口更灵活,使用稍微复杂一些。libfuse的注释就是文档,非常详细,编程的时候根据注释提示来就可以了。
/** * Read data * * Read should send exactly the number of bytes requested except * on EOF or error, otherwise the rest of the data will be * substituted with zeroes. An exception to this is when the file * has been opened in 'direct_io' mode, in which case the return * value of the read system call will reflect the return value of * this operation. * * fi->fh will contain the value set by the open method, or will * be undefined if the open method didn't set any value. * * Valid replies: * fuse_reply_buf * fuse_reply_iov * fuse_reply_data * fuse_reply_err * * @param req request handle * @param ino the inode number * @param size number of bytes to read * @param off offset to read from * @param fi file information */ void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi);
看这个read的注释,read需配合fuse_reply_buf、fuse_reply_iov、fuse_reply_data、fuse_reply_err这几个接口来完成功能,每个fuse_operations的有效接口都不一样,详细的用法大家可以参考人家写好的文件系统。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。