Android 9.0 system分区挂载读写:原理、实践与故障排查
2026/9/18 21:17:51 网站建设 项目流程

我前阵子帮人调一台Android 9.0的设备,对方的需求很简单——往/system目录里塞一个运营商预装包。结果一顿操作下来,光是让system分区变成可写就折腾了整整一个下午。这事儿要是放在Android 7、8时代,adb rootadb remount基本就完事了,但9.0之后的分区架构、校验机制和权限模型全都变了,老一套玩法直接失效。这篇文章就把我这次踩坑的全过程整理出来,包括原理、步骤、还有各种报错的排查思路,给还在跟system分区较劲的朋友一个参考。

老规矩,先说你最关心的问题:这个操作到底能干什么?修改/system分区的内容,主要应用在几个场景:给系统应用做定制与替换、往系统目录预置so库或框架文件、修改build.prop等配置文件以调整系统行为、以及在CTS/GMS认证相关的适配调试中修改系统级参数。适配的人群主要是做ROM定制、企业设备方案、系统级工具链开发的工程师,也包括一部分想在真机上做深度折腾的发烧友。整个过程我尽量讲得直白一些,但涉及命令行和系统原理的部分没法绕开,建议你跟着步骤边看边操作。

1. 为什么Android 9.0挂载system和以前不一样

要理解挂载操作怎么变得这么难,得先搞清楚Android 9.0在系统架构层面动了哪些手脚。这还真不是Google拍脑袋改的,每一层变化背后都有它的目的,但副作用就是——我们这些做定制的人,操作门槛被大幅拉高了。

1.1 从rootfs到system-as-root的转变

在Android 8.0及之前的版本中,系统启动时由boot分区里的ramdisk提供根文件系统(rootfs),/system分区则作为独立分区在init进程后期挂载。这种情况下,只要内核里没有额外的封锁,adb root之后对/system执行mount -o rw,remount是很顺理成章的事。

Android 9.0开始,Google强制推行了system-as-root的启动方式,也就是说根文件系统不再由独立的ramdisk承载,而是直接把整个/system分区作为根文件系统来挂载。这样做的好处是启动速度更快、OTA升级更可靠,也更容易实现无缝更新。但副作用也很明显:根文件系统默认就是只读挂载的,并且在启动早期就会被内核以只读方式锁定,后面想再改挂载标志,就没那么简单了。

底层机制的变化带来一个直接现象:如果你在Android 9.0上执行adb remount,大概率会看到类似remount of the / superblock failed: Permission denied的报错。这并非命令不存在,而是因为根分区的超级块不允许被重新挂载为可写状态。

1.2 dm-verity与动态分区的双重限制

Android 9.0是dm-verity全面铺开的版本。dm-verity是内核层的一个块设备校验机制,它会对分区的每个数据块计算哈希值,并和分区头部保存的根哈希做比对,一旦数据被篡改,系统直接拒绝访问。这套机制的初衷是防止恶意程序篡改系统文件后获得持久化驻留能力,出发点没毛病,它也确实让Android设备的系统完整性提升了一大截。

但对我们来说,这意味着即便你用root权限强行把system分区挂载成rw,只要dm-verity处于开启状态,每次读取被修改过的块,I/O层就会报错,应用启动时直接崩溃或提示文件损坏。所以光会挂载还不够,还得先把dm-verity关掉,否则改了等于白改。

动态分区虽然是在Android 10才全面铺开的,但在Android 9.0的某些设备上已经可以看到雏形,比如部分骁龙845平台已经使用/dev/block/mapper/system这样的设备映射路径。如果你的设备走的是这个模式,那mount的时候就必须认准逻辑分区的设备节点,而不是物理分区节点,不然系统根本找不到指定的分区。

1.3 SELinux强制模式下的写权限问题

SELinux在Android 9.0上保持强制(Enforcing)状态,它的策略文件预编译进了内核和sepolicy联合体中。光有Linux文件权限(chmod 777)还不够,SELinux的Type Enforcement机制会拦截一切未经授权的进程对system目录的写操作。

