fuse接口用法说明

fuse接口用法说明

fuse介绍

fuse即Filesystem in Userspace,用户空间文件系统,可以在应用程序中实现文件系统,能够在用户态使用标准的文件操作,如cat、ls、grep、重定向等功能,虽然效率比内核态要低,但胜在方便。很多场景下用起来还是非常方便的。

fuse本身是内核提供的一个功能,内核开启fuse支持后,会在/dev目录下,生成fuse设备节点,应用层可以通过该设备节点完成用户态文件系统。

libfuse是一个对fuse功能封装的库,提供一系列的api,使用户可以更方便、更简单的使用fuse功能,后面的内容都是基于libfuse来说明的。

目前个人有嵌入式设备上使用fuse的场景,打造一个应用层的proc系统,使用libuse实现起来还是非常方便的,下面主要介绍fuse的基本用法。

libfuse用的还是比较广,它的主页上介绍了一些基于libfuse打造的文件系统,但这个库文档不是很多,文档都是doxygen生成的,也就是说看官方文档,和看头文件差不多,代码里面的注释还是很全面的,实际使用的过程中,看注释也能用,但有些地方还是不容易弄明白。

libfuse的编译

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目录下:

  • hello.c :最简单的libfuse用法,基本api用法
  • hello_ll.c :ll是low level的缩写,展示了低层次接口的基本用法
  • null.c :实现了一个类似linux下的/dev/null的设备
  • fioc.c/fioclient.c :fioc是fuse ioctl的缩写,这个例子展示的就像很多驱动一样,能够使用ioctl接口进行交互
  • fsel.c/fselclient.c : fsel是fuse select缩写,比较高级的用法。
  • cusexmp.c/fusexmp.c/fusexmp_fh.c : 这几个例子都是文件映射相关的,有一定的实用意义,实现的接口较全面。

验证测试程序,这里只是看一下基本用法,进到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 version

FUSE 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支持很多选项,如

  • -o 这个选项就像mount的-o选项一样,可以定义一些挂载选项,
  • -d 这是个调试选项,这个选项建议在开发阶段都加上,对调试帮助很大
  • -f 这个就是让程序运行到前台的选项,如不使用fuse_main这种最简单的api,选项用处就不大

加上参数-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: 40

unique: 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的api使用

libfuse提供了3套不同层次的接口,大家可以根据自己情况选用。

简单(lazy)接口

前面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目录下。
上面例子用到了几个特性:

  • 用户参数通过fuse_new传进去,在所有的fuse_operations中,都可以通过fuse_get_context来得到这个user_data的指针
  • open操作的时候可以把句柄写入到fh中,后续其他操作可以获取到该fh的值
  • read操作,最后一次需return 0,且getattr长度需正确,否则得不到正确的输出
    以上测试程序输出如下:

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的有效接口都不一样,详细的用法大家可以参考人家写好的文件系统。

  • 11
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值