【Linux】分析Fuse中libfuse源码

这篇具有很好参考价值的文章主要介绍了【Linux】分析Fuse中libfuse源码。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在Linux中,我们可以使用FUSE来进行自定义用户态文件系统的实现。编译example中的示例是学习FUSE的第一步,本文侧重于剖析FUSEclient端的源码。

(一) 下载libfuse源码,避免重复造轮子

Linux环境我们选择Ubuntu,方便我们查看一些Linux的相关手册。在准备接触FUSE之前,我们可以先上网搜索一下,或者你也可以快速翻一下手册,执行man 8 fuse,在手册里我们可以看到好几个名词概念,例如FUSEfilesystemlibfusefilesystem ownerclient

【Linux】分析Fuse中libfuse源码
至此,我们正式在手册里看到了libfuse的身影,然后我们去github上拉取源码,地址为 https://github.com/libfuse/libfuse 。截止该文完成,该库在github上最新版本为3.14。不过在查看Release Notes后,我们依然选择采用3.12版本进行源码分析。

【Linux】分析Fuse中libfuse源码
我们拉取代码至本地,git操作不再赘述。
接下来我们查看ReadMe文档,可以看到该工程依赖于meson构建工具,而不是依赖于我们熟悉的CMake,因此下一步,我们需要解读meson构建脚本,即各个meson.build

(二) 解读与分析meson构建脚本,便于转化为CMakeLists脚本

1. 理解meson基本语法

初识meson,我们依旧选择最官方的文档。进入meson官网。如下图,在网页左侧的导航栏中,我们可以先读一下简介、教程与示例,并大致浏览一下一些meson里的约定内容。

【Linux】分析Fuse中libfuse源码
此处介绍几个比较重要的函数方法,后续分析meson脚本用得上。

meson.get_compiler

示例:

cc = meson.get_compiler('c')

获取当前机器的C编译器,可进行编译参数配置。

project, library

示例:

project('libfuse3', ['c'], version: '3.12.0',
        meson_version: '>= 0.42',
        default_options: [
            'buildtype=debugoptimized',
            'cpp_std=c++11',
            'warning_level=2',
        ])
        
libfuse = library('fuse3', libfuse_sources, version:meson.project_version(), soversion: '3',                           include_directories: include_dirs, dependencies: deps, 
                  install: true, link_depends: 'fuse_versionscript', 
                  c_args: [ '-DFUSE_USE_VERSION=312',
                            '-DFUSERMOUNT_DIR="@0@"'.format(fusermount_path) ],
                  link_args: ['-Wl,--version-script,' + meson.current_source_dir()
                              + '/fuse_versionscript' ])

*project()*定义了该工程的一些基本信息,*library()*定义了预构建的库的一些信息,包括编译参数、链接参数等。

configuration_data, configure_file

示例:

cfg = configuration_data()
cfg.set_quoted('PACKAGE_VERSION', meson.project_version())
configure_file(output: 'config.h',
               configuration : cfg)

*configuration_data()创建一个可配置对象,用以配置一些宏定义,最后使用configure_file()*输出为config.h,我们的libfuse中需要其发挥全局定义的作用。如下为构建自动生成的内容。

// config.h
#define PACKAGE_VERSION "3.12.0"
add_project_arguments

示例:

add_project_arguments('-D_REENTRANT', '-DHAVE_CONFIG_H', '-Wno-sign-compare',
                      '-Wstrict-prototypes', '-Wmissing-declarations', '-Wwrite-strings',
                      '-fno-strict-aliasing', language: 'c')
add_project_arguments('-D_REENTRANT', '-DHAVE_CONFIG_H', '-D_GNU_SOURCE',
                     '-Wno-sign-compare', '-Wmissing-declarations',
                     '-Wwrite-strings', '-fno-strict-aliasing', language: 'cpp')

*add_project_arguments()*用以配置工程对不同编程语言的参数。

include_directories

示例:

include_dirs = include_directories('include', 'lib', '.')

*include_directories()*用以生成上下文可用的include目录。

dependency

示例:

thread_dep = dependency('threads')

