Android Kernel 文件系统中的 SHARED_BLOCKS

最近换了新手机。每次拿到新玩具当然都会想要折腾一番,更不用说上次换手机已经是四年以前的事情了。然而在折腾的过程中我却发现,现在的安卓系统似乎已经不再是我以前所知道的那个样子了。嘛毕竟技术是在不停地进步,在飞速变化的科技领域四年已经足够改变很多事物了。这些变化之中让我最感到不适应的就是这篇文章的主题 EXT4_FEATURE_RO_COMPAT_SHARED_BLOCKS 这个 Google 自创的 ext4 文件系统扩展功能。

让我把这个故事从头说起。


拿到新手机后,第一件干的事情当然是解锁 BL 获取 root 权限,毕竟只有获取 root 权限之后才有更宽泛的 customize 空间,其中就包括强行把毫无意义的系统的拍照音效关掉。

于是我按照一直以来的做法,打开 RE 进入系统分区,准备把快门音效文件替换掉。然而,当我在替换的时候却发现,无法将系统分区 mount 为 r/w。第一反应是,RE 出 bug 了?于是我用 adb shell 进手机,尝试将系统分区重新挂载时却得到:

1
2
$ mount -o remount,rw /product
'/dev/block/dm-9' is read-only

?怎么回事,查看一下挂载发现

1
2
$ df -h | grep product
/dev/block/dm-9 2.9G 2.9G 9.0M 100% /product

可以看到该分区几乎是满的,但这并不足以说明为什么无法挂载为 rw。
查看一下 fstab:

1
2
3
...
product /product ext4 ro wait,slotselect,avb=vbmeta_system,logical,first_stage_mount,readahead_size_kb=128
...

也没有发现什么有用的信息。再继续查看 mount:

1
2
3
...
/dev/block/dm-9 on /product type ext4 (ro,seclabel,relatime)
...

仍然没有相关信息。直到我用不属于安卓内核的 tune2fs 查看时才发现:

1
2
3
4
$ tune2fs -l /dev/block/dm-9
...
Filesystem features: ext_attr dir_index filetype extent sparse_super large_file huge_file uninit_bg dir_nlink extra_isize shared_blocks
...

可以看到 fs features 的最后有一个 shared_blocks
这个 shared_blocks 是什么玩意?我翻遍了内核中关于 ext4 文件系统的 feature flag 的文档,但没有找到半点与这个 flag 相关的记载。
当时我就疑惑了,难道这是 AOSP 自己扩展的文件系统 feature?于是来到 AOSP 源码库搜索,在 platform 代码库中找到了相关资料:

1
2
3
4
5
6
...
#define EXT4_FEATURE_RO_COMPAT_BIGALLOC 0x0200
#define EXT4_FEATURE_RO_COMPAT_METADATA_CSUM 0x0400
#define EXT4_FEATURE_RO_COMPAT_SHARED_BLOCKS 0x4000
#define EXT4_FEATURE_RO_COMPAT_VERITY 0x8000
...

引自: system/extras/ext4_utils/include/ext4_utils/ext4.h#542

在这个头文件里有一个 EXT4_FEATURE_RO_COMPAT_SHARED_BLOCKS = 0x4000 的宏定义,然而 内核文档的 read-only compatible features field 定义 中根本就不存在这个值!(从该文档的表格中可以发现枚举值直接从 0x2000RO_COMPAT_PROJECT 跳到了 0x8000RO_COMPAT_VERITY

AOSP Platform 里有这个扩展 feature,那么 AOSP Kernel 呢?来到 kernel 库,找到相关代码:

1
2
3
4
5
6
...
#define EXT4_FEATURE_RO_COMPAT_METADATA_CSUM 0x0400
#define EXT4_FEATURE_RO_COMPAT_READONLY 0x1000
#define EXT4_FEATURE_RO_COMPAT_PROJECT 0x2000
#define EXT4_FEATURE_RO_COMPAT_VERITY 0x8000
...

引自:common/fs/ext4/ext4.h#2019-2022

AOSP Kernel 和 原版 Kernel 一样没有这个 feature!

到这里就很明显了,系统分区之所以无法 remount 为 rw 是因为 fs 里存在一个 Kernel 无法识别的 RO_COMPAT_SHARED_BLOCKS feature 存在,正因为 Kernel 里无法识别这个 feature,所以之前无论我查看 /proc/mounts 也好,查看 fstab 也好都没有相关信息,只能用 Platform 的工具 tune2fs 才能识别到这个 flag。

既然这样,那么只要把 RO_COMPAT_SHARED_BLOCKS 这个 flag 去掉应该就可以使系统分区能够挂载为 rw 了。于是我尝试

1
$ e2fsck -E unshare_blocks /dev/block/dm-9

却被提示分区空间不足。
为什么?我突然想起一开始 df 的时候就已经看到,系统分区的空间几乎是装满的。

也就是说,想要解除 fs 里被共享的那些 blocks,理所当然就需要拿出相对应的空闲 blocks 来进行分配,然而分区里的空闲 blocks 被用光了,所以无法解除 SHARED_BLOCKS 这个 flag。

于是便造成了一个死结:没有足够的空闲空间,则无法解除 SHARED_BLOCKS;无法解除 SHARED_BLOCKS,则无法将系统分区 mount 为 rw。

所以系统分区永远无法 mount 为 rw!

不得不说,AOSP 的开发者们真是想出了一个“杰出”的 idea 来防止用户更改系统分区。

当然,我们还可以通过 Magisk 模块的 bind mount 或者 KernelSU 的 overlayfs 来修改系统分区,但这些修改都是只能在 init 时进行的,想要像以前那样 on the fly 地修改系统分区已经是不可能了。

最后,这个 SHARED_BLOCKS 的 feature 其实早就不是什么新鲜事了,早在 2019 年 Google 就已经实装在 AOSP 中了,而我上一次更换机种大概正好就是在那之前不久,所以正好错过了这一换代,这么多年来一直使用着能够将系统分区挂载为 rw 的手机。

并且在我检索资料的过程中还发现了一段相当有趣的对话。

Subject: About read-only feature EXT4_FEATURE_RO_COMPAT_SHARED_BLOCKS

这个 thread 的最下面有一位来自 AOSP 相关部分的开发成员的回复,可以了解到这个 feature 的开发动机和一些幕后的事由。

References

The Linux Kernel documentation - ext4 Data Structures and Algorithms
Android Code Search
LinuxLists - Subject: About read-only feature EXT4_FEATURE_RO_COMPAT_SHARED_BLOCKS
Twitter - John Wu’s Post

 Comments
Comment plugin failed to load
Loading comment plugin