说实话,Android 9.0这套SELinux策略比之前任何一代都要严格。尤其是adbd这个守护进程,它在user版本上跑在untrusted_app域里,根本没有任何写system的权限路径。只有userdebug和eng版本才会给adbd开放一个相对宽松的su域或直接允许adb root调用。这也是为什么网上很多教程里,第一件事就强调必须刷userdebug/eng版本固件。

综合来看,Android 9.0挂载system涉及到启动架构、块设备校验、强制访问控制三层机制的交织。只看单一层面去尝试,往往顾此失彼。我把这些限制拆解开讲,是希望你在动手之前先建立完整的认知模型,后面的操作才可能一次走通。

2. 准备工作:固件版本、解锁状态与root方案选型

在真正执行挂载之前,你需要确认手头的设备到底处在什么状态。这一步没做对,后面每一步都可能是无效操作。根据我这次的实际经历,整理了下面几个核心判断点。

2.1 必须明确设备是user版还是userdebug/eng版

这是整个流程里最容易忽略、也是影响最大的一项。很多朋友拿到设备就直接开搞,结果怎么弄都root不了,最后才发现手里的固件是普通user版本。

  • user版本:就是正常量产销售的系统。adb root默认被禁用,adb remount不能用,selinux强制开启。想在此基础上做system分区修改,必须先刷一个允许调试的boot镜像或采用其他root方案,流程会绕不少。
  • userdebug版本:在user基础上开放了adb rootadb remount、部分selinux权限降级。这是做系统定制最常用的固件版本,也是Google官方推荐给开发者的调试版本。
  • eng版本:工程师版本,限制最少,adb root默认开启,selinux通常为Permissive模式,但这版本一般不会出现在量产设备上。

判断当前设备是哪个版本,可以在设置里查看“版本号”或者执行:

adb shell getprop ro.build.type

如果输出是user,那你后续的操作就得按照user版本的特殊流程来走;如果是userdebug,那恭喜你,配合解锁的bootloader,整个流程会顺畅非常多。

2.2 Bootloader解锁状态检查

Android 9.0的设备几乎都带有AVB(Android Verified Boot)校验,Bootloader锁定状态下,任何对系统分区的修改都会在下次启动时触发校验失败,甚至直接变砖。因此解锁Bootloader是绕不开的一步。

检查解锁状态的通用方式因品牌而异,但一般可以通过fastboot命令确认:

adb reboot bootloader fastboot getvar all

在输出信息中找unlocked这一项。如果显示no,就需要先在OEM网站上申请解锁码或者使用品牌官方的解锁工具执行解锁操作。这里要提醒一句,解锁Bootloader会清空设备所有数据,并且不同品牌对解锁的支持差异非常大——有的品牌直接开放,有的则需要等待审核,还有一部分国行设备甚至不提供任何解锁渠道。动手之前务必评估清楚。

另外,解锁Bootloader之后,设备的dm-verity校验默认仍然处于开启状态,所以还需要用额外命令显式关闭它,具体做法我在下一章展开。

2.3 root方案与Magisk的取舍

在Android 9.0上,Magisk是我个人最推荐的root方案。它通过修补boot镜像的方式实现systemless root,不直接改动system分区,而是通过magisk镜像机制实现文件覆盖,这样系统应用能感知到文件存在,但system原本的分区却保持干净。整个挂载与修改流程因此能减少很多因根分区污染引发的问题。

Magisk的安装步骤通常如下:

  1. 在能联网的设备上安装Magisk Manager APK。
  2. 获取当前设备的boot.img(可以在系统更新包里解包,或者直接从设备上dump)。
  3. 打开Magisk Manager,选择“安装”,然后选择“修补boot镜像文件”,引导选择boot.img。
  4. 将修补后的boot.img拉回电脑,通过fastboot刷入:
fastboot flash boot magisk_patched.img

如果你的设备刚好是userdebug版本,其实也可以不依赖Magisk,直接用原厂userdebug boot配合adb root完成整个操作。Magisk的附加价值在于:即使设备重启后adb remount失效,Magisk的镜像机制仍能帮助你保留对system目录的修改效果。对需要长期使用的设备来说,Magisk方案明显更稳。

3. 实操:Android 9.0挂载system读写的完整流程