*dependency()*用以对外部库进行依赖。

2. 拆解meson构建脚本

在libfuse工程中,我们通过meson结合ninja构建工具可以得到最终的构建产物,分别是libfuse3.sofusermount3fuse相关头文件
现在我们出于将该工程移植到Android项目中的目的,未来利用NDK搭配CMake来进行编译,于是需要拆解meson构建脚本,我们通过查阅meson官网上的文档,将多个meson脚本进行翻译。
乍一看,可能会觉得有点头疼,毕竟这里也有好一些构建脚本,静下心来,读懂它后就可以比较轻松地翻译为CMakeLists.txt文件了。
此处不进行通篇翻译,过于冗余,仅结合前文介绍几个片段作为参考示例。

libfuse = library('fuse3', libfuse_sources, version: meson.project_version(),
                  soversion: '3', include_directories: include_dirs,
                  dependencies: deps, install: true,
                  link_depends: 'fuse_versionscript',
                  c_args: [ '-DFUSE_USE_VERSION=312',
                            '-DFUSERMOUNT_DIR="@0@"'.format(fusermount_path) ],
                  link_args: ['-Wl,--version-script,' + meson.current_source_dir()
                              + '/fuse_versionscript' ])

pkg = import('pkgconfig')
pkg.generate(libraries: [ libfuse, '-lpthread' ],
             libraries_private: '-ldl',
             version: meson.project_version(),
             name: 'fuse3',
             description: 'Filesystem in Userspace',
             subdirs: 'fuse3')

正如前文所说,*library()*是用来创建目标产物库的,这段代码的意思是配置了库的相关头文件,源文件,链接依赖文件,C编译器参数,链接参数,我们可以等价地将其翻译为如下的CMake脚本语句。

add_library(fuse3

        SHARED

        fuse.c
        fuse_i.h
        fuse_loop.c
        fuse_loop_mt.c
        fuse_lowlevel.c
        fuse_misc.h
        fuse_opt.c
        fuse_signals.c
        buffer.c
        cuse_lowlevel.c
        helper.c
        modules/subdir.c
        mount_util.c
        fuse_log.c
        mount.c
        modules/iconv.c)
        
set_target_properties(fuse3
        PROPERTIES LINK_DEPENDS ${CMAKE_SOURCE_DIR}/fuse_versionscript
        LINK_FLAGS -Wl,--version-script,${CMAKE_SOURCE_DIR}/fuse_versionscript)

target_link_libraries(fuse3

        log
        dl)

如果你对于为什么我们没有显式链接libpthread库,那么可以参考这篇官网文档,可以帮助你了解在Android NDK中应该显式链接哪些需要的库。

(三) 理解versionscript与symver,为解读源码做准备

1. versionscript简介

在前文中可以看到,我们在编译时配置了链接依赖文件fuse_versionscript,同时也配置了--version-script参数。你如果知道这个知识点,那么可以跳过第三节,直接阅读下一节。否则的话,可以在此稍作停留,让我们补一补versionscript的知识。
versionscript是一个在链接器中的概念,意在给各个函数方法加上版本信息,这样做的用处就是可以指定某个函数方法的不同版本的实现。在源代码中,我们可以这样声明一个函数原型,跟普通的函数声明没有区别,然后我们可以进行对该函数的实现。
例如,在libfuse中有这样一个函数原型:

int fuse_parse_cmdline(struct fuse_args *args,
                       struct fuse_cmdline_opts *opts);

这时候你直接想在源码中进行定义的跳转时会发现,这失败了。我们的IDE并没有找到这个函数的定义,但是这又是可以被编译的,为什么呢?如果你直接查看汇编代码,那么你可以找到答案。不过我相信聪明的你会有正确的直觉,这是不是跟前面说的fuse_versionscript文件有关系。
我们打开这个文件,你可以看到(这里我们节选了一部分,只有3.12版本新增的这部分):

