ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Android设备Ext4文件系统排查实战:从挂载失败到数据丢失

Android设备Ext4文件系统排查实战:从挂载失败到数据丢失 做Android系统层或应用侧开发的朋友应该都碰到过类似的场景设备卡在开机动画串口里刷“fs_mgr_mount_all failed”某个App突然打不开自己的下载目录文件管理里既看不到预览也找不到文件或者明明删了十几个G系统空间还是0B。这些问题看着五花八门最后排查到底层基本都会落到同一个名字上——Ext4文件系统。这篇文章不是教科书而是把我这几年在Android设备上排查Ext4问题常用的思路、工具和踩坑记录整理出来从分区挂载、内核日志到应用层数据目录异常尽量让遇到同类问题的人能少走弯路。1. 为什么是ext4分区结构与文件系统的定位1.1 Android分区布局与ext4的分工不管手机上跑的是Android 10还是Android 14底层存储分区基本都遵循一套相似逻辑boot、vendor_boot、dtbo这些只读固化分区负责启动引导system、vendor、product等系统镜像分区负责承载系统镜像而真正需要频繁读写的主要就是/userdata分区。用户数据、应用安装包、数据库、下载文件全都在这个分区上。早期Android设备直接在GPT分区表里划出独立的system和data分区system用ext4data也用ext4。到了Android 11之后的动态分区时代system、vendor、product被统一塞进一个super逻辑分区里系统镜像很多改成了只读压缩文件系统erofs但userdata分区依然是ext4或者f2fs。为什么data分区一直坚持用传统文件系统因为它需要support动态增长和自由读写erofs一类的只读文件系统根本应付不了。在物理设备上/data所在分区通常是mmcblk0pXX或sdaXX这种块设备节点。Android通过fs_mgr在init阶段解析fstab调用fs_mgr_mount_all完成挂载。如果你看到开机阶段报“Failed to mount /data”大概率就是Ext4文件系统层面的问题超级块损坏、日志区异常、分区表错乱或者这次开机的SELinux上下文标记不对。我遇到过很多团队在把Android移植到非官方硬件时图省事直接烧一个通用userdata.img结果因为文件系统feature和内核不匹配挂载后立刻报“unsupported feature”数据分区起不来。所以第一个排查意识要建立起来文件系统本身是有feature开关的tune2fs -l /dev/block/by-name/userdata 能看到当前分区支持了哪些feature内核在EXT4_FEATURE_RO_COMPAT_SUPP里没有对应项就会拒绝挂载。1.2 日志机制决定了问题排查方向ext4是从ext3演进过来的继承了ext3的日志机制但把它做得更强大。它的核心思想是在真正修改磁盘上的元数据和数据块之前先把要做的操作写入一块专门的日志区域系统掉电或者崩溃后通过重放日志就能把文件系统恢复到一致状态。这块日志区域也直接影响玩家排查问题的方向。默认挂载参数是dataordered意思是元数据先通过journal提交数据块在commit之前保证已经写到磁盘。如果你看到dmesg里有“EXT4-fs error (device mmcblk0p53): ext4_find_entry: deleted inode referenced”这种日志那就不是简单的应用崩溃而是文件系统元数据已经出了偏差需要e2fsck干预。还有一点很容易被忽视ext4的journal在断电恢复时会把崩溃前未提交的事务全部重放这个时间跟事务量成正比。手上一些杂牌平板在意外断电后重启要卡在“正在优化应用”十几分钟一部分就是journal replay在后台跑。遇到这种问题别急着怀疑应用先抓dmesg看有没有“recovery complete”标记。2. 排查前先武装好工具日志采集与文件系统工具箱2.1 日志采集dmesg、logcat、kmsg的配合排查Ext4问题第一件事就是抓日志。很多开发者只盯着logcat但文件系统层面的错误Android的系统日志系统根本接不住得看内核日志。adb shell dmesg 可以抓内核环形缓冲区的历史记录但dmesg缓冲区通常有限问题复现后要尽快导出。adb shell logcat -b kernel 在userdebug版本的设备上可以看到klogd转发来的内核日志抓起来比dmesg方便。如果设备卡死、ADB都连不上那就只能在串口下抓完整kernel log。很多嵌入式板子和开发底座都带串口此时串口是唯一的救命稻草。我在排查一次/Data分区挂载失败的时候就是因为没有提前开串口只能死等重启后抓dmesg。后来养成的习惯是凡是做文件系统相关开发串口日志从开机第一秒就开启adb logcat只作为辅助。另外一个关键点日志里搜索关键词不要只搜“ext4”还要搜“fs_mgr”、“mount”、“I/O error”、“blk_update_request”和“selinux”。很多ext4故障的根因是底层MMC/eMMC的I/O错误而不是文件系统逻辑本身。底层介质坏了上层文件系统再健康也无济于事。2.2 文件系统诊断命令df、e2fsck、debugfs的选择核心命令集其实不多但每一条都有讲究。df -h看空间占用df -i看inode占用。很多“空间不足”其实是inode耗尽。mount看当前挂载参数特别关注是否只读挂载。如果本来是rw变成了ro多半是内核检测到文件系统错误后主动remount只读保护磁盘。e2fsck -fn /dev/block/...只检查、不修改。这是Live系统下唯一推荐的用法因为带写修复参数-p或-y会破坏正在使用的分区。tune2fs -l /dev/block/...查看超级块信息、feature列表、挂载次数、最后的错误行为。debugfs /dev/block/...进入交互式分区级调试工具可以查看inode、目录项、块分配情况。在非挂载状态下操作否则会有风险。要注意的是Android设备上普通用户没有权限直接读/dev/block/by-name/userdata。需要adb root后再对这些设备节点操作。如果手机没有root那就只能通过fastboot模式或者用工程固件临时root否则文件系统诊断无从谈起。2.3 fastboot下离线检查绕开Live系统的限制文件系统检查最好在分区没有挂载的时候做但很多生产设备根本没有root权限。这种情况下我的标准做法是在PC上通过fastboot boot一个临时的TWRP或自定义ramdisk从ramdisk里启动一个小型Linux。ramdisk内不依赖主系统的/data挂载可以安全地对userdata分区做e2fsck。好处是分区处于完全静默状态没有应用在写数据e2fsck结果可信。实际操作时fastboot boot twrp.img 进入TWRP然后adb shell进入命令行对/dev/block/bootdevice/by-name/userdata执行e2fsck -fy。如果TWRP没有内置e2fsck需要预先在ramdisk里塞入对应的静态编译版本。另一个思路是fastboot getvar current-slot确认当前槽位再决定检查哪个分区。在高通平台userdata分区上有metadata分区保存加密元信息如果直接把userdata分区复制到PC上再e2fsck很可能因为缺少metadata上下文而报错。所以除非是做取证分析否则尽量用设备本地工具别把分区dd出来在PC上修。3. 权限与路径/storage/emulated/0/android/data/ 的各种怪象3.1 Operation not permittedchmod失败的真正原因很多排查帖子都会提到一个场景想进入/storage/emulated/0/Android/data/com.xxx/files/目录或者对里面的文件执行chmod结果返回“Operation not permitted”。很多人第一反应是权限不够切到root照样失败这就说明问题不是普通权限。这个路径在Android上并不是一个真实存在的物理目录而是由FUSE挂载出来的虚拟视图。物理数据实际位于/data/media/0/Android/data/包名/。内核通过FUSE将外部存储访问转发至ExternalStorageProvider再叠加SELinux策略和Scoped Storage的访问控制。所以chmod失败通常不是文件系统拒绝你而是SELinux拒绝该进程对FUSE inode的特定权限。即使切换到root只要SELinux处于Enforcing状态内核仍然会给操作打上AVC denial。此时需要查看的是dmesg中的avc日志而不是怀疑ext4出了问题。如果确实需要修改这个目录的权限要从两个层面同时处理一是临时将SELinux设为Permissiveadb shell su 0 setenforce 0二是在Android 11以上还要绕过Scoped Storage检查。很多情况下更干净的做法是用adb push/pull操作到shell用户可访问的临时目录而不是直接在App私有目录上做权限修改。我还遇到过一种特殊情况文件系统层面被设置了immutable属性。chattr i设置的文件即使root也无法修改或删除。这时需要lsattr查看标记再用chattr -i解除。这个坑在OTA升级升级脚本残留时特别常见。3.2 FileProvider路径映射预览打不开、下载找不到一个超级常见的用户反馈是“我这个App里的文件打不开预览下载的文件在文件管理器里也找不到。”我处理过类似问题根子往往不在ext4而在ContentProvider的路径映射。Android应用分享文件时通常会通过FileProvider生成一个content://URI。比如某个搜索类App的fileprovider就可能是content://com.xxx.searchbox.fileprovider/baiddpath/android/data/com.xxx.searchbox/…这样的结构。如果App在xml里配置的file-path与实际文件路径不一致目标App解析URI时就会失败表现为“打不开预览”。另一个问题是很多App把下载文件写到/storage/emulated/0/Android/data/包名/files/Download/下面。这个路径受Scoped Storage保护其他文件管理器应用根本扫描不到。用户打开系统文件管理器看到的只有/storage/emulated/0/Download而不会看到应用私有目录下的下载内容。排查这类问题时不要急着认为是存储坏了先确认文件是不是落到了Android/data内部目录。从ext4的角度看文件本身是完整存在的只是对外可见性被FUSE和FileProvider挡住了。解决方向是修改App的存储策略把文件写到公共目录或者在FileProvider配置中增加对应路径并适当设置exported和grantUriPermissions。3.3 应用私有目录迁移与跨Android版本适配Android每次大版本升级应用文件目录的行为都会有变化。Android 11的Scoped Storage强制执行后/storage/emulated/0/Android/data/包名/这个目录就不再是普通App可以自由进出的地方了。Android 12之后跨用户存储路径区分更加明显/storage/emulated/0、/storage/emulated/10分别对应不同用户。在做Android 12适配时我遇到过一个很有意思的情况多用户环境的data目录权限共享某个用户在/sdcard/Android/data里的文件另一个用户的应用通过FileProvider访问时被拒绝。这不是ext4的文件权限问题而是SELinux和存储用户隔离机制造成的。日志里能搜到明显的“Permission Denied”但没有“EXT4-fs error”。所以当你发现清理工具无法扫描出Android/data下的大文件时不要骂系统不给你权限。这在Android设计里是刻意为之的。正确适配方式是通过MediaStore API申请相关权限或者让用户通过系统的文件选择器授权访问。4. 文件丢失、空间膨胀与“CPU 100%”的联动4.1 掉电与sync文件为什么一夜之间消失很多用户遇到过这种情况晚上睡觉前应用还在下载第二天醒来发现文件没了或者文件大小是0。应用层可能会把它归结为“文件系统坏了”但很多时候是掉电前数据没有落盘。ext4默认使用delayed allocation意思是写文件的时候数据先留在page cache等到一定时机才把块分配和磁盘写入一起做。这样性能高但代价是如果突然断电很多尚未flush到磁盘的page cache会直接丢失。应用调用write()之后数据并不保证已经写到物理磁盘。只有调用fsync()或fdatasync()数据才算真正可靠。很多下载框架只做到了关闭文件描述符没有调用fsync。在pc上可能问题不大因为PC的电源管理相对宽松但在移动设备上用户直接扣电池、电池耗尽自动关机、系统崩溃重启都会引发数据丢失。如果dmesg里看到“Aborting journal on device mmcblk0p53”说明内核检测到严重问题开始中断日志更新接下来很可能就是文件系统状态异常。恢复时系统会强制运行fsck所以会有那次“开机特别慢”的体验。这个跟Windows上突然断电后磁盘检查是一个原理。4.2 空间被谁吃了delalloc、open deleted file与inode再来看空间问题。很多人遇到“存储空间不足”但删除了一堆文件后还是不足这时df和du的结果往往对不上。df显示的是文件系统层面的块占用du显示的是用户在目录树里能看到的文件占用。两者对不上通常有几个原因有进程打开了一个已被删除的文件文件还在被写入但目录树里已经看不到du统计不到。App写了大文件但没有调用fsyncdelalloc延后分配导致df显示已占用但du看不到。ext4默认会保留一部分块给root用户使用df -h显示已满时其实还有约5%的保留空间。相机、相册等应用的缩略图数据库比如/storage/emulated/0/Android/data/com.android.gallery3d/files/thumbdb/会积累大量小文件inode耗尽导致无法创建新文件。排查这类问题我会用lsof查看被删除但仍打开的文件方法是进入/proc目录每个pid目录下的fd链接里如果带着“(deleted)”标记就是这种文件。找到之后要么重启进程让系统释放fd要么确认数据无价值后kill掉进程。还有一个很容易被人忽略的点/data分区内的加密元数据。Android开启文件级加密FBE后每个用户目录下会有0、10等子目录以及ce、de密钥目录。这些目录虽然小但inode数量占用不可忽视。如果inode耗尽任何应用创建文件都会报“No space left on device”即使df显示还有余量。此时执行df -i检查inode使用率比df -h更有用。4.3 存储IO阻塞D态进程、kswapd0、jbd2与CPU 100%排查线上问题时我看到过一台测试机CPU占用率干到100%top命令看不见哪个进程占用明显但系统负载很高。这种诡异现象通常和存储IO阻塞有关。当文件系统因为掉电、错误或者磁盘介质问题陷入内核态不可中断等待时进程就进入了D state。ps里的D state代表进程在内核里等待IO完成此时它不再消耗CPU时间片但系统负载会很高因为load average统计的是不可中断进程数这就会造成“没进程在跑但load爆表”的假象。此时应该做的是top -H 查看线程列表看有没有kswapd0、jbd2/mmcblk0p53、flush-179:0这类内核线程占CPU。查看/proc/进程pid/stack确认该线程阻塞在哪个函数上比如writeback、btree_lock、ext4_da_write_begin。dmesg抓取是否有“task hung”相关日志。我处理过一个典型案例某个App日志框架以每秒几十条的速度往data分区写日志每次写入都强制fsync。这个操作把ext4的jbd2线程和mmc驱动拖垮最终整个系统IO响应变成几秒一次。由于日志进程不断唤醒、阻塞系统负载飙升CPU的softirq和ksoftirqd占用高user space CPU反而是0。最后通过降低日志频率、合并写入、去掉fsync才把问题解决。5. 嵌入式场景从NFS根文件系统到LittleFS的另类排查5.1 NFS v3挂载rootfs的常见坑做嵌入式Linux和Android BSP移植时经常用NFS挂载根文件系统来加速调试。比如在内核bootargs里写root/dev/nfs nfsroot192.168.1.100:/srv/rootfs,vers3,tcp ipdhcp。这个场景虽然挂的是NFS但排查思路跟ext4有共通之处而且很多工程板NFS root起来之后/userdata仍然是ext4分区两个文件系统的坑会交织在一起。NFS v3挂载根文件系统时最明显的坑是rpcbind和权限配置。服务端exports文件里没有加no_root_squash的话客户端以root身份读文件会被映射成nobody导致根文件系统看起来“只读”。另外NFS v3对TCP较老手NFS v2根本不支持大文件所以vers参数一定要写成3以上。另一个常见的错误是内核虽然加载了NFS客户端驱动但没有打开CONFIG_ROOT_NFS选项或者没有内置NFS v3模块导致挂载根文件系统时死等。这时通过串口会看到类似“Waiting for root device /dev/nfs”反复刷屏。5.2 LittleFS与ext4闪存上的取舍思路很多用PlatformIO做ESP32或STM32开发的工程师对LittleFS并不陌生。Atmel等Nor Flash、SPI Flash上用的比较多的是LittleFS或SPIFFS而不会直接上ext4。为什么因为ext4是为块设备block device设计的最小I/O单位是块需要日志区减少元数据一致性风险。而SPI Nor Flash的擦除块大、写入次数有限直接跑ext4不仅磨损严重日志区经常擦写也会把Flash写废。LittleFS做的是日志结构文件系统天然带掉电保护和磨损均衡非常适合nor flash。但在实际工程里经常是一个系统里混着两种文件系统根文件系统或者固件分区用littlefs外置SD或者eMMC上的数据分区用ext4。我在移植一个Android TV盒子项目时设备自带eMMC跑的是ext4的/data分区内部调试分区却用了littlefs。调试时如果搞混了操作对象在littlefs分区上执行e2fsck会直接报错因为它的超级块结构完全不一样。排错时第一步永远是通过mount或者/proc/mounts确认当前文件系统类型再决定用哪个工具。littlefs也有对应的PC端工具比如littlefs-fuse就可以在host上挂载img文件检查内容。但现在很多调试者习惯性对着ext4的坏块跑e2fsck结果越修越坏。5.3 跨文件系统的“鸡尾酒”问题嵌入式设备最头疼的就是跨文件系统排查。我遇到过一块开发板bootloader和内核日志显示一切正常但rootfs挂载卡死。后来发现它不是纯ext4也不是纯NFS而是initramfs先挂成一个tmpfs再尝试用NFS v3挂载根文件系统失败后回退到eMMC上的ext4用户分区。这种情况下用户在logcat或dmesg里能看到NFS挂载报错但ext4分区没有任何异常。如果只看“挂载失败”几个字很容易以为是ext4坏了。其实问题是NFS路径不通系统在等待超时后才revert到本地ext4整个过程看起来像“启动异常慢”和“分区找不到”。排查建议是先把mount命令的输出看清楚确认真实挂载点和文件系统类型再逐层去验证网络、块设备和文件系统状态。6. 问题速查表与排查顺序建议6.1 常见症状与对应手段症状可能根因优先排查手段开机卡在动画/data分区挂载失败、journal replay慢抓dmesg搜“EXT4-fs error”、“mount”App内文件打不开预览FileProvider路径映射错误、MIME类型不准查看Manifest和xml里配置的file-path文件管理器找不到下载文件文件落在Android/data内部私有目录确认下载路径移动到公共Download目录chmod提示Operation not permittedSELinux拒绝或immutable标记dmesg搜avclsattr查看属性df显示满但du很小删除的打开文件、delayed allocation残留lsof查(deleted)等待进程关闭fd还有空间却无法创建文件inode耗尽、FBE密钥目录膨胀df -i确认inode使用率系统负载高但CPU占用不高D态进程等待IO、jbd2阻塞top -H看内核线程/proc/pid/stack掉电后文件丢失或大小归零未调用fsync、delalloc未落盘App层检查写文件流程补fsync逻辑6.2 我习惯的排查顺序我处理Ext4问题基本不会一上来就跑e2fsck。那是最后手段因为fsck本身在高负载系统上也可能造成二次损害。第一步永远是把现场日志完整保存下来包括dmesg、串口日志、logcat如果设备还能进系统附上mount和df输出。第二步确认文件系统状态通过只读方式捕捉当前错误码。第三步才会考虑离线检查配合e2fsck、debugfs、tune2fs逐项分析。最后再分享一个小细节Android的/storage/emulated/0/Android/data/路径在旧版本里可以直接通过adb shell访问但在新版本上越来越受限。我遇到过有人拿着Android 12的依赖库代码反复排查为什么访问不了这个目录最后发现根因是系统版本行为变化代码本身没错。文件系统排查的大原则其实很简单先分清楚是内核层、系统层还是应用层的问题再一层一层往下挖。把日志和路径搞明白了ext4本身反而往往是最后才会去碰的那个点。
返回列表