好了,原理和准备都铺垫完了,下面进入正题。这一章我会按照实际操作的先后顺序,演示从连接设备到最终确认system可写的完整流程,并针对每个关键步骤解释底层逻辑。这里以一台已经解锁Bootloader、已刷入Magisk修补镜像的Android 9.0设备为例。

3.1 连接设备并确认root权限

用USB连接设备后,首先在电脑上执行:

adb devices

确认设备已经出现在列表内。如果你用的是userdebug固件,可以直接执行:

adb root

这条命令会让adbd以root权限重启,输出一般是:

adbd is already running as root

如果设备是user版本但已装好Magisk,那么adb root很可能不可用,此时需要通过Magisk Manager授予adb shell超级用户权限。adb shell连接后执行su,能拿到带#提示符的shell就说明root可用。

adb shell su

我个人的习惯是先用id命令确认当前上下文:

id

如果能输出uid=0(root)并且selinux上下文是sumagisk相关域,说明shell已经具备操作system分区的基础权限。

3.2 关闭dm-verity与AVB校验

在Android 9.0上,直接remount之前必须先关闭dm-verity,否则内核在读取系统文件时会抛出I/O错误。关闭dm-verity的方法是:

adb disable-verity

执行这条命令后,adbd会在用户数据分区的特定位置写入一个标记,下次启动时bootloader或内核就会跳过verity校验。命令执行完需要重启设备:

adb reboot

设备重启后,再重新执行adb root(或su)。此时你可以通过以下命令来确认verity确实处于关闭状态:

adb shell cat /sys/module/dm_verity/parameters/enable

注意不同设备上这个路径可能略有差异。如果输出为N0,说明校验已经关闭。有部分设备(尤其是动态分区设备)还需要额外关闭AVB校验:

adb shell avbctl disable-verification

avbctl在部分精简系统里并不存在,如果没有这个命令,那就需要回到fastboot模式,通过fastboot命令关闭验证:

fastboot flash vbmeta vbmeta.img

其中vbmeta.img是一个关闭了验证标志的镜像,通常需要自己构造或者从第三方资源获取。这一步在不同品牌上差异很大,如果你的设备执行avbctl disable-verification没有报错,那就不需要折腾vbmeta了。

3.3 使用adb remount挂载system为读写

完成上述准备后,最核心的一步就来了。在Android 9.0上,正确挂载system分区的命令仍然是:

adb remount

但和旧版本不同的是,这个命令在9.0上会同时完成两件事:重新挂载/system为可写,并把/vendor等其他只读分区一并处理(如果它们也是动态分区的一部分)。执行成功后,设备通常会重启一次,并在重启过程中应用新的挂载参数。

重启完成后,再次连上设备并执行:

adb shell mount | grep " /system "

你期望看到类似这样的输出:

/dev/block/dm-0 /system ext4 rw,seclabel,relatime,errors=panic 0 0

注意第二个字段和第三个字段之间的挂载选项中出现了rw,这就说明system分区现在是可写的了。如果没有看到rw,那就要检查是否漏掉了disable-verity这一步,或者是设备本身不支持remount。

3.4 手动mount方案:当remount不可用时

部分Android 9.0设备对adb remount支持得不够好,特别是某些厂商定制系统,remount命令可能直接报错:

dm_verity is enabled on the system partition

这时需要手动执行mount命令。最常见的方式是:

adb shell mount -o rw,remount /

因为Android 9.0已经是system-as-root架构,根目录/实际上就是/system,所以直接对根文件系统执行remount就能达到目的。如果这条命令报错,再尝试指定具体设备节点:

mount -o rw,remount -t ext4 /dev/block/mapper/system /system

如果你的设备是动态分区布局,/dev/block/mapper/system这个路径大概率存在。非动态分区的老设备则需要先查看当前挂载信息:

mount | grep " /system "

找到对应的设备节点后,再用上面的命令格式执行remount。手动mount成功之后,用touch /system/test.txt验证一下写权限。如果文件能创建成功,那说明整个分区已经可写了。

3.5 修改文件后的权限与SELinux上下文修复

很多人在这一阶段会掉进坑里:文件确实写进去了,但设备一重启或者相关服务一访问就报错,日志里一堆avc: denied的记录。这基本都是因为SELinux上下文没设置对。