FUSE_3.12 {
	global:
		fuse_session_loop_mt;
		fuse_session_loop_mt_312;
		fuse_loop_mt;
		fuse_loop_mt_32;
		fuse_loop_mt_312;
		fuse_loop_cfg_create;
		fuse_loop_cfg_destroy;
		fuse_loop_cfg_set_idle_threads;
		fuse_loop_cfg_set_max_threads;
		fuse_loop_cfg_set_clone_fd;
		fuse_loop_cfg_convert;
		fuse_parse_cmdline;
		fuse_parse_cmdline_30;
		fuse_parse_cmdline_312;
} FUSE_3.4;

我们可以看到以fuse_parse_cmdline开头的函数还有fuse_parse_cmdline_30fuse_parse_cmdline_312这两个,似乎有一定的眉目了。我们看一下这个函数的声明与定义。

int fuse_parse_cmdline_312(struct fuse_args *args,
                           struct fuse_cmdline_opts *opts);

FUSE_SYMVER("fuse_parse_cmdline_312", "fuse_parse_cmdline@@FUSE_3.12")
int fuse_parse_cmdline_312(struct fuse_args *args,
                           struct fuse_cmdline_opts *opts) { /* ... */ }
                           
int fuse_parse_cmdline_30(struct fuse_args *args,
                          struct fuse_cmdline_opts *opts);

FUSE_SYMVER("fuse_parse_cmdline_30", "fuse_parse_cmdline@FUSE_3.0")
int fuse_parse_cmdline_30(struct fuse_args *args,
                          struct fuse_cmdline_opts *opts) { /* ... */ }

我们看到这两个函数定义与声明之间都有一个宏:

#define FUSE_SYMVER(sym1, sym2) __asm__("\t.symver " sym1 "," sym2);

我们可以看到调用了汇编函数,并指定了.symver,这其实就是符号版本。同时我们注意到,这两个宏调用中都有fuse_parse_cmdline的身影,但是@的数量不同。简单理解的话,@@就是当前默认使用的函数定义的意思,而@是非默认的意思。至于如何在链接时使用指定版本的函数定义,希望读者可以去自行探究。

2. 使用nm命令查看函数的symver

接下来我们在Linux环境中通过nm命令查看一下so库中的各个函数的symver信息。

nm -D /usr/local/lib/x86_64-linux-gnu/libfuse3.so

可以看到下面这些信息(仅节选部分):
【Linux】分析Fuse中libfuse源码
我们看到fuse_parse_cmdline@@FUSE_3.12的地址指向01e8d0,而fuse_parse_cmdline_312@@FUSE_3.12的地址也指向该位置。因此得以确定,我们在实现中调用fuse_parse_cmdline函数时,实际的调用目标是fuse_parse_cmdline_312函数。

(四) 结合NDK与CMake交叉编译,进入Android的fuse世界

1. NDK中移除了pthread_setcancelstate与pthread_cancel

在NDK中,我们经常可以看到有很多的库函数受限于Android的API版本,这意味着高版本受限的API必须满足工程的minSdkVersion ≥ 受限API版本的情况下才可以被编译,否则编译器会拒绝编译。比如NDK21.4.7075529的pthread.h中定义了以下一些版本受限的函数(仅节选):

#if __ANDROID_API__ >= __ANDROID_API_N__
int pthread_barrierattr_init(pthread_barrierattr_t* __attr) __INTRODUCED_IN(24);
int pthread_barrierattr_destroy(pthread_barrierattr_t* __attr) __INTRODUCED_IN(24);
int pthread_barrierattr_getpshared(const pthread_barrierattr_t* __attr, int* __shared) __INTRODUCED_IN(24);
int pthread_barrierattr_setpshared(pthread_barrierattr_t* __attr, int __shared) __INTRODUCED_IN(24);
#endif

#if __ANDROID_API__ >= 26
int pthread_getname_np(pthread_t __pthread, char* __buf, size_t __n) __INTRODUCED_IN(26);
#endif /* __ANDROID_API__ >= 26 */

#if __ANDROID_API__ >= 28
int pthread_setschedprio(pthread_t __pthread, int __priority) __INTRODUCED_IN(28);
#endif /* __ANDROID_API__ >= 28 */

