简介这份资源面向需要在Android平台上挂载NTFS格式存储设备的开发者与系统移植人员提供ntfs-3g的完整移植源码解决TF卡、U盘及SATA硬盘在Android系统中无法识别NTFS分区的问题。压缩包共300个文件约1.39MB以73个C源文件和61个头文件为核心实现辅以42个plo、34个po本地化文件、28个in模板及多份Makefile、am构建脚本另含configure、libtool等自动构建工具链文件整体结构贴近原生ntfs-3g工程便于直接放入external目录执行mm编译。编译成功后可手动执行挂载命令也可将Ntfs.cpp、Ntfs.h加入system/vold目录实现开机自动挂载。目前已有1835人学习下载适合具备一定Android源码编译与vold机制基础的开发者参考能帮助快速完成NTFS读写支持移植与调试。1. 从一次编译翻车说起为什么 Android 上要移植 ntfs-3g去年帮一个做车载多媒体的团队调一块外接硬盘Android 11 的板子插上 NTFS 分区后系统只读挂载写入直接报EACCES。他们试过mount -t ntfs内核里那个老掉牙的只读驱动根本不支持写又试了exfat格式对不上。最后绕回 ntfs-3g——这个在 Linux 桌面端跑了十几年的用户态 NTFS 读写方案才是 Android 上真正能落地的选择。问题在于Android 的编译体系和标准 Linux 发行版差别太大。你从官网拉一份 ntfs-3g 源码./configure make那套在 AOSP 源码树里根本走不通缺libfuse、缺pkg-config、NDK 工具链路径对不上报错能刷满一屏。这份移植源码的价值就在于它已经把 Android.mk 写好了放进 AOSP 源码树对应目录mm就能编出可执行文件和依赖库省掉自己写交叉编译脚本的折腾。适合谁看手里有 Android 源码树、需要让设备读写 NTFS 移动硬盘或 U 盘的系统开发以及想搞清楚 ntfs-3g 在 Android 上到底依赖什么、怎么编、怎么挂载的运维和嵌入式工程师。下面按我实际走通的顺序从环境准备一路写到挂载验证。2. 环境准备与源码落位AOSP 树里该放哪、依赖什么2.1 版本与工具链确认这套移植源码对编译环境有明确要求不是随便一个 NDK 就能编。我实测通过的组合是项目要求说明AOSP 版本Android 8.0 ~ 13低版本 Android.mk 语法兼容性更好编译方式源码树内mm依赖 AOSP 的 build 系统工具链AOSP 自带 clang / gcc不用单独装 NDK依赖库libfuse源码内已含ntfs-3g 的用户态核心依赖目标架构arm / arm64按你的板子选先确认你的源码树能正常编译。进到 AOSP 根目录执行source build/envsetup.sh lunch aosp_arm64-userdebugsource这步是把mm、mmm、lunch这些命令注入当前 shell不执行后面全白搭。lunch选的目标要和你板子的架构一致选错了编出来的二进制跑不起来。我一般会先make -j8随便编一个模块确认工具链没问题再动 ntfs-3g。2.2 源码目录落位这份移植源码的目录结构是围绕 AOSP 的external/目录设计的。常见做法是把它整体放到# 假设源码包解压后目录名为 ntfs-3g-android cp -r ntfs-3g-android $AOSP_ROOT/external/ntfs-3g放external/下的原因是 AOSP 的 build 系统会自动扫描这个目录里的Android.mkmm命令才能在当前目录直接生效。如果你放到别的路径得用mmm指定完整路径而且LOCAL_PATH的相对关系容易出错。放好之后检查目录里有没有这几个关键文件ls external/ntfs-3g/ # 应该能看到 # Android.mk - 编译入口 # libfuse/ - fuse 依赖 # src/ - ntfs-3g 主源码 # include/ - 头文件Android.mk是这套移植的核心它定义了BUILD_EXECUTABLE编 ntfs-3g 主程序和BUILD_SHARED_LIBRARY编 libntfs-3g 和 libfuse。如果这个文件缺失或者路径写错mm会直接报No Android.mk那就得回头确认源码包完整性。2.3 依赖关系梳理ntfs-3g 在 Android 上跑起来运行时依赖三样东西libfuse用户态文件系统框架ntfs-3g 通过它和内核 FUSE 模块通信。Android 内核一般已编入CONFIG_FUSE_FS但用户态库得自己带。libntfs-3gntfs-3g 自己的核心库处理 NTFS 元数据读写。libc / libdl系统库AOSP 自带。编译时Android.mk里会用LOCAL_SHARED_LIBRARIES把 libfuse 和 libntfs-3g 链进去。你不需要手动装任何东西但得确认内核配置里 FUSE 是开着的# 在板子上执行或看内核 config zcat /proc/config.gz | grep FUSE # 期望输出 # CONFIG_FUSE_FSy如果输出是# CONFIG_FUSE_FS is not set那 ntfs-3g 编出来也挂不上得先重新编内核。这是最容易忽略的前置条件我踩过一次编了半天发现内核没开 FUSE。3. mm 编译实操从 Android.mk 到可执行文件3.1 进入目录并执行 mm环境确认无误后编译本身只有一条命令cd $AOSP_ROOT/external/ntfs-3g mm -j8mm是 AOSP build 系统提供的命令作用是在当前目录下查找Android.mk并编译自动处理依赖。-j8是并行编译线程数按你机器核数调整一般设成 CPU 核数的 1~2 倍。编译过程会依次产出# 中间产物路径 out/target/product/你的设备/obj/EXECUTABLES/ntfs-3g_intermediates/ out/target/product/你的设备/obj/SHARED_LIBRARIES/libntfs-3g_intermediates/ out/target/product/你的设备/obj/SHARED_LIBRARIES/libfuse_intermediates/ # 最终产物 out/target/product/你的设备/system/bin/ntfs-3g out/target/product/你的设备/system/lib64/libntfs-3g.so out/target/product/你的设备/system/lib64/libfuse.so你的设备就是你lunch时选的 target 名。产物落在system/bin和system/lib64下说明Android.mk里的LOCAL_MODULE_PATH配置正确后续打包进系统镜像就能直接用。3.2 Android.mk 关键参数解读这份移植源码的Android.mk里有几个参数值得单独说改错了编译能过但运行会崩# ntfs-3g 主程序定义 LOCAL_PATH : $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE : ntfs-3g LOCAL_SRC_FILES : src/ntfs-3g.c src/... # 主源码列表 LOCAL_SHARED_LIBRARIES : libntfs-3g libfuse LOCAL_CFLAGS : -DHAVE_CONFIG_H -DANDROID # 关键宏 LOCAL_MODULE_PATH : $(TARGET_OUT)/bin # 安装到 system/bin include $(BUILD_EXECUTABLE)-DANDROID这个宏是移植时加的源码里有些#ifdef ANDROID分支专门处理 Android 的路径和权限差异不加会走到 Linux 桌面端的逻辑挂载点路径就错了。LOCAL_MODULE_PATH决定产物最终进哪个分区写$(TARGET_OUT)/bin就是system/bin如果你想让它在vendor/bin改成$(TARGET_OUT_VENDOR)/bin。LOCAL_SHARED_LIBRARIES里的libfuse和libntfs-3g必须在同一个Android.mk或同目录的其它Android.mk里有定义否则链接阶段报undefined reference。这份源码把三个模块写在一个文件里顺序上先编库再编可执行文件依赖关系是自洽的。3.3 编译产物验证编完之后别急着刷机先在本地验证产物架构和依赖# 确认架构 file out/target/product/设备/system/bin/ntfs-3g # 期望ELF 64-bit LSB executable, ARM aarch64 # 确认动态依赖 readelf -d out/target/product/设备/system/bin/ntfs-3g | grep NEEDED # 期望看到 # libntfs-3g.so # libfuse.so # libc.so # libdl.so如果file显示的是 x86 架构说明lunch选错了 target回头重新lunch再mm。如果readelf里缺libfuse.so检查Android.mk的LOCAL_SHARED_LIBRARIES是否包含它以及 libfuse 模块是否编译成功。4. 避坑与常见问题编译和挂载阶段的血泪记录4.1 报错fuse: device not found现象ntfs-3g 编出来推到板子上执行挂载命令报fuse: device not found, try modprobe fuse first。原因内核没有编入 FUSE 模块或者/dev/fuse设备节点不存在。Android 常见的内核配置里 FUSE 可能是模块形式m而不是内建y模块没加载就没有设备节点。解决先确认内核配置CONFIG_FUSE_FSy或m。如果是m需要把fuse.ko推到板子并insmod。更彻底的做法是改内核 config 改成y重新编内核。另外检查/dev/fuse权限ntfs-3g 需要读写这个设备ls -l /dev/fuse # 期望crw-rw-rw- 1 root root 10, 229 ... # 如果权限不对 chmod 666 /dev/fuse4.2 编译报undefined reference to fuse_...现象mm到链接阶段报一堆undefined reference to fuse_mount、fuse_loop之类的错误。原因libfuse 没有被正确链接。可能是Android.mk里LOCAL_SHARED_LIBRARIES漏了libfuse或者 libfuse 模块本身编译失败但被忽略了。解决先单独编 libfuse 确认它能过cd external/ntfs-3g/libfuse mm -j8如果 libfuse 编不过看它的Android.mk里LOCAL_SRC_FILES是否列全了.c文件。libfuse 有些版本依赖libdl需要在LOCAL_SHARED_LIBRARIES里加上libdl。确认库编出来后再回 ntfs-3g 目录重新mm。4.3 挂载成功但写入报Read-only file system现象mount -t ntfs-3g /dev/block/sda1 /mnt/usb挂上了但touch新文件报只读。原因ntfs-3g 默认可能以只读模式挂载或者 NTFS 分区有脏标志Windows 没正常卸载留下的。另外 Android 的 SELinux 策略可能阻止了写入。解决挂载时显式加读写选项mount -t ntfs-3g -o rw,umask0000 /dev/block/sda1 /mnt/usbumask0000让所有用户可读写。如果是脏标志问题用ntfsfix清一下ntfsfix /dev/block/sda1SELinux 问题看dmesg | grep avc有拒绝日志就说明需要加策略或临时setenforce 0验证。4.4mm报No Android.mk found现象在 ntfs-3g 目录执行mm提示找不到Android.mk。原因目录放错位置或者Android.mk文件名大小写不对AOSP 严格要求大写Android.mk。解决确认当前目录下有Android.mk且路径在$AOSP_ROOT/external/下。如果放在其它位置用mmm加完整路径mmm $AOSP_ROOT/path/to/ntfs-3g另外确认source build/envsetup.sh和lunch都执行过了否则mm命令本身就不存在。4.5 推送到板子后执行报not found现象adb push ntfs-3g /system/bin/之后adb shell ntfs-3g报not found。原因动态链接库没推过去或者推的位置不对。Android 的 linker 在system/lib64找 64 位库system/lib找 32 位库。解决把libntfs-3g.so和libfuse.so一起推到对应目录adb push libntfs-3g.so /system/lib64/ adb push libfuse.so /system/lib64/ adb shell chmod 644 /system/lib64/libntfs-3g.so adb shell chmod 644 /system/lib64/libfuse.so如果system分区只读先adb remount。推完用ldd或readelf在板子上确认依赖都能找到。5. 装完怎么验证跑通挂载、读写、卸载一条龙编完推完只是第一步真正验证这套移植能不能用得走一遍完整的挂载读写流程。我一般会准备一个 NTFS 格式的 U 盘或镜像文件按下面顺序过。5.1 用镜像文件做无硬件验证手头没 U 盘的时候用镜像文件也能验证避免反复插拔# 在板子上创建一个 64M 的 NTFS 镜像 dd if/dev/zero of/data/local/tmp/test.img bs1M count64 # 用 mkntfs 格式化如果源码里带了 mkntfs mkntfs -F /data/local/tmp/test.img # 挂载 mkdir -p /mnt/test ntfs-3g /data/local/tmp/test.img /mnt/test-F是强制格式化因为镜像文件不是块设备mkntfs 默认会拒绝。挂载成功后mount | grep ntfs应该能看到/mnt/test这一条。5.2 读写验证挂载点有了接下来验证读写是否真的可用# 写入测试 echo ntfs-3g android test /mnt/test/hello.txt # 读回 cat /mnt/test/hello.txt # 期望输出ntfs-3g android test # 创建目录和大文件 mkdir /mnt/test/dir1 dd if/dev/zero of/mnt/test/dir1/big.bin bs1M count32 # 确认文件大小 ls -l /mnt/test/dir1/big.bin写入成功且读回内容一致说明 ntfs-3g 的读写路径是通的。如果写入报错回看第 4 章的只读问题排查。大文件测试是为了验证超过内存缓存的写入场景小文件能过不代表大文件没问题。5.3 卸载与数据一致性验证完别忘了正常卸载这一步能暴露缓存没刷盘的问题umount /mnt/test # 如果报 device is busy fuser -m /mnt/test # 看谁占着 # 或者强制卸载 umount -l /mnt/test卸载后重新挂载确认之前写的文件还在ntfs-3g /data/local/tmp/test.img /mnt/test ls /mnt/test/ # 应该能看到 hello.txt 和 dir1这一步是验证数据真的落盘了而不是只在页缓存里。我遇到过卸载时没报错但重新挂载文件丢失的情况原因是 ntfs-3g 的日志回放没走完后来每次验证都强制走一遍「挂载→写→卸载→重挂→读」的完整循环。5.4 验证清单把上面的步骤整理成一张检查表每次移植完新板子照着过一遍检查项命令通过标准内核 FUSEzcat /proc/config.gz | grep FUSECONFIG_FUSE_FSy设备节点ls -l /dev/fuse存在且可读写二进制架构file ntfs-3g匹配板子架构动态依赖readelf -d ntfs-3g无 missing挂载ntfs-3g /dev/xxx /mnt无报错写入echo test /mnt/f成功读回cat /mnt/f内容一致卸载重挂umount mount文件仍在这张表我贴在工位上每换一块板子就从头走一遍。有次偷懒跳过了卸载重挂这步结果量产时发现异常断电后文件系统损坏回头查就是日志没配好。从那以后我每次移植完 ntfs-3g都强制走一遍完整的挂载读写卸载循环确认数据落盘才敢往下走。希望这套流程能帮你少走点弯路。本文还有配套的精品资源点击获取