Android系统对/system下不同类型文件有固定的SELinux标签要求,比如:

  • /system/app下的应用目录通常是u:object_r:system_file:s0u:object_r:apk_data_file:s0
  • /system/lib64下的库文件通常是u:object_r:system_lib_file:s0
  • /system/bin下的可执行文件通常是u:object_r:system_file:s0

如果你用root shell直接往/system里放文件,文件默认会继承shell进程的SELinux上下文,这往往是错误类型。修改后的文件必须显式设置正确的上下文:

chcon -R u:object_r:system_file:s0 /system/your_file

如果是推送一个完整的应用目录,还需要一并处理权限位:

chmod -R 755 /system/your_app_dir chown -R root:root /system/your_app_dir

这一步不做好,即使system分区是rw的,应用也跑不起来。我在帮朋友改预装应用时就遇到过这种问题,之前一直以为是没有正确挂载,后来仔细看logcat才发现全是SELinux拦截,改完标签之后才恢复正常。

3.6 使修改永久生效

system分区在重启后通常会被重新以只读方式挂载,这是Android启动流程决定的。但如果你已经按照上面的步骤修改了文件内容,那么即使分区的挂载状态恢复为ro,修改过的内容依然会保留在磁盘上——除非设备执行了恢复出厂设置或者OTA升级。

如果你希望通过Magisk实现更灵活的systemless修改,可以把要覆盖的文件放到/data/adb/modules/模块名/system/目录下,Magisk会在启动时通过mirror机制把这个目录里的内容覆盖到system对应路径。这个方式的好处是:

  • 不需要真正改动system分区
  • 后续想要还原时,直接删除模块即可
  • 不会触发dm-verity校验,也不影响OTA升级

对长期维护设备的场景,我强烈建议用这种方式替代直接改system分区。如果只是临时调试,那直接改rw也就够了。

4. 修改system后的常见连锁问题与排查

system分区虽然能写了,但修改完毕之后的连锁问题一点也不少。这一章我整理了几个典型场景,都是我自己或身边同事实际碰到过的,一并写出来供你排查时参考。

4.1 系统应用签名校验导致崩溃或回滚

/system/app下的应用可能是最常见的需求。但很多ROM在编译时会启用系统应用签名校验(PackageManagerService的scanPackageOnly阶段会检查签名是否匹配系统签名)。你替换进去的应用如果是第三方签名,系统最直接的反应就是——启动时崩,或者开机后自动回滚到原版应用。

解决思路有几个:

  • 将应用编译时使用平台签名(platform key),这需要拿到固件对应的platform.x509.pem和platform.pk8密钥对。
  • 关闭系统的签名校验,这种改动比较复杂,需要修改框架代码,不推荐新手尝试。
  • 不做应用替换,改用Magisk模块做应用增强或Xposed模块的方式实现功能注入。

如果只是要预置一个APK而无需更新系统内置应用,可以在/system/app下新建独立目录并放入APK,只要签名不是和现有系统应用冲突,一般不会触发签名校验问题。但还是要注意,预置应用的APK需要经过zipalignapksigner签名,否则可能被PackageManager拒绝解析。

4.2 修改build.prop后无法开机

/system/build.prop是系统属性配置的集中地,被人为修改后导致开机卡死在开机动画甚至循环重启的案例太多了。原因一般是键值内容不合法,或者修改了受ro.前缀保护的只读属性。

ro.开头的属性在系统启动早期就被加载进属性服务,之后不可修改。如果你直接改build.prop里的ro.product.model等字段,最常见的现象就是:属性读取异常导致启动服务判断错误,系统框架直接crash。

所以要改build.prop时我建议:

  1. 先用adb pull /system/build.prop拉到本地备份。
  2. 只修改与你需求强相关的字段,不要顺手动一些无关项。
  3. 修改完后保持UNIX换行符,不要用Windows记事本直接编辑(最好用VS Code或Notepad++)。
  4. 每次改动后,最小改动原则验证,别一次性堆叠多项修改,否则出了问题都不知道是哪一项引起的。