我们可以看到有的是API24(7.0),API26(8.0)等等之类的限定介绍。

NDK为什么要移除上述两个函数

问题回到pthread_setcancelstatepthread_cancel两个函数为什么被Google从NDK中移除,这个问题的原因之一是线程被标记结束后不一定会把自己拥有的资源释放掉,甚至不一定会结束,因此很可能造成内存泄露或死锁等问题,而这些问题在移动设备上更加突出。我们通过查看Linux上的pthread_cancel的man page可以看到,该库函数内部实际使用了信号作为实现手段。然而在libfuse的实现中我们可以看到里面调用到了这两个函数,那么下一个问题就是,如何自定义实现这两个函数的行为。

如何进行类似的行为平替

通过查找与搜索,不难找到网上说的使用pthread_kill函数来进行代替实现。
那么我们也对该方案进行了采纳,目的是先让我们的工程通过NDK环境的编译,并成功运行起来。以下是我们定义的pthread_setcancelstatepthread_cancel的行为。

#define SIG_CANCEL_SIGNAL SIGUSR1
#define PTHREAD_CANCEL_ENABLE 1
#define PTHREAD_CANCEL_DISABLE 0

static int pthread_setcancelstate(int state, int *oldstate) {
    sigset_t new, old;
    int ret;
    sigemptyset(&new);
    sigaddset(&new, SIG_CANCEL_SIGNAL);

    ret = pthread_sigmask(state == PTHREAD_CANCEL_ENABLE ? SIG_BLOCK : SIG_UNBLOCK, &new, &old);
    if (oldstate != NULL) {
        *oldstate = sigismember(&old, SIG_CANCEL_SIGNAL) == 0 ? PTHREAD_CANCEL_DISABLE
                                                              : PTHREAD_CANCEL_ENABLE;
    }

    return ret;
}

static inline int pthread_cancel(pthread_t thread) {
    return pthread_kill(thread, SIG_CANCEL_SIGNAL);
}

2. 移植Linux端生成的config.h并进行改写

让我们重新回到meson.build构建脚本,我们看一看原来它在Ubuntu-Linux操作系统上会生成什么产物,再看看它在Android-Linux操作系统上应该生成什么。我们通过分析构建脚本,可以看到里面有相当一部分用于测试当前机器环境是否具有相应的目标函数能力的代码:

# Test for presence of some functions
test_funcs = [ 'fork', 'fstatat', 'openat', 'readlinkat', 'pipe2',
               'splice', 'vmsplice', 'posix_fallocate', 'fdatasync',
               'utimensat', 'copy_file_range', 'fallocate' ]
foreach func : test_funcs
    cfg.set('HAVE_' + func.to_upper(),
        cc.has_function(func, prefix: include_default, args: args_default))
endforeach
cfg.set('HAVE_SETXATTR', 
        cc.has_function('setxattr', prefix: '#include <sys/xattr.h>'))
cfg.set('HAVE_ICONV', 
        cc.has_function('iconv', prefix: '#include <iconv.h>'))

# Test if structs have specific member
cfg.set('HAVE_STRUCT_STAT_ST_ATIM',
         cc.has_member('struct stat', 'st_atim',
                       prefix: include_default,
                       args: args_default))
cfg.set('HAVE_STRUCT_STAT_ST_ATIMESPEC',
         cc.has_member('struct stat', 'st_atimespec',
                       prefix: include_default,
                       args: args_default))

这些测试内容最终会反映到config.h文件中去,同理,我们可以在NDK中进行手动测试并写出一致的config.h文件(仅部分):

#define HAVE_SPLICE

#define HAVE_STRUCT_STAT_ST_ATIM

#undef HAVE_STRUCT_STAT_ST_ATIMESPEC

#define HAVE_UTIMENSAT

#define HAVE_VMSPLICE

至此我们已经准备好了大部分NDK环境下的编译准备,其余可能还涉及一些内部的常量定义,读者可自行根据报错来修复,此处不再赘述。
我们通过将hello程序引入进来,并在CMakeLists.txt中进行简要配置即可在assemble后生成Android环境下的可执行文件(AS的输出目录为 <module>/build/intermediates/cmake/<debug/release>/obj/<abi>/下):

