先从我实际遇到的一个案例说起一台Android测试机上/data分区明明还剩好几个G的可用空间可应用就是装不进去、日志写不出来df看着一切正常但操作时一直被拒绝。这种问题我碰过不少次大多数时候问题本身并不神秘只是很多人排查文件系统问题时一上来就盯着 ext4 这四个字母却忽略了一个关键事实——在Android里文件系统问题往往并不是存储引擎坏了而是挂载策略、权限模型和存储分层这三层里某一层出了问题。这篇内容源自我和团队做Android设备维护、嵌入式系统调试时积累的排查经验结合最近在一些技术社区里被反复问到的场景把常见的Ext4文件系统问题按类型拆开讲清楚。适合做Android系统开发、应用层适配、嵌入式Linux移植的工程师也适合做设备运维、遇到线上存储异常时需要快速定位的人。整篇更像是一份实战笔记把排查思路、关键命令、根因分析都放进去没有废话基本可以直接照着操作。1. Android的文件系统长什么样先搞清楚你在哪一层挂的很多人排查存储问题第一步就看df看到ext4就觉得内核文件系统层出了问题这个惯性思路害了不少人。想要高效定位问题首先得把Android的存储架构在脑子里建起来——从你敲命令的位置到最终落盘的数据中间隔了好几层每一层都有自己的规则和脾气。1.1 从内核到应用的数据通路真正的ext4文件系统其实只存在于两个地方一个是/data分区所在的用户数据块设备另一个是/system、/vendor、/product这些只读分区。往内核层看VFSVirtual File System虚拟文件系统是统一入口它把各种文件系统挂到系统的目录树上ext4只是VFS之下的一种具体实现。你在/data里创建、读取、删除文件实际走的是 VFS → ext4 → 块设备驱动 这条链路。那/sdcard、/storage/emulated/0呢这里就有一个容易误会的点。用户可见的/storage/emulated/0/Android/data、/storage/emulated/0/DCIM这些路径其实不是直接挂在ext4块设备上的而是由一个叫FUSEFilesystem in Userspace或旧版本上的sdcardfs层实现的。FUSE 这一层把/data/media目录在真正的ext4上暴露给应用并且由ExternalStorageProvider这类系统服务来管理和限制访问规则。这就是为什么你经常能看到这样的情况应用通过FileAPI 读写/storage/emulated/0/Pictures/xxx.jpg明明没问题但走原生路径访问/data/media/0/Pictures/xxx.jpg却碰到Permission denied。不是ext4拒绝了你是FUSE层的权限校验把你拦住了。1.2 挂在用户空间的分区存储层在Android 10及以上版本系统还引入了分区存储Scoped Storage的概念。虽然数据最终还落在那块ext4文件系统上但应用能看到的视图被限制到自己的私有目录/sdcard/Android/data/package或者内部存储里的/data/data/package和特定的媒体集合目录Pictures、Movies、Download。这层设计的原因很简单谷歌希望隔离应用之间的数据访问防止一个App扫走整个用户目录。但对做开发调试的人来说这层视图经常让排查工作变得异常痛苦——明明/data空间够、文件系统本身也没坏App却访问不到本身应该能访问的东西。1.3 /data 和 /sdcard 不是一回事做问题定位前建议你先分清/data与/sdcard这两个路径各自的挂载点、格式和特点它们的管理方式是截然不同的路径实际挂载节点文件系统主要用途常见坑/data/dev/block/by-name/userdataext4应用私有数据、系统核心数据inode耗尽、加密、权限错乱/data/media同一ext4设备media子目录ext4FUSE的底层存储池常被误认为独立文件系统/storage/emulated/0FUSE/sdcardfs挂载点无虚拟应用可见的用户空间权限策略复杂容易Permission denied/system、/vendor各分区ext4/erofs系统只读程序只读属性限制无法直接修改有了这张图后面所有问题的排查思路就清晰了先搞清楚当前卡点是出在用户态权限层、挂载层还是底层ext4本身然后再对症下药。2. 最常踩的坑/storage/emulated/0/Android/data 操作失败与权限之谜行业里有一类问题出现的频率高得惊人就是操作/storage/emulated/0/Android/data/下的目录时报错最经典的就是adb shell $ chmod 777 /storage/emulated/0/Android/data/com.example.app chmod: /storage/emulated/0/Android/data/com.example.app: Operation not permitted类似的还有ls能列出目录但cd进不去、某些文件管理器打开/Android/data后一片空白、写文件时提示EACCES或Operation not permitted。这些问题表面上像ext4权限位问题实际上三者叠加。排查过一次之后你就知道真正限制你的并不是文件系统本身。2.1 根因拆解分区存储、SELinux、FUSE的三层封锁第一层是分区存储限制。Android 11开始系统强制应用无法直接列举或随意访问其他应用的/Android/data目录这是framework层加的限制。你从adb shell里操作也会受这个限制因为shell本身也是一个应用同样被scoped storage的策略约束。第二层是SELinux策略。你执行chmod、chown这类操作时VFS层会检查进程的安全上下文有没有对应的权限。shell域u:r:shell:s0对某些/storage下的节点做写操作或改属主操作很容易被avc: denied拦下来。判断方法非常简单在logcat或dmesg里搜avc如果有类似avc: denied { setattr } for pid... commchmod的记录那就是SELinux的事。第三层是FUSE挂载选项。FUSE这一层在Android里启动时通常会根据传递给守护进程的参数来决定允许哪些操作透传到底层文件系统。有时候连root都会被卡住是因为FUSE层直接拒绝了这个操作根本没到ext4那一层。打个比方ext4相当于一个写字楼的保险柜FUSE是前台接待SELinux是门卫。你想进保险柜改东西先得过门卫、再通过前台登记前台还规定了你只能进你自己的办公室。很多人抱怨文件系统坏了其实只是被门卫和前台拦住了而已。2.2 完整排查链路一次从报错到底层原因的追踪这里是我在实机上完整走一遍的排查过程你可以照着来。目标设备是Android 13的一台测试机操作是尝试修改/storage/emulated/0/Android/data下某个第三方应用目录的权限第一步先确认路径是不是真的存在adb shell ls -ld /storage/emulated/0/Android/data/com.example.app如果能列出来但ls里边的子项为空那大概率不是ext4问题而是FUSE层给你返回了一个空视图。第二步写一个最小文件测试adb shell touch /storage/emulated/0/Android/data/com.example.app/test.txt同样报Operation not permitted基本可以排除路径不存在这类低级错误问题聚焦到权限层。第三步抓内核日志和logcatadb shell dmesg | grep avc adb logcat -d | grep -i storage如果 dmesg 里有avc: denied方向就明确了——SELinux拦截。如果 logcat 里有Permission denied且来源是ExternalStorageProvider那就是分区存储限制。第四步尝试从另一个上下文访问——用root或换成支持MANAGE_EXTERNAL_STORAGE权限的调试应用。以root身份执行相同操作如果能成功就更进一步证明ext4层和块设备完全健康纯粹是上层策略问题。2.3 实际可行的绕过思路仅限调试场景明白了原因就得知道怎么在调试中破局。针对常见场景我整理了一套稳定可行的做法场景A仅需读取App沙盒文件。Android 11及以上用adb连接设备后在开发者选项打开文件权限有的厂商叫USB调试安全设置或者用支持SAF的普通文件管理器授权访问。简单可靠。场景B需要在shell下操作但手头没有root。可以考虑用adb shell appops set --uid uid MANAGE_EXTERNAL_STORAGE allow来给特定应用授予访问权限注意需要对应权限才能执行成功。场景C有root的调试设备。root后先执行setenforce 0临时关闭SELinux仅调试设备生产机千万别这么干再操作/data和/sdcard路径通常就能畅通。操作完记得setenforce 1恢复。场景D要读写/data/data/package这类内部存储目录。这不是/storage/emulated/0而是挂在真正的ext4/data分区上同样受SELinux和DAC权限控制root后配合chmod和chown常规处理即可。核心思路就是先判断不允许来自哪里再决定用哪个层级的手段去解决。我见过不少人在没有root的机器上跟SELinux硬肝、跟分区存储较劲毫无意义——你需要的不是改ext4而是获得一个更高权限的上下文。3. 根文件系统挂载与只读问题从NFS v3到remount rw的实战排查接下来是嵌入式开发、系统移植里几乎天天要面对的一类问题。做嵌入式Linux、Android系统裁剪、用户态调试时根文件系统挂载不上、只读挂载修改不了、或者挂载后sync写不进去是很典型的现象。我在板子上用NFS v3挂载根文件系统时踩过的坑尤其多值得单独拿出来说说。3.1 为什么开发阶段用NFS v3挂根文件系统嵌入式Linux调试阶段把根文件系统放在开发主机上通过NFS共享给目标板可以说是个经典方案。好处很明显改完代码不必烧写Flash直接在宿主机上同步文件目标板上重启或重挂就生效调试迭代速度极快。Android生态里AOSP源码编译产物也能通过NFS挂载配合adb root、adb remount来调试系统镜像。NFS v3和v4相比挂载参数上有个要特别注意的差异——v3对文件锁和权限模型的支持比较基础出问题往往是参数配置问题。常见的是挂在目标板上后提示nfs: server X not responding, still trying这种就是网络层面连通或重传次数设置不合理还有的是挂载成功后读写全部Read-only file system这就是服务端导出选项或客户端挂载选项把读写禁了。举个例子我在一块RK平台的板子上宿主机/srv/nfs/rootfs作为根目录exports文件这样写/srv/nfs/rootfs 192.168.1.0/24(rw,sync,no_root_squash,no_subtree_check)目标板上执行mount -t nfs -o nolock,rw,vers3 192.168.1.100:/srv/nfs/rootfs /mnt/rootfsnolock这个参数经验上建议加上因为v3默认会尝试调用rpc.statd做文件锁目标板环境里这套服务经常没启动加上nolock能直接省掉很多莫名其妙的Input/output error。确保rw同时出现在服务器端的导出选项和客户端的挂载选项里有一边漏了就只读。3.2 目标板上remount rw失败的排查链路开发阶段更新系统分区最常用的命令是adb root adb remount或者shell里手动执行mount -o rw,remount /system但实际中remount经常失败。请记住一个核心机制内核不允许对一个文件系统执行与初始化挂载选项冲突的重新挂载尤其涉及只读转可写时块设备本身不能被占用写缓存或处于不安全状态。这句话展开就是三个常见原因第一个原因是分区本身有verity保护。在Android 10及以上版本很多系统分区用dm-verity把块设备设成了只读校验模式单纯remount改不了底层的dm目标即使VFS层允许也会被拒绝。对应解法是关闭AVB/verity验证这需要 unlock bootloader 并刷入 userdebug/eng 版本或者用adb disable-verity。第二个原因是块设备正忙。如果某个进程持续占用着该系统分区上打开的文件remount时内核无法安全切换读写状态会报Device or resource busy。排查方法是lsof f -- /system或fuser -m /system找出占用进程。第三个原因是陈旧挂载状态。文件系统以只读挂载时如果某些元数据写缓存没有完全落盘直接remount也会失败这种可以先sync再试。顺带一提sync后 remount 仍然失败的考虑是不是ext4的日志journal区出了问题。我在处理一个长时间异常断电的板子时卡在remount上怎么都过不去最后fsck.ext4 -fy /dev/block/xxx修复完日志问题立刻就好了。一定要先备份fsck在生产机上的风险你得自己权衡。3.3 Fastboot刷机时的分区只读问题还有一个高频场景在fastboot模式想fastboot erase cache或fastboot flash boot时报FAILED (remote: not allowed in locked state)。这也是文件系统问题的一种近亲——不是文件系统坏了而是bootloader处于locked状态禁止对分区做写操作。解决办法是fastboot oem unlock # 部分厂商是 fastboot flashing unlock解锁会清空数据这也是很多人刷机后数据全没了的原因。这类问题的本质是设备安全策略不是ext4故障但排查时如果不知道这条很容易往文件系统损坏方向瞎想。4. 存储空间假满、文件损坏和诡异的CPU 100%底层ext4的排查方法终于到真正的ext4底层问题。这类问题通常有比较明显的特征df显示空间足够但写文件报No space left on device文件读到一半报I/O错误设备莫名其妙卡顿、CPU被内核线程吃满。每一类都有清晰的排查路径。4.1 空间显示充足却No space left on device新手最容易懵的就是这个场景df -h /data还剩3Gtouch test却提示No space left on device。这里要普及一个知识点ext4不是只按块大小存文件它还要用inode记录文件的元数据权限、属主、时间戳、数据块指针等。inode数量在格式化时就固定了不会随着文件增大而增加。如果分区里塞满了大量碎片小文件比如某些App的缓存、日志文件可能会出现数据块还剩一堆但inode全耗光了的尴尬情况。排查命令df -i /data重点关注IUsed%和IFree两列。如果IUsed%接近100%那没跑了。解决思路是找到“小文件大户”清理# 统计/data下各目录的inode占用 for d in /data/*; do echo $d: $(find $d -type f 2/dev/null | wc -l); done | sort -t: -k2 -rn | head或者在/data下用du --inodes -d 3快速定位。清掉大量临时小文件后inode释放问题迎刃而解。这个坑在系统长期运行的设备和高频写入日志的Android盒子上特别常见。那还有没有可能是inode之外的问题有比如磁盘配额quota超限、F2FS与ext4混用等但inode耗尽是最容易忽略也最典型的。4.2 文件系统损坏kye、dmesg和fsck的配合异常断电、强制下电、硬件链路不稳都会导致ext4出现不一致。轻则个别文件损坏重则整个分区无法挂载。这类问题我在嵌入式设备上遇到得最多尤其是那些供电不稳、喜欢直接拔电源的项目。核心排查工具就是dmesg和fsck。内核在挂载时检测到ext4日志异常通常会打这样的日志EXT4-fs error (device mmcblk0p25): ext4_find_entry: ...或者EXT4-fs (mmcblk0p25): warning: mounting fs with errors, running e2fsck is recommended看到这类日志立刻停掉对该分区的写操作然后进入 recovery 模式或把盘挂到主机上做离线检查umount /dev/block/mmcblk0p25 fsck.ext4 -fy /dev/block/mmcblk0p25关于-f和-y多说一句-f是强制检查哪怕文件系统标记为clean也执行完整扫描-y是对所有修复询问自动回答yes。在无人值守的设备上-y很实用但也要知道它可能在某些极端情况下丢弃部分受损数据。重要设备上最好先做块级镜像再修复。另外要养成一个习惯任何Android/嵌入式文件系统排查都不要只依赖一套工具。e2fsck用于检查ext4debugfs可以在文件系统离线时查看和修改底层结构比如找回被删除的文件前提是块还没被覆盖blkid查看块设备UUID和文件系统类型排查挂载错分区的问题。这几样配合起来基本能应对90%的底层损坏问题。4.3 线上CPU 100%时的文件系统排查视角关于线上服务器的CPU使用达到100%了如何排查、定位和解决这个话题虽然听起来更偏通用运维但在Android设备上也会出现类似问题——系统卡顿到几乎不可用top看到kworker或jbd2占满CPU。这里有一个重要的排查方向jbd2是ext4的日志提交线程它持续占满CPU往往意味着文件系统在做大量的元数据提交或反复重放日志底层存储性能也极差或出现坏块。排查链路可以这样走先top -H看CPU占用最高的线程如果看到jbd2/mmcblk0p25-8这种名字就锁定是文件系统日志线程在作怪。接着用dmesg | tail看有没有大量的I/O错误再用iostat -x 1如果有看%util、await的异常情况把问题细化到是块设备性能瓶颈、坏块重试还是日志区反复报错。如果指向坏块/存储寿命问题大概率得换硬件或调整文件系统的挂载参数例如commit600降低日志提交频率能显著减少存储写入负载。值得强调的是CPU 100%只是一个症状入口文件系统只是可能导致CPU跑满的源头之一也存在内存回收风暴、驱动异常等其他可能。这里想提醒的就是排查时不要只看进程列表要把内核线程和I/O栈一起看进去才不会在应用层绕圈子。5. 日常自救清单把常见问题在5分钟内分个类为了让上面这些经验在日常工作中更可用我习惯把存储/文件系统问题先在脑子里快速分个类。你可以把它当成一张自查表每次报障都先过一遍能省下大量无头苍蝇式的排查时间。症状第一怀疑对象首要排查命令常见解法能df能看到分区但无法访问挂载点未挂载或挂载参数不对mount | grep /data重新挂载检查fstab参数写文件Permission deniedSELinux或DAC权限dmesg | grep avc调整SELinux策略或chmodAndroid/data目录内容为空分区存储限制换root / 授权App授予MANAGE_EXTERNAL_STORAGE空间够但写不了inode耗尽df -i /data清理小文件掉电后文件打不开文件系统不一致dmesg | grep EXT4-fsfsck离线修复remount失败verity或设备被占用lsof f -- /system关闭verity或kill占用进程CPU被jbd2占满日志提交异常/坏块top -Hdmesg换盘或调整挂载参数这套自查表里隐藏的一个核心方法论是先定位层级用户态/挂载层/内核块设备层再选中具体工具最后才决定操作。做文件系统排查这么多年我发现绝大多数疑难杂症最后都是某一个环节的小问题只是大部分人习惯在错误的层级里反复试探才把事情越搞越复杂。最后分享一个我个人的操作习惯无论是问题排查还是日常维护把原始命令输出存成日志文件。用adb shell dmesg dmesg_$(date %Y%m%d_%H%M%S).log这类方式保存现场比事后回忆可靠得多。很多诡异问题第一现场的信息稍纵即逝错过那几分钟可能得再折腾几小时才能抓回来。
阅读完成 · 觉得有帮助?