如果已经改崩了,不用急着重新刷机。只要bootloader还是解锁状态,可以进入fastboot模式把原版build.prop通过adb push等方式恢复——但前提是你的data分区没有被锁,这一招对数据也有要求。更稳妥的做法是修改前就备份boot镜像和build.prop,这样能快速回滚。

4.3 SELinux avc拒绝但system分区显示rw

这个坑我前面提到过,这里再展开细说。当你的system分区明明显示为rw,往里面push文件也不报错,但相关功能就是不行时,请第一时间去抓取内核日志:

adb shell dmesg | grep "avc: denied"

如果看到大量类似下面的输出:

avc: denied { write } for pid=1234 comm="system_server" name="xxx" dev="dm-0" ino=456 scontext=u:r:system_server:s0 tcontext=u:object_r:system_file:s0 tclass=file permissive=0

那就说明是SELinux在拦截。解决办法取决于你想让哪个进程获得访问权限:

  • 如果是普通调试进程,只需确保SELinux处于Permissive模式,加上adb shell setenforce 0即可。
  • 如果是系统服务(如system_server)要访问你新放的文件,必须在上层sepolicy策略中补充对应的allow规则,比如在system/sepolicy中新增allow system_server system_file:file write;然后重新编译boot镜像,或者用Magisk模块动态加载sepolicy规则。

对我这次的实际需求来说,最省事的办法是把设备临时切到permissive模式,验证功能逻辑没问题后再去完善selinux策略。如果直接改sepolicy重新打包镜像,工作量会大不少,而且涉及重新签名,新手不建议一来就折腾这块。

4.4 adb remount在重启后失效

adb remount这个命令带有的临时性比很多人预期的要强。在Android 9.0上,执行adb remount之后确实可以修改system分区,但重启后一切回到原样——挂载选项恢复为ro,之前对挂载状态做的改动全部被重置。

这是因为remount命令只修改了当前启动会话中的挂载状态,并没有修改分区表或做持久化标记。想要一劳永逸,可以考虑:

  • 使用Magisk模块,这是最推荐的方案。
  • 直接修改fstab文件中的flag,但这需要连fstab本身都是可写的才行,逻辑上有点先有鸡还是先有蛋的问题。
  • /system/etc/init/下放一个rc脚本,每次开机时自动执行mount -o rw,remount /

第三种方法实测有效,但需要你在system可写状态时就把脚本放好,而且脚本要处理SELinux上下文和开机时序。我自己试过用init脚本实现开机自动remount,比预想中麻烦,因为init阶段某些服务的SELinux上下文会限制脚本的执行。如果只是临时调试,个人建议别在这个方向上浪费时间。

5. 基于这次实操的几点总结与补充建议

在整个流程都走通之后,我把这次的经验沉淀成几条原则,给自己备忘的同时也分享给读者参考。

第一条:尽量用userdebug或eng版本固件进行开发调试。量产user版本的限制是嵌套式的,每个环节都增加了额外成本。如果项目预算允许,直接让固件组编译一个userdebug版本,比在user版本上各种绕路要高效太多。我这次操作能够顺利走通,很大程度上就是因为设备本身是userdebug版本。

第二条:破坏性操作前必须做完整备份。刷机、解锁、关闭verity这些操作都有不可逆的风险,备份至少包含boot分区、system分区和当前完整OTA包。强烈建议全分区备份,这样无论怎么折腾都能回得去。很多朋友只备份了数据分区,真出问题时才发现system回不到原始状态,反而更麻烦。

第三条:读写system只是手段,不是目的。大多数实际需求,比如替换预装应用、修改系统配置、预置so库,其实都有绕过system直改的替代方案。Magisk模块已经在绝大多数场景下可以完美替代直接修改system分区,而且维护成本低得多。对于需要长期稳定运行的设备,优先考虑systemless方案永远是对的。

如果你现在正准备对一台Android 9.0设备做system分区的读写操作,我的建议是:先把这篇文章里的原理读透,对照自己的设备状态做好判断,再按照3-5章的步骤逐步操作。遇到问题不要慌,多半是某个前置条件没满足,回头排查一下通常都能解决。如果实际操作中还有新问题,欢迎在留言区发日志片段,我看到了会尽量帮你分析。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询