add_executable(hello

        hello.c)
        
target_link_libraries(
        hello

        fuse3)

为了后续的执行,我们将这些成果物adb push到以下Android设备系统路径中去:

adb push <your path>\build\intermediates\cmake\debug\obj\armeabi-v7a\libfuse3.so /system/lib

adb push <your path>\build\intermediates\cmake\debug\obj\armeabi-v7a\hello /system/bin

(五) 从fuse_main()开始,一步一步分析libfuse组成

1. libfuse的进程与线程结构

现在源码有了,环境有了,产生成果物的脚本也有了,是时候分析libfuse的工作原理了。首先,我们来选择在哪个环境下分析源码,是在Ubuntu中呢,还是在Android中呢。其次,我们选择如何分析源码,是通过断点调试呢,还是通过打印日志信息呢,或者是打印方法调用栈呢。这些你都可以进行选择,不过有一些遗憾的是,在Android NDK中没有你可能需要的execinfo.h,因此如果想打印方法调用栈,需要另寻他法,此处不赘述,读者自行去查。

在libfuse官方推荐的经典示例hello程序中,我们可以在其源码中看到其主要调用了fuse_main()函数,查看对应的源码,我们来到了helper.c文件下的fuse_main_real()函数,而这,就是整个libfuse的主要入口。

int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
                   size_t op_size, void *user_data) {
    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    struct fuse *fuse;
    struct fuse_cmdline_opts opts;
    struct fuse_loop_config *loop_config = NULL;

    if (fuse_parse_cmdline_312(&args, &opts) != 0) {	// 1 parse args
        return 1;
    }

    fuse = fuse_new_31(&args, op, op_size, user_data);	// 2 create fuse, fuse_session

    if (fuse_mount(fuse, opts.mountpoint) != 0) {	// 3 mount path to fs
		// ......
    }

    if (fuse_daemonize(opts.foreground) != 0) {	// 4 default: fork & exit current process
		// ......
    }

    struct fuse_session *se = fuse_get_session(fuse);
    if (fuse_set_signal_handlers(se) != 0) {	// 5 set signal handlers
		// ......
    }

    loop_config = fuse_loop_cfg_create();
	// ......
    res = fuse_loop_mt_312(fuse, loop_config);	// 6 loop to process

    fuse_remove_signal_handlers(se);	// 7 remove signal handlers
out3:
    fuse_unmount(fuse);	// 8 use fusermount3
out2:
    fuse_destroy(fuse); // 9 release fuse resource
out1:
    fuse_loop_cfg_destroy(loop_config);
    free(opts.mountpoint);
    fuse_opt_free_args(&args);
    return res;
}

在上述代码段中,我们已经将其中重要的步骤做了1234各种标记,里面最关键的是步骤6,前5个步骤可以我们将其归为“初始化动作”,步骤6归为“循环处理工作”,后几个步骤归为“资源释放动作”。
默认情况下,hello程序会在步骤4中进行fork()操作,这会开启一个新的子进程,并退出主进程。你可能会发现你在hello.c的很多函数实现里加了printf()打印却没有任何信息显示,其实这就是在步骤4中进行了fd的重定义,将stdin(0),stdout(1),stderr(2)与/dev/null特殊设备相关联,如果你希望接下来能看到打印,那么将下面这段话注释掉即可:

nullfd = open("/dev/null", O_RDWR, 0);
if (nullfd != -1) {
    (void) dup2(nullfd, 0);
    (void) dup2(nullfd, 1);
    (void) dup2(nullfd, 2);
    if (nullfd > 2) {
        close(nullfd);
    }
}

我们对fuse的结构体进行入手,可以逐渐展开一张结构体的关联图(仅展示部分,读者可以自行绘制):

