资讯动态

WinFsp 实战:用 memfs-fuse 构建一个可移植的内存 FUSE 文件系统

发布时间:2026/9/25 17:55:18 来源:尧图企业网站定制
存储驱动开发【免费下载链接】winfspWindows File System Proxy - FUSE for Windows项目地址https://gitcode.com/gh_mirrors/wi/winfsp点击查看免费下载在 Windows 上开发 FUSE 文件系统通常要面对工具链与类型系统的双重障碍POSIX 风格的 FUSE API 在 Windows 上既没有原生struct stat也没有统一的 uid/gid 语义。WinFsp 仓库中的 memfs-fuse 示例 提供了一个完整的参考实现——一个纯内存的 FUSE 文件系统既能用 Visual Studio 构建也能在 Cygwin 环境下用 GCC 编译同一份代码还可在 Linux、macOS 上直接编译为原生 FUSE 程序。本文基于该示例的 README、Makefile、工程文件与全部源码逐层拆解它的构建方式、数据模型与关键实现技巧。一、memfs-fuse 的定位与仓库组织memfs-fuse 是一个内存 FUSE 文件系统in-memory FUSE file system其全部内容由官方 README 一句话概括但配套的源码规模不小。示例目录包含memfs-fuse.cpp文件系统主体实现约 700 行 Ccompat.h跨平台类型兼容层MakefileCygwin GCC 构建脚本memfs-fuse.sln / memfs-fuse.vcxprojVisual Studio 工程。README 明确列出三种构建途径使用 Visual Studio打开memfs-fuse.sln使用 Cygwin GCC直接链接 WinFsp DLLmake winfsp-fuse使用 Cygwin GCC链接 CYGFUSEmake cygfuse。下面逐一结合构建文件展开。二、三种构建方式详解2.1 使用 Visual Studio 构建打开 memfs-fuse.sln 即可用 MSVC 编译。从 memfs-fuse.vcxproj 可以读到这套工程的关键配置目标平台定义了Win32x86、x64、ARM64三个平台均支持 Debug/Release 两种配置产物统一输出到build\$(Configuration)\目录头文件路径包含目录指向$(MSBuildProgramFiles32)\WinFsp\inc\fuse与$(MSBuildProgramFiles32)\WinFsp\inc即依赖已安装的 WinFsp 开发包对应本仓库 inc/fuse 目录下的fuse.h、winfsp_fuse.h等头文件预处理器定义所有配置都定义了FSP_FUSE_USE_STAT_EX这会让struct fuse_stat使用带扩展字段st_flags、保留字段的增强布局从而启用chflags、setcrtime等 Windows 特有扩展回调见下文 3.2 节链接设置链接winfsp-$(PlatformTarget).lib导入库并对winfsp-$(PlatformTarget).dll设置延迟加载DelayLoadDLLs。延迟加载的意义在于即使进程启动时 WinFsp 服务尚未就绪程序也不会立即崩溃FspLoad会推迟到首次实际调用时执行。2.2 使用 Cygwin GCC 直接链接 WinFsp DLLmake winfsp-fusetst/memfs-fuse/Makefile 提供了两个有效目标无目标参数时打印用法并退出cygfuse: memfs-cygfuse winfsp-fuse: memfs-winfsp-fuse memfs-cygfuse: memfs-fuse.cpp g $^ -o $ -g -Wall -stdgnu17 pkg-config fuse --cflags --libs memfs-winfsp-fuse: export PKG_CONFIG_PATH$(PWD)/winfsp.install/lib memfs-winfsp-fuse: memfs-fuse.cpp ln -nsf regtool --wow32 get /HKLM/Software/WinFsp/InstallDir | cygpath -au -f - winfsp.install g $^ -o $ -g -Wall -stdgnu17 pkg-config fuse --cflags --libs两条命令的编译部分完全相同-g -Wall -stdgnu17差异只在pkg-config 指向哪一组fuse.pcwinfsp-fuse 目标先用regtool --wow32 get /HKLM/Software/WinFsp/InstallDir从 Windows 注册表读出 WinFsp 的安装目录转换为 Cygwin 绝对路径后在源码目录建立符号链接winfsp.install再把PKG_CONFIG_PATH指向winfsp.install/lib。这样pkg-config fuse解析到的是 WinFsp 安装包自带的fuse.pc内容与本仓库 src/dll/fuse/fuse.pc.in 一致Cflags指向inc/fuse头文件Libs指向bin/winfsp-arch.dll最终g 直接链接 winfsp 的 DLLcygfuse 目标不设PKG_CONFIG_PATHpkg-config fuse解析到的是Cygwin 已安装的 cygfuse 包提供的fuse.pc链接对象为 CYGFUSE 兼容层 DLL。2.3 使用 Cygwin GCC 链接 CYGFUSEmake cygfuseCYGFUSE 是 WinFsp 项目提供的 Cygwin 侧兼容层其源码位于 opt/cygfuse 目录。该子目录的 Makefile 展示了它的产出物$(Build)/cygfuse-$(Version).dll: cygfuse.c fuse.cygport mkdir -p $(Build) gcc $(Debug) \ -shared -o $(Build)/cygfuse-$(Version).dll \ -Wl,--out-implib$(Build)/libfuse-$(Version).dll.a \ -I../../../inc/fuse \ cygfuse.c即编译 opt/cygfuse/fuse/cygfuse.c 生成版本化的cygfuse-ver.dll并通过--out-implib生成libfuse-ver.dll.a导入库再以 fuse.pc.in 生成 pkg-config 文件。make cygfuse构建 memfs 时正是消费这套产物。CYGFUSE 的角色是把 Cygwin 的 POSIX 类型uid_t、stat等桥接到 WinFsp FUSE 层期望的类型具体桥接函数如fsp_fuse_conv_to_win_path、fsp_fuse_winpid_to_pid可在 inc/fuse/winfsp_fuse.h 中看到声明与 Cygwin 实现。顶层 opt/cygfuse/Makefile 还额外提供make cygport/make dist通过cygport工具把 WinFsp 仓库源码打包成fuse与fuse3两个 Cygwin 发行版 tarball。2.4 三种方式对比构建方式编译器链接对象依赖前提适用场景memfs-fuse.slnMSVCwinfsp-arch.lib延迟加载 DLL已安装 WinFsp 开发包原生 Windows 开发make winfsp-fuseCygwin gWinFsp 安装包的 DLL已安装 WinFsp可从注册表读取 InstallDirCygwin 环境直连 WinFspmake cygfuseCygwin gcygfuse 兼容层 DLL已构建/安装 CYGFUSE 包Cygwin 环境经 CYGFUSE 桥接三条路径最终都调用同一个 FUSE C APIfuse_mainfuse_operations因此 memfs-fuse.cpp 本体对构建方式零感知。三、源码精读一个 FUSE 文件系统怎么写3.1 数据模型node_t整个文件系统用一个类memfs承载内部节点结构 node_t 非常紧凑struct fuse_stat stat; // 预格式化的 POSIX stat 数据 std::vectoruint8_t data; // 文件数据仅文件节点使用 std::unordered_mapstd::string, std::shared_ptrnode_t childmap; // 目录项 - 子节点 std::unordered_mapstd::string, std::vectoruint8_t xattrmap; // 扩展属性设计上有两点值得注意stat 结构被预填充并常驻。节点构造时memfs-fuse.cpp就把st_ino、st_mode、st_nlink、st_uid/gid及全部时间戳一次性写入fuse_stat后续getattr、readdir直接把node-stat拷贝出去避免了每次调用临时组装 stat 的开销容量预分配策略。resize(size, capacity)在扩容时按 64 KiB 为单位向上取整reserve缩容时才resize shrink_to_fitmemfs-fuse.cpp。这既避免了小块文件频繁 realloc又允许ftruncate缩小时真正归还内存——对“内存文件系统”这个语义是恰当的。inode 号由单调整数_ino线性递增从 1 开始1 号分配给根目录uid/gid 取自fuse_get_context()中调用方的上下文见make_nodememfs-fuse.cpp。3.2 操作表与 fuse_main 入口memfs-fuse.cpp 的main成员函数静态声明一张fuse_operations表并调用fuse_main(argc, argv, ops, this)。已实现的回调覆盖了典型的 POSIX 面元数据getattr委托给fgetattr、readlink、mknod、mkdir、unlink、rmdir、symlink、rename、link、chmod、chown、truncate/ftruncate、utimens数据open、read、write、releaseflush返回-ENOSYS因为数据始终在内存中无需刷盘扩展属性setxattr、getxattr、listxattr、removexattr并对com.apple.ResourceFork属性名显式返回-ENOTSUP目录opendir、readdir、releasedirWindows 扩展setcrtime设置创建/出生时间、条件编译的chflags仅在FSP_FUSE_USE_STAT_EX定义时启用修改st_flags能力协商init见 3.5 节。显式置空的回调包括getdir、utime、fsync、fsyncdir、create、lock、ioctl、poll、write_buf、read_buf、flock、fallocate等——FUSE 层对这些空指针回调会按“不支持”处理。几个实现细节renamememfs-fuse.cpp先解析新旧路径的父目录若newname为空意味着把目录移动到自身子路径直接返回-EINVAL防止目录环目标已存在时先走remove_node删除目录被占用会因ENOTEMPTY失败符合 POSIX 语义link会同时递增st_nlink并刷新新旧父目录的st_ctim/st_mtimwrite在偏移超出当前大小时先resize到off size稀疏扩展由vector::resize零填充实现并检查SIZE_MAX溢出返回-EFBIG所有回调入口处都通过getself()取回this并持有一把std::mutex全程加锁即所有 FUSE 操作串行化。这是示例代码的简化选择换取实现上的绝对正确性。3.3 路径解析lookup_nodelookup_node 是全部路径操作的基石。它不依赖内核的路径缓存FUSE 的 path-based API 模式而是每次按/把请求路径切分后逐级在childmap中查找返回三元组(父节点, 末段名, 目标节点)for (const char *part path, *p; *part; part p !!(*p)) { for (p part; *p / ! *p; p) ; if (part p) continue; // 容忍连续斜杠 prnt node; if (!node) break; name.assign(part, p); auto iter node-childmap.find(name); node node-childmap.end() ! iter ? iter-second : nullptr; ... } return std::make_tuple(prnt, name, node);make_node与remove_node都构建在这个返回值之上前者用prnt/name判断父目录是否存在-ENOENT与节点是否已存在-EEXIST后者依据dir参数核对类型-EISDIR/-ENOTDIR与空目录-ENOTEMPTY。3.4 打开句柄技巧让已打开的节点在 unlink 后继续存活这是本示例最有教学价值的一段。open_nodememfs-fuse.cpp把fuse_file_info::fh直接当作一个堆上shared_ptr的地址使用// A file descriptor is a raw pointer to a shared_ptr. // This has the effect of incrementing the shared_ptr // refcount, thus keeping an open node around even // if the node is unlinked. fi-fh (uint64_t)(uintptr_t)new std::shared_ptrnode_t(node);源码注释解释得很清楚fh里存的是一个裸指针指向new出来的shared_ptr于是打开操作为节点额外增加了一次引用计数——即使该节点随后被unlink从父目录childmap中抹除只要还有打开的文件描述符节点对象就不会被析构后续read/write通过get_node(path, fi)直接解引用fi-fh拿到同一节点。release/releasedir时close_node执行delete (shared_ptrnode_t *)fi-fh释放这份额外引用。这与 POSIX 中“已打开目录被删除后仍可继续读写”的行为一致。3.5 能力协商init 回调FUSE 挂载建立连接后会调用init协商文件系统能力。memfs-fuse.cpp 的写法是static void *init(struct fuse_conn_info *conn) { #if defined(FSP_FUSE_CAP_READDIR_PLUS) conn-want | (conn-capable FSP_FUSE_CAP_READDIR_PLUS); #endif #if defined(FSP_FUSE_USE_STAT_EX) defined(FSP_FUSE_CAP_STAT_EX) conn-want | (conn-capable FSP_FUSE_CAP_STAT_EX); #endif return getself(); }模式是把内核/服务侧报告支持的能力位capable与自己愿意启用的位做交集后写入want。FSP_FUSE_CAP_READDIR_PLUS增强 readdir可随目录项直接返回 stat减少后续 getattr 往返、FSP_FUSE_CAP_STAT_EX增强 stat对应 2.1 节 MSVC 工程里定义的FSP_FUSE_USE_STAT_EX等能力位定义在 inc/fuse/fuse_common.h 中同文件还列出了FSP_FUSE_CAP_READ_ONLY、FSP_FUSE_CAP_DELETE_ACCESS、FSP_FUSE_CAP_CASE_INSENSITIVE等。最后返回的指针被 FUSE 层存入上下文即后续每个回调中fuse_get_context()-private_data的来源。四、compat.h一套代码跑在 Windows、Cygwin 与类 Unix 系统compat.h 是整个示例“可移植”承诺的兑现分三段Windows FSP_FUSE_SYM 模式compat.h包含winfsp/winfsp.h并把fuse_main重定义为#define fuse_main(argc, argv, ops, data)\ (FspLoad(0), fuse_main_real(argc, argv, ops, sizeof *(ops), data))即调用前显式执行FspLoad(0)加载 WinFsp 服务与运行时再调用真正的fuse_main_real并把sizeof *(ops)一并传入——这让 DLL 侧能按编译期操作表布局解释回调指针实现结构体版本兼容。非 Windows 且未定义 fuse_stat 的平台compat.h把fuse_uid_t、fuse_mode_t、fuse_off_t、fuse_stat等统一映射到本地uid_t、mode_t、stat等 POSIX 类型。这意味着同一份源码在 Linux/macOS 上包含原生fuse.h也能编译此时FSP_FUSE_SYM会把内联函数体变成静态内联实现见 winfsp_fuse.h 中FSP_FUSE_SYM的定义常量兜底S_IFMT、S_IFDIR等文件类型位、XATTR_CREATE/XATTR_REPLACE、ENOATTR/ENODATA的跨平台映射以及 macOS 上st_atim与st_atimespec的命名差异compat.h。五、FUSE 头文件中的类型约定WinFsp 侧的 FUSE 兼容 API 声明在 inc/fuse/winfsp_fuse.h。其中一段注释解释了fuse_*类型体系存在的原因FUSE uses a number of types (notably: struct stat) that are OS specific… For this reason we will define our own fuse_* types which represent the types as the WinFsp DLL expects to see them.FUSE 使用若干 OS 相关类型……为此我们自定义 fuse_* 类型表示 WinFsp DLL 期望看到的形态。要点Windows 分支下显式定义fuse_ino_t64 位无符号、fuse_off_tint64_t、fuse_timespec64 位下tv_sec/tv_nsec均为int64_t等并刻意“avoid using long anywhere in these headers”规避 Cygwin64 与 Win64 中long宽度不一致的问题winfsp_fuse.h。struct fuse_stat按FSP_FUSE_USE_STAT_EX宏分为基础版与增强版后者多st_flags与保留字段winfsp_fuse.h这正是 2.1 节 MSVC 工程全局定义该宏的原因。六、运行挂载内存文件系统构建产物是普通控制台程序。FUSE C API 的fuse_main会解析标准 FUSE 命令行参数因此典型用法是把挂载点作为第一个参数传入例如将产物复制到挂载点目录后以memfs-fuse-x64.exe Z:\ -f之类形式前台运行具体挂载参数语义遵循 FUSE 2.x 选项规范由 src/dll/fuse 中的选项解析器处理。挂载成功后即可用 Windows 文件管理器或fsutil/POSIX 工具对该卷执行创建目录、写文件、chmod、扩展属性等操作所有数据只存在于进程内存中进程退出或卷卸载后全部丢失——这恰是“内存文件系统”的语义。需要说明的适用前提无论哪种构建方式目标机器上都必须先安装 WinFspCygwin 路径还需要能从注册表HKLM\Software\WinFsp\InstallDir读到安装目录make cygfuse要求 CYGFUSE 已构建并安装到 Cygwin 环境可直接用 opt/cygfuse 下的脚本先行构建由于所有操作被全局互斥锁串行化该实现的价值在于演示 FUSE 编程模型与跨平台技巧而非吞吐量。七、小结memfs-fuse 用不到 700 行 C 展示了在 WinFsp 上实现 FUSE 文件系统的完整闭环构建层同一份源码经 MSVC、Cygwin g直连 WinFsp DLL 或经 CYGFUSE 桥接三条路径编译pkg-config 与注册表InstallDir键是 Cygwin 路径定位 WinFsp 安装位置的机制模型层node_t以“预填 stat 数据向量 子节点哈希表”三件套支撑全部元数据与数据操作inode 线性编号路径逐级解析技巧层fuse_file_info::fh存shared_ptr地址实现“删除后打开句柄仍有效”init回调按capable与want交集协商readdirplus、stat_ex等能力compat.h抹平 Windows/Cygwin/类 Unix 三套类型系统。对希望为 Windows 编写 FUSE 文件系统的开发者而言这条从 README 三个构建入口出发、深入 memfs-fuse.cpp 与 winfsp_fuse.h 的阅读路径覆盖了 FUSE C API 在 Windows 平台上落地的全部关键接口。赞分享存储驱动开发【免费下载链接】winfspWindows File System Proxy - FUSE for Windows项目地址https://gitcode.com/gh_mirrors/wi/winfsp点击查看免费下载相关推荐推荐使用 Go-Fuse 构建可挂载的文件系统推荐使用 Go Fuse 构建可挂载的文件系统 是一个基于 Go 语言的库用于构建自定义的可挂载文件系统。它是一个轻量级、高性能且易于使用的工具可以帮助您开发工具OpenCV 人脸识别与人脸关键点检测实战指南基于 opencv_contrib face 模块从训练到部署OpenCV 人脸识别与人脸关键点检测实战指南基于 opencv_contrib face 模块从训练到部署 本篇技术指南以 opencv_contrib 的计算机视觉图像处理机器学习突破IO瓶颈WinFsp内存文件系统Memfs设计与实战指南突破IO瓶颈WinFsp内存文件系统Memfs设计与实战指南 在高频文件操作场景中传统磁盘IO往往成为系统性能瓶颈。WinFsp内存文件系统Memfs模块存储驱动开发上一篇unity-mcp 的 create_script 工具详解从零创建 C 脚本的完整实战指南下一篇深入解析 go-ansitermKubeSphere 依赖的跨平台 ANSI 终端仿真状态机解析器创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