
我前阵子帮人调一台Android 9.0的设备对方的需求很简单——往/system目录里塞一个运营商预装包。结果一顿操作下来光是让system分区变成可写就折腾了整整一个下午。这事儿要是放在Android 7、8时代adb root加adb 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 root、adb remount、部分selinux权限降级。这是做系统定制最常用的固件版本也是Google官方推荐给开发者的调试版本。eng版本工程师版本限制最少adb root默认开启selinux通常为Permissive模式但这版本一般不会出现在量产设备上。判断当前设备是哪个版本可以在设置里查看“版本号”或者执行adb shell getprop ro.build.type如果输出是user那你后续的操作就得按照user版本的特殊流程来走如果是userdebug那恭喜你配合解锁的bootloader整个流程会顺畅非常多。2.2 Bootloader解锁状态检查Android 9.0的设备几乎都带有AVBAndroid 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的安装步骤通常如下在能联网的设备上安装Magisk Manager APK。获取当前设备的boot.img可以在系统更新包里解包或者直接从设备上dump。打开Magisk Manager选择“安装”然后选择“修补boot镜像文件”引导选择boot.img。将修补后的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如果能输出uid0(root)并且selinux上下文是su或magisk相关域说明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注意不同设备上这个路径可能略有差异。如果输出为N或0说明校验已经关闭。有部分设备尤其是动态分区设备还需要额外关闭AVB校验adb shell avbctl disable-verificationavbctl在部分精简系统里并不存在如果没有这个命令那就需要回到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,errorspanic 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:s0或u: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需要经过zipalign和apksigner签名否则可能被PackageManager拒绝解析。4.2 修改build.prop后无法开机/system/build.prop是系统属性配置的集中地被人为修改后导致开机卡死在开机动画甚至循环重启的案例太多了。原因一般是键值内容不合法或者修改了受ro.前缀保护的只读属性。ro.开头的属性在系统启动早期就被加载进属性服务之后不可修改。如果你直接改build.prop里的ro.product.model等字段最常见的现象就是属性读取异常导致启动服务判断错误系统框架直接crash。所以要改build.prop时我建议先用adb pull /system/build.prop拉到本地备份。只修改与你需求强相关的字段不要顺手动一些无关项。修改完后保持UNIX换行符不要用Windows记事本直接编辑最好用VS Code或Notepad。每次改动后最小改动原则验证别一次性堆叠多项修改否则出了问题都不知道是哪一项引起的。如果已经改崩了不用急着重新刷机。只要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 pid1234 commsystem_server namexxx devdm-0 ino456 scontextu:r:system_server:s0 tcontextu:object_r:system_file:s0 tclassfile permissive0那就说明是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章的步骤逐步操作。遇到问题不要慌多半是某个前置条件没满足回头排查一下通常都能解决。如果实际操作中还有新问题欢迎在留言区发日志片段我看到了会尽量帮你分析。