【Linux】分析Fuse中libfuse源码
有了结构体关联图,我们在读源码的过程中,便可以像调试程序一样一边跟踪程序执行中各变量的内容,一边剖析程序的状态机模型。
在“初始化动作”阶段,我们主要关注我们的自定义函数实现去了哪里。这个不复杂,稍作代码跟踪,我们就可以看到最后我们实现的系列函数被copy去了fuse_fsstruct fuse_operations op字段中。

    memcpy(&fs->op, op, op_size);

另一个在“初始化动作”阶段要注意的是,我们的一系列mount动作,最终会打开/dev/fuse设备,并通过该设备进行读写(消息转发)。

fuse_mount
--->fuse_session_mount
	--->fuse_kern_mount
		--->fuse_mount_sys
			--->1. open("/dev/fuse", ...)
			--->2. mount

现在我们重点来看步骤6,源码中这里是写了fuse_loop_mt(),不过为了方便跳转,结合前文介绍的symver,我们可以直接将其改为fuse_loop_mt_312()函数调用,没有影响,更方便在IDE中跳转。
fuse_loop_mt_312函数中,我们可以其实际关键的调用了fuse_session_loop_mt_312,我们可以展示该函数最近的调用栈:

fuse_loop_mt_312
--->fuse_session_loop_mt_312
	--->fuse_loop_start_thread
		--->fuse_start_thread
			--->pthread_create

结合源码,可以看到fuse_loop_start_thread这里创建了一个用于执行fuse_do_work()的线程。在fuse_do_work()我们终于见到了久违的while循环体。

while (!fuse_session_exited(mt->se)) {
    // ... 
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
    res = fuse_session_receive_buf_int(mt->se, &w->fbuf, w->ch); // 1 read request data from /dev/fuse
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
    // ...
    pthread_mutex_lock(&mt->lock);
    if (mt->numavail == 0 && mt->numworker < mt->max_threads) {
        fuse_loop_start_thread(mt);	// 2 create work thread when too busy
    }
    pthread_mutex_unlock(&mt->lock);
    // ...
    fuse_session_process_buf_int(mt->se, &w->fbuf, w->ch);	// 3 handle request data & response
}

工作循环内的主要调用还是比较好理解的,步骤1接收数据,步骤3处理数据并回复。步骤2就是在工作线程均繁忙的时候创建新的工作线程去处理。

2. 自定义文件操作接口的工作流水线

前文已经分析出了在事件循环中我们会读取请求数据,随后处理请求数据并回复。
在请求数据中,其实libufse中的实现很简单,就是从一个fd中读取预期缓冲大小的数据(实际并不会读取这么多数据):

    res = read(ch ? ch->fd : se->fd, buf->mem, se->bufsize);

这里有一个问题,这个预期大小会是多少byte。其实在默认情况下,初次init时大小为<value1>,后续为<value2>,都与pagesize()有关。具体的value1与value2是多少,留给读者去探索。
我们以在console中进入mountpoint所在的父路径为例,例如mountpoint/sdcard/Test,那么现在我们执行ls /sdcard命令来触发自定义文件系统的相关函数的调用。可以在刚才执行/system/bin/hello /sdcard/Test的console中看到有一些打印(这里假设我们自己加上去了一些用于查看关键信息的打印,非libfuse源码自带)。
【Linux】分析Fuse中libfuse源码

我们将其转换为函数调用栈:

fuse_session_process_buf_int
--->do_getattr // fuse_ll_ops[opcode].func
	--->fuse_lib_getattr // session->op.getattr
		--->fuse_fs_getattr
			--->hello_getattr	// fuse_fs->op.getattr

我们可以在几段代码中找到很多答案(结合前面的结构体关联图分析更直观):

enum fuse_opcode {	// protocol definition.
    FUSE_LOOKUP = 1,
    FUSE_FORGET = 2,  /* no reply */
    FUSE_GETATTR = 3,
    FUSE_SETATTR = 4,
    // ......
}

static struct {
    void (*func)(fuse_req_t, fuse_ino_t, const void *);

    const char *name;
} fuse_ll_ops[] = {
        [FUSE_LOOKUP]       = {do_lookup, "LOOKUP"},
        [FUSE_FORGET]       = {do_forget, "FORGET"},
        [FUSE_GETATTR]       = {do_getattr, "GETATTR"},	// FUSE_GETATTR = 3
        [FUSE_SETATTR]       = {do_setattr, "SETATTR"},
        // ......
}

