资讯详情

资讯详情

Android移植ntfs-3g:AOSP源码树编译与避坑指南

简介这份资源面向需要在Android平台上读写NTFS格式存储设备的开发者与系统移植人员提供ntfs-3g的完整移植源码解决TF卡、U盘及SATA硬盘在安卓系统中无法挂载NTFS分区的问题。压缩包共300个文件约1.39MB以C源码与头文件为主体73个.c、61个.h并包含大量多语言翻译文件42个.plo、34个.po、构建配置脚本configure、Makefile、m4、install-sh等以及ntfsprogs相关工具整体结构贴近原生ntfs-3g工程便于直接放入external目录执行mm编译。编译成功后可手动执行ntfs-3g挂载命令也可将Ntfs.cpp、Ntfs.h加入system/vold目录实现开机自动挂载。目前已有1835人学习下载适合具备一定Android系统与Linux内核基础、需要快速完成NTFS挂载适配的开发者参考能省去从零移植与调试构建脚本的时间。1. 从一次 Android 挂载 NTFS 移动硬盘翻车说起去年帮一个做车载多媒体的团队调一块外接硬盘Android 11 的板子插上 USB 硬盘后lsusb能看到设备dmesg也认出了块设备节点但mount死活挂不上报的是unknown filesystem type ntfs。当时第一反应是内核没开 NTFS 支持翻内核 config 发现CONFIG_NTFS_FS是开的但那是老的内核只读驱动面对现在普遍是 exFAT/NTFS 混用的移动硬盘读写需求根本满足不了。后来换成 ntfs-3g 用户态方案才彻底解决但中间踩的坑足够写一篇血泪笔记——源码怎么放进 AOSP 树、Android.mk 怎么写、mm编译报错怎么救每一步都有玄学。这篇记录的就是把 ntfs-3g 移植进 Android 源码树、并且能直接用mm命令编译出可执行文件和库的完整过程。适合两类人一是做 Android 系统定制、需要让设备原生支持 NTFS 读写的 BSP 工程师二是拿到一份移植源码但不知道怎么塞进 AOSP、编译一直报错的开发者。装不好会怎样轻则mm报一堆 undefined reference重则编出来的二进制在设备上跑起来直接 segfault连日志都抓不到。下面按环境、源码结构、编译配置、避坑、验证的顺序走一遍。2. 环境准备AOSP 版本、NDK 与 ntfs-3g 源码版本怎么选2.1 AOSP 版本与编译工具链的对应关系ntfs-3g 移植到 Android 最核心的约束不是 ntfs-3g 本身而是你手上的 AOSP 版本决定了用哪套编译工具链。Android 8.0 之前用的是 GCC之后全面切到 ClangAndroid.mk里LOCAL_C_INCLUDES和LOCAL_CFLAGS的写法差异很大。我一般会先确认三件事AOSP 分支号、默认 Clang 版本、以及build/soong是否已经强制启用。AOSP 版本默认工具链Android.mk 支持建议 ntfs-3g 版本Android 7.xGCC 4.9完整支持ntfs-3g 2017.3.23Android 8.x–9.xClang 6/7支持但逐步迁移ntfs-3g 2017.3.23Android 10–11Clang 9/11支持需补LOCAL_SDK_VERSIONntfs-3g 2021.8.22Android 12Clang 12建议转 Android.bpntfs-3g 2022.10.3选版本的原则很简单AOSP 越新ntfs-3g 越要用新版本因为老版本里大量用了register关键字和隐式函数声明Clang 12 之后直接报 error。我一般会在external/下建目录命名成ntfs-3g和 AOSP 原有模块保持一致的风格。2.2 源码目录结构与关键文件说明拿到移植源码后先别急着编译把目录结构理清楚。一份能直接mm的 ntfs-3g 移植源码通常包含下面这些关键部分external/ntfs-3g/ ├── Android.mk # 编译入口mm 就是找它 ├── include/ │ ├── ntfs-3g/ # 核心头文件 │ └── config.h # 平台配置Android 下必须手改 ├── libntfs-3g/ # 核心库源码 ├── ntfsprogs/ # 工具集mkntfs、ntfsfix 等 ├── src/ # ntfs-3g 主程序 └── fuse/ # FUSE 接口层Android 上依赖 libfuseconfig.h是最容易翻车的地方。Linux 发行版上这个文件是configure脚本生成的Android 没有 autotools 环境必须手动准备一份。常见做法是从源码里自带的config.h.in改把HAVE_*宏按 Android 的实际情况打开或关掉。比如HAVE_SETXATTR在 Android 上要关掉因为 bionic 的 xattr 实现和 glibc 不一致HAVE_MALLOC_H也要关Android 没有这个头。2.3 依赖库libfuse 在 Android 上的处理ntfs-3g 依赖 FUSE 才能把文件系统挂到用户态。Android 从 7.0 开始内核就带了fuse模块但用户态libfuse需要自己准备。AOSP 树里其实有external/fuse但版本偏老接口和 ntfs-3g 2021 之后的版本对不上。我一般会做两件事一是确认external/fuse存在且能编出libfuse二是如果版本太老就把 ntfs-3g 源码里fuse/目录下的兼容层用上让 ntfs-3g 自带一份精简的 FUSE 封装。# 确认 AOSP 树里 fuse 模块是否存在 ls external/fuse/Android.mk # 如果不存在从对应 AOSP 分支的 external/fuse 拉一份进来 # 注意 fuse 版本要和 ntfs-3g 的 fuse 接口版本匹配 grep -r FUSE_USE_VERSION external/ntfs-3g/ | headFUSE_USE_VERSION这个宏决定了 ntfs-3g 调用哪套 FUSE API。Android 的external/fuse一般支持到 26如果 ntfs-3g 源码里写的是 30 以上就得改小否则编译时一堆结构体成员找不到。这一步不做后面mm必挂。3. 编译配置Android.mk 怎么写才能让 mm 一次过3.1 Android.mk 的核心结构mm命令的本质是在当前目录找Android.mk并执行。ntfs-3g 移植源码能不能一次编过八成看这个文件写得对不对。下面是我在 Android 11 上验证过的一份Android.mk骨架LOCAL_PATH : $(call my-dir) # 先编 libntfs-3g 静态库 include $(CLEAR_VARS) LOCAL_MODULE : libntfs-3g LOCAL_SRC_FILES : $(wildcard libntfs-3g/*.c) LOCAL_C_INCLUDES : \ $(LOCAL_PATH)/include \ $(LOCAL_PATH)/include/ntfs-3g \ external/fuse/include LOCAL_CFLAGS : -DHAVE_CONFIG_H -D_FILE_OFFSET_BITS64 \ -Wno-unused-parameter -Wno-sign-compare LOCAL_STATIC_LIBRARIES : libfuse include $(BUILD_STATIC_LIBRARY) # 再编 ntfs-3g 主程序 include $(CLEAR_VARS) LOCAL_MODULE : ntfs-3g LOCAL_SRC_FILES : $(wildcard src/*.c) LOCAL_C_INCLUDES : $(LOCAL_PATH)/include LOCAL_CFLAGS : -DHAVE_CONFIG_H -D_FILE_OFFSET_BITS64 LOCAL_STATIC_LIBRARIES : libntfs-3g libfuse LOCAL_SHARED_LIBRARIES : libc libdl include $(BUILD_EXECUTABLE)这段的核心逻辑是分两步先把libntfs-3g编成静态库再让主程序链接它。LOCAL_CFLAGS里的-D_FILE_OFFSET_BITS64必须加否则在 64 位 Android 上处理大文件会溢出。-DHAVE_CONFIG_H是告诉源码去读我们手写的config.h不加这个宏源码会走默认配置大概率编不过。3.2 config.h 的关键宏配置config.h是 Android 移植里最需要手工干预的文件。下面这几个宏是我每次都要检查的/* Android bionic 没有这些必须关 */ #undef HAVE_SETXATTR #undef HAVE_MALLOC_H #undef HAVE_GETMNTENT #undef HAVE_STRUCT_STAT_ST_BLOCKS /* 这些要打开 */ #define HAVE_STRING_H 1 #define HAVE_FCNTL_H 1 #define HAVE_UNISTD_H 1 #define HAVE_ERRNO_H 1 #define HAVE_SYS_STAT_H 1 #define HAVE_SYS_TYPES_H 1 /* 字节序Android 设备基本是小端 */ #define WORDS_BIGENDIAN 0 #define HAVE_LITTLE_ENDIAN 1HAVE_SETXATTR关掉是因为 Android 的setxattr在 bionic 里行为不一致ntfs-3g 用它来存 NTFS 的扩展属性关掉后功能降级但不影响基本读写。HAVE_GETMNTENT关掉是因为 Android 没有/etc/mtabntfs-3g 挂载时会走另一条路径。这些宏不改编译期可能过但运行期会出各种ENOSYS。3.3 用 mm 编译并处理常见报错配置就绪后在external/ntfs-3g目录下执行# 先 source 和 lunch确保环境变量对 source build/envsetup.sh lunch aosp_arm64-userdebug # 进入模块目录 cd external/ntfs-3g mm -j8mm会自动找当前目录的Android.mk-j8是并行编译加速。第一次编大概率会报错最常见的三类一是undefined reference to fuse_xxx说明libfuse没链上检查LOCAL_STATIC_LIBRARIES里有没有libfuse二是config.h: No such file说明LOCAL_C_INCLUDES没包含include目录三是error: use of undeclared identifier register这是 Clang 新版本把register关键字删了得去源码里把register全删掉。# 批量删 register 关键字的常见做法 find . -name *.c -o -name *.h | xargs sed -i s/\bregister\b//g这条命令会把所有register关键字去掉对 ntfs-3g 这种老代码库基本安全。改完再mm一般就能编出ntfs-3g可执行文件和libntfs-3g.a。4. 避坑记录移植 ntfs-3g 时最容易翻车的五个点4.1 现象mm 报 undefined reference tofuse_mount_compat25原因ntfs-3g 源码里用的 FUSE API 版本和 AOSPexternal/fuse提供的版本不一致fuse_mount_compat25是旧版接口新版 libfuse 已经改名或移除。解决打开include/ntfs-3g/fuse.h把FUSE_USE_VERSION改成和external/fuse/include/fuse.h里定义的一致通常是 26。改完清理重编mm clean-ntfs-3g mm -j84.2 现象编出来的 ntfs-3g 在设备上跑挂载时报fuse: device not found原因Android 内核虽然带了 fuse 模块但/dev/fuse设备节点的权限不对普通进程打不开。解决确认init.rc里有chmod 0666 /dev/fuse或者用 root 跑。另外 SELinux 策略也要放行ntfs-3g进程的 domain 需要允许fuse_device访问。常见做法是在file_contexts里给ntfs-3g打上su或自定义 domain 标签。4.3 现象挂载成功但写入大文件时程序崩溃原因_FILE_OFFSET_BITS64没定义或者off_t在 32 位编译目标下还是 32 位导致文件偏移溢出。解决在Android.mk的LOCAL_CFLAGS里同时加-D_FILE_OFFSET_BITS64和-D_LARGEFILE_SOURCE并且确认LOCAL_MULTILIB设置正确。如果是 64 位目标这两个宏其实默认生效但 32 位目标必须显式加。4.4 现象config.h里HAVE_SETXATTR开着编译过但运行时报Operation not supported原因Android bionic 的setxattr系统调用在部分内核上被 SELinux 拦截返回EOPNOTSUPP。解决直接把HAVE_SETXATTR关掉ntfs-3g 会跳过扩展属性操作基本读写不受影响。如果业务确实需要 xattr得单独适配 Android 的 xattr 接口工作量不小。4.5 现象mm编过了但system/bin/ntfs-3g没进镜像原因Android.mk里只写了BUILD_EXECUTABLE没加LOCAL_MODULE_PATH或LOCAL_MODULE_TAGS导致模块没被PRODUCT_PACKAGES收录。解决在Android.mk里加LOCAL_MODULE_TAGS : optional LOCAL_MODULE_PATH : $(TARGET_OUT_OPTIONAL_EXECUTABLES)然后在设备mk文件里把ntfs-3g加进PRODUCT_PACKAGES。这一步不做编出来的东西只在out/里躺着刷机后设备上找不到。5. 装完怎么验证从 mm 产物到设备实挂的完整检查链编完只是第一步真正要确认的是这套 ntfs-3g 在设备上能不能挂载、能不能读写。我一般会按下面这条链走一遍每一步都有明确的检查点。先看mm产物是否齐全。在 AOSP 根目录执行# 确认可执行文件和静态库都生成了 ls -l out/target/product/*/system/bin/ntfs-3g ls -l out/target/product/*/obj/STATIC_LIBRARIES/libntfs-3g_intermediates/libntfs-3g.a两个文件都在说明编译阶段没问题。如果ntfs-3g不在system/bin下回去检查LOCAL_MODULE_PATH。接着用file命令确认架构对不对file out/target/product/*/system/bin/ntfs-3g # 期望输出类似ELF 64-bit LSB executable, ARM aarch64架构错了说明lunch选错了目标重新lunch再编。推到设备上实测。我一般用adb push先放到/data/local/tmp试跑避免直接刷机adb push out/target/product/*/system/bin/ntfs-3g /data/local/tmp/ adb shell chmod 755 /data/local/tmp/ntfs-3g # 插上 NTFS U 盘确认块设备节点 adb shell ls /dev/block/sd* # 手动挂载 adb shell mkdir -p /mnt/ntfs adb shell /data/local/tmp/ntfs-3g /dev/block/sda1 /mnt/ntfs挂载成功的话adb shell ls /mnt/ntfs能看到 U 盘里的文件。写入测试用ddadb shell dd if/dev/zero of/mnt/ntfs/test.bin bs1M count100 adb shell sync adb shell ls -l /mnt/ntfs/test.bin100MB 写进去不崩、sync不报错基本就稳了。最后一步是卸载再重挂确认没有残留adb shell umount /mnt/ntfs adb shell /data/local/tmp/ntfs-3g /dev/block/sda1 /mnt/ntfs能反复挂载卸载不出错说明 FUSE 层和 ntfs-3g 的配合没问题。这套验证链我每次移植新版本都会走一遍尤其是dd写大文件那步很多隐藏的off_t问题只有写大文件才会暴露。从那以后我每次编完 ntfs-3g都强制走一遍「push → 挂载 → dd 写 100M → 卸载重挂」的流程少一步都不敢往设备里刷。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →