ARTICLE DETAIL

资讯详情

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

Android Ext4文件系统排查指南:从文件打不开到CPU飙高

Android Ext4文件系统排查指南:从文件打不开到CPU飙高 最近连着处理了三个看起来八竿子打不着的工单一个用户说App里下载的文件打不开预览一个说线上服务器CPU突然冲到100%还有一个直接把一段报错丢过来——unable to chmod /storage/emulated/0/android/data/...: Operation not permitted。排查到最后全都能绕回到Android设备的Ext4文件系统上。今天把这几个问题的排查思路、命令和踩坑记录整理出来不是教科书式的文档纯粹是我实际处理问题时用到的路子希望对遇到类似情况的兄弟有点帮助。1. 这类问题的表象与底层逻辑1.1 先看现象看起来完全不相关的几个场景第一个场景最典型用户反馈游戏下载的更新包在App里点开预览是黑屏用文件管理器去/storage/emulated/0/android/data/com.tencent.tmgp.sgame/files/...这个路径看到的却是空目录甚至提示文件不存在。我拿到设备后第一反应也觉得邪门文件明明在App内显示下载完成了为什么在系统文件管理器里找不到第二个场景更刺激一台用于跑Android模拟实例的Linux服务器不是桌面模拟器是开源方案那种无头实例某个时刻CPU 100%但跑着的Java进程占用却不高负载却比进程数高了一截业务请求全部卡住。第三个场景看起来最简单就是调试阶段想对某个App的私有目录执行chmod 777结果被系统拒绝。表面上这三个问题风马牛不相及一个是路径可见性一个是系统性能一个是权限操作。但最后它们的根因都指向同一个地方——文件系统的挂载方式、inode状态、日志提交行为和VFS层的交互逻辑。只要文件系统这一层出问题上面所有业务表现都会变得奇奇怪怪。1.2 底层逻辑一次文件访问究竟要经过哪些关卡要理解为什么会“莫名其妙”先得把一次文件访问的路径理清楚。一个App进程执行open(/storage/emulated/0/xxx)或read()时调用会先进入Linux内核的VFS虚拟文件系统层。VFS是一个抽象接口层向上给应用程序提供统一API向下把请求分发到具体文件系统的实现上。Android里/storage/emulated/0这个路径并不直接对应一个真实物理分区它通常通过FUSE用户态文件系统或者sdcardfs某些老设备挂载最终数据落地在/data/media/0而/data分区使用的正是Ext4。所以一条文件操作实际要经过四层应用程序、VFS、某个具体文件系统驱动Ext4或FUSE、块设备驱动。而问题可能出在任何一层也可能是层与层之间配合时出的岔子。比如文件“找不到”可能是路径拼接错误也可能是FUSE挂载节点没起来CPU 100%可能是Ext4日志线程在疯狂重试也可能是VFS层某个锁导致任务陷入不可中断睡眠权限操作失败可能是SELinux的拒绝也可能是挂载选项带了nosuid、nodev之类的限制。排查文件系统问题本质上就是在这些层级里做二分定位。2. Ext4在Android里的角色和需要掌握的核心概念2.1 /data 分区为什么是 Ext4Android系统的物理分区布局各厂商略有不同但/data作为用户数据分区很长一段时间默认的文件系统就是Ext4。用户安装的应用、应用私有数据、系统设置、甚至降级后的外部存储媒体文件/data/media都在这个分区上。Ext4是Linux社区在Ext3基础上改进来的日志文件系统支持大文件、大容量、扩展属性、延迟分配稳定性和兼容性经过长期验证所以Android生态选择了它。由于Android 10之后系统分区开始采用ERofs等只读压缩文件系统来节省空间很多人误以为Android全面抛弃了Ext4。实际上动态分区里的system、vendor可以用只读文件系统但data分区为了支持写入、掉电恢复和兼容性绝大多数机型依然保留Ext4。也就是说App下载、缓存、数据库、SharedPreferences最终都在Ext4上读写。所以排查文件打不开、空间明明不够、日志写不进去这类问题最终都绕不开对Ext4的检查。2.2 必须理解的几个Ext4关键机制排查Ext4问题前这几个概念建议至少能说清楚它们是什么以及异常时可能呈现什么症状超级块文件系统的“总控表”记录块大小、inode总数、空闲块数、挂载UUID等信息。超级块损坏时文件系统无法挂载设备表现为开机卡在logo或者反复重启。inode每一个文件或目录都对应一个inode里面存权限、时间戳、数据块指针但不存文件名。文件名放在目录项dentry里。inode数量是固定的一旦耗尽即使磁盘还有大量空间也会报No space left on device。块组和位图文件系统把空间划分成多个块组每个块组有自己的inode位图和块位图用来管理分配。位图损坏会导致孤儿文件、重复分配严重时fsck也救不回来。日志journalExt4是日志文件系统写入操作会先记录日志再更新实际数据。日志能保证崩溃后元数据一致但代价是增加一次写盘。日志提交线程内核里的jbd2/xxx-8如果出现异常就可能表现为CPU 100%或者IO延迟飙升。延迟分配delayed allocation数据块先放在内存里稍后统一落盘。好处是减少碎片、提升性能坏处是如果设备突然掉电新写数据容易丢。在线上服务器场景里延迟分配配合大量小文件写入可能导致内存脏页堆积随后瞬间刷盘造成IO尖峰。2.3 排查前先准备好这些工具下面这些工具和命令是排查Android Ext4问题时最常用的建议提前确认你的环境和权限支持哪些工具/命令用途关键参数或补充adb shell df -h /data查看/data分区空间使用情况-i可以看inode使用率adb shell mount查看所有挂载点及挂载选项配合grep ext4、grep emulatedadb shell dumpsys diskstats查看系统统计的各分区IO、读写数据量适合做历史趋势参考adb shell ls -laZ查看目录/文件的SELinux上下文排查权限问题必看adb shell cat /proc/mounts查看更准确的挂载参数和来源比mount信息更全e2fsck/fsck.ext4检查并修复Ext4文件系统只能在umount或recovery下运行iostat、perf、blktraceLinux服务器侧IO和内核行为分析服务器场景必备如果设备没有rootdf、mount、ls -lZ基本都能用但更深入到find /data和du这类操作大概率需要root。遇到线上服务器问题时记得优先保留现场先执行ps -eo pid,stat,comm,wchan:30和cat /proc/pid/stack再考虑重启或迁移。3. 从“文件打不开/找不到”到“权限挂载异常”的实操案例3.1 理解/storage/emulated/0的权限模型Android 11开始强制分区存储机制应用不能随便扫其他App的目录即使目录存在也看不到或者看到了但打不开。本质上是因为/storage/emulated/0/Android/data/下面每个子目录都用包名隔离应用A访问应用B的私有目录会被FUSE层拦截返回空目录或者Operation not permitted。用户从文件管理器去看某个游戏目录文件管理器本身也只是一个应用同样受分区存储限制。所以“文件明明存在但用户找不到”很可能不是文件丢失而是路径对用户不可见。遇到这种场景第一件事别急着怀疑磁盘坏了。先用adb shell切换到实际目录确认文件存在再看应用的可见性和权限。例如adb shell ls -la /storage/emulated/0/Android/data/com.package/files/ adb shell ls -laZ /storage/emulated/0/Android/data/com.package/files/如果文件列表里有内容说明系统里文件是存在的。接下来要检查是不是FUSE挂载异常可以看adb shell mount | grep emulated正常会看到类似tmpfs /storage/emulated ...或/dev/fuse /storage/emulated ...的挂载记录挂载选项里会有nosuid,nodev有些还有noexec。如果这里没有挂载记录应用和文件管理器看到的/storage/emulated/0就是空壳文件自然找不到。3.2 一步步定位到“不可见”的原因我处理这个工单时实际排查顺序是这样的第一步确认应用自身的数据。/storage/emulated/0/Android/data/下能不能列出内容如果应用把文件写在了自己的内部存储/data/data/com.package/files而不是外部存储路径那么用户从文件管理器当然看不到。很多App所谓的“下载目录”实际指向内部私有目录只是UI上显示一个假的路径这种情况是App设计问题不是文件系统问题。第二步确认是否被媒体扫描忽略。如果文件是图片、视频、音频而应用下载后没有主动通知系统媒体库扫描通过MediaScannerConnection.scanFile()或广播那么相册、文件管理器里就无法看到因为现代Android文件管理器用MediaStore做索引不会实时去扫存储卡。这不是Ext4故障但用户感知就是“下载的文件找不到”。第三步检查SELinux上下文。如果之前用root做过重挂载、把文件夹从A目录移到了B目录可能导致SELinux上下文不对。比如文件应有的context是media_rw_data_file却变成了unlabeled那么某些有SELinux限制的守护进程就访问不了。此时会报权限错误dmesg里经常能看到avc: denied关键字。经验拿到“文件找不到”报错时先判断是“实际不存在”还是“可见性受限”。如果adb shell ls能看到但用户App看不到绝大多数情况是分区存储的策略或媒体库索引问题而不是磁盘数据丢了。3.3 案例chmod被拒绝不是权限位的问题第三个场景adb shell chmod 777 /storage/emulated/0/Android/data/com.x.ehviewer报Operation not permitted。很多刚接触Android调试的人第一反应是文件系统只读实际上FUSE挂载天然禁止通过chmod更改权限位因为权限模型由FUSE守护进程统一管理直接对挂载节点执行chmod根本不会生效。加上这类路径属于受保护目录SELinux规则也会拦一道。正确的处理方式是对于App自己的私有目录在应用代码里通过Context.getFilesDir()、getExternalFilesDir()去操作App运行时是能正常读写的。对需要跨应用共享的文件使用MediaStore API写入公共目录或者用SAF存储访问框架让用户授权访问目录。调试阶段想临时拿数据不要想着改权限而是用run-as com.package直接进入应用沙盒adb shell run-as com.package ls -l files如果设备已root且确实需要修改FUSE上的权限可以尝试重新挂载或者关闭验证但这种操作在正式环境绝对不要做否则会被安全机制拉黑。这个案例告诉我们的核心逻辑看到Operation not permitted不要只盯着chmod权限位先看挂载选项和SELinux策略。权限位只是Linux权限体系的一个面另外两个面往往更常出问题。4. 空间、inode耗尽与文件系统损坏的定位4.1 空间满最容易被忽略的“元凶”Android设备用久了/data分区满了会带来一堆奇怪表现应用启动缓慢、日志写不进去、数据库事务失败、下载的文件只在缓存里没落盘最终用户感知就是“文件打不开”。最直接的检查命令是adb shell df -h /data adb shell df -i /datadf -h看字节空间df -i看inode空间。很多开发者只看前者忽略了后者。如果字节空间还剩几十MB但inode使用率达到100%那一样无法创建新文件。定位空间占用大户时如果设备已rootadb shell du -sh /data/* 2/dev/null | sort -rh | head -20但要注意du统计的是当前可见文件如果一个文件已经被删除但还有进程持有句柄比如某个App正在写日志日志文件被rm掉了但fd还是打开的df显示空间没释放du却找不到任何大文件。这时用Linux的lsof L1能列出被删除但仍被打开的进程。Android上需要rootadb shell里一般没有lsof可以下个busybox或者直接从/proc/*/fd里筛查。清理空间时不要粗暴删除尤其是/data/data下的系统应用数据。最稳妥的是在系统设置里清应用缓存或者用命令adb shell pm trim-caches 100G这条命令会清理系统内所有应用缓存直到释放指定空间比手动删文件夹安全得多。4.2 inode耗尽的坑小文件太多有次我遇到一个手机储存空间明明还有20GB但任何App都无法下载文件安装更新也会失败。df -h看起来一切正常df -i一查/data的inode使用率已经到了99%。原因是一个社交App疯狂生成小文件缓存每个文件才几KB但数量却高达几十万。inode耗尽后的典型症状No space left on device但空间并不少。mkdir、touch、open全部报错连系统都可能弹出“存储空间不足”。重启设备后问题依旧因为是永久inode耗尽不是临时占位。排查命令adb shell df -i /data adb shell find /data -xdev -type f 2/dev/null | wc -l定位哪个目录文件数量最多可以直接冒烟枚举adb shell for d in /data/data/*; do echo -n $d ; find $d -type f 2/dev/null | wc -l; done | sort -k2 -rn | head解决方案清掉异常应用的数据缓存设置该类目录为SB如果应用设计有问题只能联系厂商。真正根除的手段是重新格式化并重建/data分区但那样会清空用户数据代价太大所以平时一定要对App的缓存目录做配额限制。4.3 文件系统损坏掉电后最怕的事移动设备经常遭遇电量耗尽、强制重启、恢复模式里误操作导致Ext4元数据不一致。轻度异常表现为某个目录打不开或者文件变成0字节重度异常直接开机进Recovery提示/data无法挂载。这时需要跑文件系统检查。在Android上找到分区路径是关键adb shell ls -l /dev/block/bootdevice/by-name/通常会有一个userdata或data符号链接指向实际块设备。要在系统完全启动前执行修复最好进Recovery模式通过adb shell进入后执行umount /data e2fsck -fy /dev/block/bootdevice/by-name/userdata注意几点e2fsck必须在文件系统未挂载的状态下运行否则会“因文件系统挂载”拒绝继续强行运行只会让损坏更严重。-f是强制检查因为Ext4有日志正常卸载后系统认为“干净”没有-f会直接跳过。-y对所有修复提示都回答yes自动化跑可以用但如果错误提示太多建议先备份镜像再修复。如果设备没有Recovery可用也可以拆下存储用PC工具做镜像分析但这对普通用户成本太高。一个更稳妥的操作习惯是遇到手机反复自动重启时先别让它反复开机直接进Recovery做fsck避免文件系统二次损坏。5. CPU 100%与Ext4/VFS的耦合线上服务器排查实录5.1 现象CPU打满但线程栈全在IO上服务器场景中Android系统通常作为容器或虚拟化实例运行在Linux主机上。某次线上告警一台主机的CPU核占用100%负载飙到32但用top看每个进程的CPU没有一个超过50%。这个状态很典型——CPU高不一定等于计算多也可能是大量进程陷入D状态不可中断睡眠后系统继续把它们的调度时间累计到“虚高”真正忙的其实是内核线程。我先用ps -eo pid,stat,comm,wchan:30抓一把ps -eo pid,stat,comm,wchan:30 | grep D果然大片D状态进程阻塞点集中在ext4_da_write_begin、jbd2_journal_commit_transaction、schedule这类内核函数上。这意味着问题出在Ext4的写路径而不是应用逻辑。5.2 从进程栈到块设备IO逐层定位定位思路分三步走先看IO负载iostat -x 1如果%util接近100%但rkB/s、wkB/s很低说明磁盘在处理大量小IO请求或者是磁盘物理链路有故障在重试。如果IO吞吐正常但进程仍然卡就要怀疑文件系统锁或日志机制。第二步抓数据包层面的块设备IOblktrace -d /dev/sda -o trace blkparse -d trace -o parsed.outblkparse能看到每个IO请求的下发时间、完成时间、队列深度。如果看到大量D2C设备完成到CPU响应时间超几百毫秒说明磁盘有问题如果Q2D请求入队到下发很大说明IO调度层拥堵如果两个都不大但任务还是卡那瓶颈在文件系统层比如某个inode锁被长时间持有。第三步确认是否是Ext4日志提交线程在烧CPU。直接看内核线程名的CPU占用top -b -n1 | grep jbd2jbd2/sdaX-8高CPU时典型原因是日志所在块设备写不进去。我碰到的一次是SSD几乎写满垃圾回收频繁导致文件系统日志提交每次都要等几百毫秒内核线程只能不断重试CPU和IO一起被打满。5.3 解决思路与踩坑提醒针对上面那种日志提交高延迟可以临时验证非生产环境测试用mount -o remount,barrier0 /data关闭barrier能减少落盘同步等待但会降低掉电一致性只适合临时缓解不适合长期使用。正规的修复手段包括清理磁盘空间降低GC压力、更换有故障的SSD、把数据迁移到新的文件系统实例。如果文件系统本身已经出现大量错误dmesg里能看到blk_update_request: I/O error这类关键词那就不是参数调优能解决的了需要备份数据、重建文件系统。这个case里还有个大坑我当时一看CPU高下意识就执行了重启结果重启后机器再也起不来。原因是我没有先保存dmesg和perf现场重启后原问题被掩盖了但磁盘上已经留下损坏的日志。后来靠之前随手打的perf record -a -g sleep 10的存档才定位到jbd2开了个大头。所以线上遇到文件系统级别问题第一动作永远是“留存证据”而不是“重启解决”。6. 排查速查表与避坑要点6.1 常见现象与排查命令速查现象可能原因优先排查命令App内文件打不开/预览黑屏路径不可见、媒体库未扫描、文件实际未落盘ls -la /storage/emulated/0/...、dumpsys media文件管理中看不到下载文件MediaStore索引缺失、使用私有目录、SAF权限不对检查应用存储路径触发扫描chmod/删除提示Operation not permittedFUSE权限模型、SELinux策略ls -laZ、dmesg存储空间足够但无法创建文件inode耗尽df -i /datadf满但du找不到大文件文件被删除仍有进程占用lsof L1开机反复重启卡在logo文件系统元数据损坏进Recovery跑e2fsck -fy服务器CPU虚高、大量D状态Ext4日志提交阻塞、磁盘性能劣化iostat -x 1、perf、查jbd2线程6.2 我踩过的五个坑第一不要在文件系统挂载状态下执行e2fsck。哪怕是只读挂载系统也可能在后台写数据强行运行大概率会把本可修复的问题搞成不可恢复。第二忽略SELinux是排查权限问题时最大的失误。很多“chmod已经777但还是无法访问”的情况其实是上下文标签错了修改权限位毫无意义。正确做法是ls -Z看标签必要时用restorecon或者调整策略。第三盲目清理/data/media/0下面的文件。它和/storage/emulated/0有映射关系如果直接删.thumbnails或者Android/data下的某个目录可能破坏应用的私有数据索引导致局部功能异常。第四App写文件一定要先确认路径是在内部存储还是外部存储。很多国产App会把文件写到Context.getFilesDir()但UI上却伪装成/storage/emulated/0/Download。这类问题根因在App文件系统再多折腾也没用。第五线上服务器CPU高时不要只盯用户进程。Linux上有大量内核线程很多文件系统故障都会让内核线程长时间占用CPU例如 jbd2、flush、kworker。先ps -eo stat,comm,wchan看一轮再动手往往能直接定位到具体内核模块。文件系统这一层平时不显山露水一旦出问题应用层的报错五花八门因为所有数据访问都得经过它。遇到“看起来不是文件系统问题”的疑难杂症时多想想VFS和Ext4也许答案就在dmesg的一行日志里。
返回列表