static struct fuse_lowlevel_ops fuse_path_ops = {
        .init = fuse_lib_init,
        .destroy = fuse_lib_destroy,
        .lookup = fuse_lib_lookup,
        .forget = fuse_lib_forget,
        .forget_multi = fuse_lib_forget_multi,
        .getattr = fuse_lib_getattr,
        .setattr = fuse_lib_setattr,
        // ......
}

至此,我们便完全走通了libfuse的工作循环。我们的这些自定义文件系统涉及到的函数正是通过上述这些调用途径被执行。
数据处理完后,下一步就是回复响应。这一步的函数调用链为:

fuse_session_process_buf_int
--->do_getattr // fuse_ll_ops[opcode].func
	--->fuse_lib_getattr // session->op.getattr
		--->1. fuse_fs_getattr // has done.
		--->2. fuse_reply_attr
			--->send_reply_ok
				--->send_reply
					--->send_reply_iov
						--->fuse_send_reply_iov_nofree
							--->fuse_send_msg
								--->writev

我们可以看到,最终也是调用熟悉的writev函数完成写操作。其实整个libfuse中,最迷的是很多地方的void*指针,不过这也是C语言的通病了,耦合降低的同时带来的却是可读性极差。如果还需要读懂来龙去脉,那还需要研读Linux源码中的FUSE模块,虽然这本质上就是一套数据交换协议,所以搞清楚了实质以后也没有什么神奇的事情。
若我们需要卸载mountpoint,则需要采用fusermount3工具,执行fusermount3 -u <mountpoint>即可,这会触发hello子进程的退出。
下表是在剖析libfuse源码中,罗列出其涉及到的一些库函数及系统调用(仅展示部分),有些可能是你常见的,有些可能是你不常见的,读者可以借此机会重温一下这些函数是否都熟悉:

函数名 函数功能
pipe 创建管道
realpath 获取真实路径
_exit 不会执行on_exit和atexit中注册的清理函数
setsid 创建一个session并设置进程组id
dup2 重定向oldfd至newfd
mount 挂载fs节点
umount2 卸载fs节点
poll 等待事件
chdir 切换工作目录
pthread相关 多线程与同步机制
signal相关 信号机制
process相关 多进程
semaphore相关 信号量
dl相关 动态加载库
io相关 I/O操作

3. /dev/fuse扮演的角色

到此为止,其实我们已经拆解了大部分libfuse的工作原理,首先我们可以确定其基于C/S架构,内核端的Fuse其实就是Server,它会将VFS给它的文件系统操作函数进行消息封装,转化为请求数据并发送至/dev/fuse设备。因此整个工作模型就像下图:
【Linux】分析Fuse中libfuse源码
至此,一个Linux上的用户态文件系统的client端的一套官方开源代码库就暂时剖析结束了,里面涉及到的参数解析、初始化、异常处理、同步处理的细节留给需要的读者去慢慢体会,这里不再展开。文章来源地址https://www.toymoban.com/news/detail-474954.html

