文件系统调试:ext4、f2fs、sdcardfs、存储挂载、权限问题
文件系统这块,说实话,是Android系统调试里最容易让人「头大」的部分。你想想看,一个手机里同时跑着ext4、f2fs、sdcardfs,还有各种挂载点和权限控制,任何一个环节出问题,轻则App闪退,重则整机变砖。我这些年排查过的存储相关Bug,少说也有上百个了,今天就把核心经验给你捋一遍。
一、先搞清楚Android存储的「三驾马车」
Android的存储架构,说白了就是三层:底层块设备、中间文件系统、上层权限封装。我们调试时最常打交道的,就是ext4、f2fs和sdcardfs这三个。
| 文件系统 | 典型用途 | 调试要点 |
|---|---|---|
| ext4 | system分区、cache分区 | 日志恢复、inode损坏 |
| f2fs | data分区(用户数据) | GC行为、NAND友好性 |
| sdcardfs | 模拟SD卡、文件权限重映射 | uid/gid映射、挂载选项 |
我个人习惯,遇到存储相关的问题,第一件事就是先确认当前用的是哪个文件系统。别笑,真有同事debug了半天,才发现data分区是f2fs,却用ext4的工具去修,那肯定不对路子。
二、ext4调试:老牌文件系统的「坑」
ext4在Android里主要用在system分区和cache分区。虽然它很成熟,但也不是没坑。
核心调试命令:
# 查看ext4文件系统信息
dumpe2fs -h /dev/block/bootdevice/by-name/system
# 检查文件系统一致性
e2fsck -fn /dev/block/bootdevice/by-name/cache
# 查看日志(journal)
debugfs -R "logdump" /dev/block/bootdevice/by-name/system
我在项目中遇到过一个问题:OTA升级后system分区校验失败,设备无法启动。当时查了半天,最后用dumpe2fs一看,发现文件系统的mount count字段异常,导致e2fsck认为文件系统需要强制检查。嗯,这里要注意,ext4的mount count和check interval两个字段,在Android里经常被厂商魔改,调试时一定要先确认原始值。
警告:千万不要在设备运行时对已挂载的ext4分区执行e2fsck -f,这会导致文件系统损坏。必须先umount,或者进入recovery模式操作。
三、f2fs调试:专为闪存设计的「新秀」
f2fs是三星为NAND闪存设计的文件系统,现在基本是Android data分区的标配。它的调试思路和ext4完全不同。
为什么?因为f2fs没有传统的「fsck」概念,它用的是「checkpoint」和「roll-forward recovery」。说白了,就是通过日志来保证一致性。
f2fs调试常用命令:
# 查看f2fs超级块信息
cat /sys/fs/f2fs/features
# 查看GC状态
cat /sys/kernel/debug/f2fs/status
# 手动触发GC(慎用)
echo "1" > /sys/fs/f2fs/[设备名]/gc_urgent
# 查看文件系统统计
fsck.f2fs -d /dev/block/bootdevice/by-name/userdata
我曾经遇到一个案例:某款手机用久了,data分区写入速度越来越慢。用户反馈说「存照片越来越卡」。我查了f2fs的GC状态,发现gc_urgent一直为0,说明后台GC没有正常工作。后来在kernel里加了f2fs的trace点,才发现是某个App频繁创建小文件,导致GC被频繁打断。解决方案是调整了gc_no_gc_sleep_time参数,让GC有更长的连续运行时间。
技巧:f2fs的fsck.f2fs命令有个-d参数,可以控制调试级别。我一般用-d 2,既能看到关键错误,又不会刷屏。
四、sdcardfs:权限映射的「黑盒」
sdcardfs是Android 8.0引入的,用来替代老的sdcardd守护进程。它的核心作用,就是把底层文件系统的权限,映射成App能看到的「模拟SD卡」权限。
说白了,就是让App以为自己在操作SD卡,实际上数据还是存在data分区里。这个映射过程,就是sdcardfs的挂载选项控制的。
查看sdcardfs挂载信息:
# 查看所有sdcardfs挂载点
mount | grep sdcardfs
# 典型输出:
# /data/media/0 /storage/emulated/0 sdcardfs rw,nosuid,nodev,noexec,relatime,...
# 关键挂载选项:
# - gid=1023: 媒体存储组
# - mask=0007: 默认权限掩码
# - derive_gid: 自动派生GID
sdcardfs的权限问题,是Android存储调试里最常见的坑。我记得有一次,某个App无法在/storage/emulated/0/Android/data/下创建文件。查了半天,发现是sdcardfs的mask选项设置成了0027,导致App没有写入权限。正确的做法是,对于Android/data目录,sdcardfs应该使用mask=0007,让App有足够的权限。
注意:sdcardfs的权限映射,依赖于底层文件系统的uid/gid。如果底层文件系统(比如f2fs)的权限乱了,sdcardfs也会跟着乱。所以排查sdcardfs问题时,一定要先确认底层文件系统的权限是否正确。
五、存储挂载:从init.rc到vold的完整链路
Android的存储挂载,不是简单的mount -t ext4。它有一套完整的流程,从init.rc脚本开始,经过vold守护进程,最终完成挂载。
我画了一张图,帮你理清这个流程:
调试挂载问题时,我一般会先看dmesg里vold的日志:
# 查看vold日志
logcat -b events | grep -i vold
# 查看内核挂载日志
dmesg | grep -i "mount\|f2fs\|ext4\|sdcardfs"
# 查看当前挂载状态
cat /proc/mounts | grep -E "ext4|f2fs|sdcardfs"
我曾经遇到一个奇葩问题:某款设备在开机时,data分区挂载失败,导致设备卡在开机动画。查了dmesg,发现vold在挂载f2fs时,报了一个EINVAL错误。后来对比了正常设备和问题设备的fstab文件,发现是fs_mgr_flags字段写错了,少了一个wait选项。嗯,这种低级错误,有时候就是最坑人的。
六、权限问题:文件系统调试的「终极Boss」
权限问题,说白了就是「谁可以读、谁可以写、谁可以执行」。在Android里,权限问题尤其复杂,因为涉及SELinux、DAC(传统Unix权限)、sdcardfs映射三层。
权限排查三板斧:
- 看SELinux上下文:
ls -Z /data/media/0 - 看DAC权限:
ls -l /data/media/0 - 看sdcardfs映射:
cat /proc/self/mountinfo | grep sdcardfs
我记得有一次,某个App无法读取/storage/emulated/0/DCIM目录下的照片。用ls -Z一看,发现SELinux上下文是u:object_r:fuse:s0,但App的SELinux上下文是u:r:untrusted_app:s0,两者不匹配。后来在sepolicy里加了对应的allow规则,问题解决。
技巧:如果你不确定某个文件的SELinux上下文是否正确,可以用restorecon -R -v /data/media/0来恢复默认上下文。这个命令在调试时特别有用。
七、实战案例:一次完整的文件系统调试
最后,我给你分享一个我实际处理过的案例,把今天讲的知识串起来。
问题现象:某款手机在升级到Android 12后,用户反馈「无法保存截图到相册」。
排查过程:
- 先看
dmesg,没有发现文件系统错误。 - 看
mount输出,确认data分区是f2fs,sdcardfs挂载正常。 - 用
ls -Z /data/media/0/Pictures/Screenshots,发现SELinux上下文异常,变成了u:object_r:media_rw_data_file:s0,而正常应该是u:object_r:userdata_file:s0。 - 用
restorecon -R -v /data/media/0/Pictures/Screenshots恢复上下文。 - 问题解决。
你看,其实很多时候,文件系统的问题并不复杂,关键是要有清晰的排查思路。先确认文件系统类型,再看挂载状态,最后查权限。按这个顺序来,大部分问题都能搞定。
好了,关于文件系统调试,我就说这么多。记住,遇到存储问题别慌,先看日志,再动手。这是我这些年debug下来最深的体会。