到了这里,关于【Linux】分析Fuse中libfuse源码的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 从 Uber 数据泄露事件我们可以学到什么?

    Uber 数据泄露始于一名黑客从暗网市场购买属于一名 Uber 员工的被盗凭证。最初尝试使用这些凭据连接到 Uber 的网络失败,因为该帐户受 MFA 保护。为了克服这一安全障碍,黑客通过 What’s App 联系了 Uber 员工,并假装是 Uber 的安全人员,要求该员工批准将 MFA 通知发送到他们

    2024年02月04日
    浏览(48)
  • 使用百度地图官方WEB API,提示APP服务被禁用了,可以访问 http://lbsyun.baidu.com/apiconsole/key# 查看自己的应用具体详情,或联系我们了解详情

    使用百度地图官方WEB API,显示APP服务被禁用了,可以访问 http://lbsyun.baidu.com/apiconsole/key# 查看自己的应用具体详情,或联系我们了解详情 笔者使用百度地图官方WEBDemo下载至本地进行测试,换上笔者的ak,运行代码游览器弹窗显示 相关代码 步骤1、重新创建应用,应用类型选择

    2024年02月12日
    浏览(124)
  • 【BBuf的CUDA笔记】九,使用newbing(chatgpt)解析oneflow softmax相关的fuse优化

    随着年纪越来越大,读代码越来越困难,如果你发现看不懂同事写的代码应该怎么办呢?不要担心,大语言模型的时代了来了,chatgpt和gpt4会教会我们怎么读代码。本篇文章就来展示一下使用newbing(chatgpt)来读oneflow softmax相关的fuse优化kernel的过程。本文的代码解释均由chat

    2024年02月01日
    浏览(49)
  • 家里的WiFi连不上?我们可以自行修复么?

    一般情况下,家里的WIFI连不上,首先确认路由器是否工作正常:检查路由器的电源灯是否开启,检查网络连接灯是否闪烁。 确认无线设备是否正常:检查无线设备(如手机)的WiFi功能是否开启,检查无线设备的WiFi信号强度是否正常,检查无线设备的连接是否锁定在正确的无

    2024年02月09日
    浏览(46)
  • vue:如果.vue文件内容过多,我们可以这样拆分

    一、标签内容太多,我们可以用组件的方式拆分 二、那如果JS的内容太多呢?因为耦合性太高,拆成组件后父子组件传值不方便,我们可以这样: 子组件: 父组件(也就是vue主页面): 逻辑拆分,相互调用不受组件影响  

    2024年02月02日
    浏览(48)
  • 关键词采集工具可以帮助我们做那些方面的工作

    针对搜索引擎的采集工具可以帮助我们做那些方面的工作,至少从10个工作场景说明,并列举详细的使用场景 Msray-plus,是一款企业级综合性爬虫/采集软件。 支持亿级数据存储、导入、重复判断等。无需使用复杂的命令,提供本地WEB管理后台对软件进行相关操作,功能

    2023年04月15日
    浏览(47)
  • 酸碱有机溶剂的移液和滴加我们可以选择哪些产品

    实验室经常会有一些样品需要转移和滴加的,因为有酸碱或者有机溶剂需要选择一些耐酸碱的产品,也有一些单位的样品需要本底值低的移液器皿处理,哪些是可以使用的呢? 移液枪是实验室最常见的移液工具,只要配上耐酸碱本底值低的PFA移液枪头就可以解决耐酸碱的问题

    2024年04月12日
    浏览(43)
  • GEEer成长日记二十三:chatGPT可以帮我们提取水体边缘吗?

    欢迎关注公众号: GEEer成长日记 目录 01  首先,chatGPT是什么? 02 进入正题,如何进行边缘检测?        chatGPT推出之后,引发了激烈的讨论,今天带各位看看它在GEE方面能为我们做什么。原本想着它可以帮我们写代码,奈何昨晚奋战到巴西输球,也没得到一个较好的结果。

    2023年04月25日
    浏览(44)
  • 深度丨一文了解隐私计算的前世今生,它真的可以保护我们的隐私吗?

    7月底,一则新闻爆出,Restore Privacy报告称,海外知名社交软件推特因安全漏洞被黑客入侵,有超过540万个账户的联系方式泄露,泄露的账户信息包括推特ID与其关联的电话号码和电子邮件信息,同时,这些信息已在一个黑客论坛上出售,价格为3万美元(约20.28万元人民币)。

    2023年04月10日
    浏览(39)
  • AI 图像生成工具可以取代摄影师吗?让我们从原理开始聊聊

    AI 的风已经吹向了每一个人,在这篇文章中我们一起来聊一聊 AI 图像生成的原理以及未来。 作为一个非职业的摄影爱好者,我通常会在 Instagram 上面搜罗各种各样的优质图片并将其放进我的收藏夹。其中,有一位我关注了很久的德国摄影师,他的作品有很多值得我学习的地方

    2024年02月03日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包