Android系统分区与升级
本文将基于我个人的理解,通俗的介绍Android系统的分区和升级逻辑,尽量不涉及过多代码。
本文的内容基于高通开源Android Q(10.0),部分内容更新至Android S(12.0),只涉及high-level-operating-system(Android)部分。
一、传统分区结构(non-A/B)
首先简单梳理一下Android传统的分区结构。
bootloader:设备启动后,会先进入bootloader程序,这里会通过判断开机时的按键组合(也会有一些其他判断条件,暂不赘述)选择启动到哪种模式,这里主要有Android系统、recovery模式、fastboot模式等。 |
boot:包含有Android系统的kernel和ramdisk。如果bootloader选择启动Android系统,则会引导启动此分区的kernel并加载ramdisk,完成内核启动。 |
system:包含有Android系统的可执行程序、库、系统服务和app等。内核启动后,会运行第一个用户态进程init,其会依据init.rc文件中的规则启动Android系统组件,这些系统组件就在system分区中。将Android系统组件启动完成后,最后会启动系统app —— launcher桌面,至此完成Android系统启动。 |
vendor:包含有厂商私有的可执行程序、库、系统服务和app等。可以将此分区看做是system分区的补充,厂商定制ROM的一些功能都可以放在此分区。 |
userdata:用户存储空间。一般新买来的手机此分区几乎是空的,用户安装的app以及用户数据都是存放在此分区中。用户通过系统文件管理器访问到的手机存储(sdcard)即此分区的一部分,是通过fuse或sdcardfs这类用户态文件系统实现的一块特殊存储空间。 |
recovery:包含recovery系统的kernel和ramdisk。如果bootloader选择启动recovery模式,则会引导启动此分区的kernel并加载ramdisk,并启动其中的init继而启动recovery程序,至此可以操作recovery模式功能(主要包括OTA升级、双清等)。 |
cache:主要用于缓存系统升级OTA包等。双清就是指对userdata分区和cache分区的清理。 |
misc:主要用于Android系统和bootloader通信,使Android系统能够重启进入recovery系统并执行相应操作。 |
传统分区结构下,系统的OTA升级流程比较简单,主要过程如下:
Android系统收到服务端下发的OTA推送,将OTA包下载至cache分区。
OTA包下载完成后,将向misc分区写入指令,表明下次启动时进入recovery模式并使用该OTA包进行升级。
重启手机。
重启后最先进入bootloader,bootloader会先判断按键组合、电源寄存器等,随后会读取misc分区的内容并解析。由于步骤2中已经向misc分区写入了指令,此处bootloader读取指令后会引导启动recovery系统。
进入recovery,读取cache分区中的OTA包,并解析其中的升级脚本,按照其指令对系统各个分区进行升级。如果recovery自身也需要升级,会在此过程中向system中写入recovery-from-boot.p文件,这是一个recovery升级所需要的patch。
recovery会清除misc分区。
重启手机。
重启后最先进入bootloader,判断按键组合、电源寄存器、misc分区内容等,默认情况会启动Android系统,此时已经是OTA升级后的新版本系统。
新版本Android系统启动后,会检查是否存在recovery-from-boot.p文件,如果存在,则会对recovery进行升级。
二、A/B分区结构
在Android O之后,Google引入了一种新的分区结构,称为A/B分区,与之对应,传统分区结构被称为non-A/B分区。
A/B分区结构,顾名思义,将系统分区分成了A和B两个槽(slot),手机启动时会选择A槽或者B槽启动,运行过程中仅使用当前槽位的分区。一旦当前运行的槽出现问题,系统仍可以选择另一个槽进行启动,从而保证系统良好的可用性。
采用A/B分区结构,能够实现无缝升级。例如用户正在运行A槽,此时收到OTA推送,则系统会在后台一边下载OTA数据,一边同时对B槽进行升级。当B槽系统升级完成,用户会收到重启提示,此时重启手机将自动切换到B槽的新版本系统。在此过程中,仅重启操作是会被用户感知的,这个重启与普通重启的耗时没有什么区别。
如果OTA失败,也仅仅是待升级的槽出现问题,可以重新尝试OTA,并不会影响用户当前运行的系统。
由于A/B分区结构可以实现一边从服务端获取OTA数据,一边直接写入待升级的槽,不需要临时存储OTA包的空间,因此不再需要在cache或userdata分区预留足够空间。
Google定义了A/B槽的几种标识:
bootable:标识该槽的系统是否可以启动。有时也用unbootable来标识(例如高通),含义与bootable相反。
successful:标识该槽的系统是否成功启动过,仅当该槽系统能够启动、运行、进行OTA升级时,才会从用户态标记该槽为successful。
active:标识该槽是否是当前运行的系统,两个槽中只有一个能标记为active。
系统重启后,在zygote启动前,init会调用update_verifier服务通过dm-verity机制校验本次升级的镜像,通过后则会被标记为successful。如果系统当前active的槽反复多次启动都没能标记为successful,则将该槽标记为unbootable,并将另外一个槽标记为active。
下面看看A/B分区结构发生了哪些变化:
bootloader:功能同non-A/B的bootloader,只是此处会根据A/B槽的bootable、successful、active等标识来选择启动哪个槽。根据不同厂商的实现,可以是唯一的不区分A/B的bootloader,也可以自定义,例如高通的实现bootloader是由唯一的pbl(此分区无法擦写)来选择A/B槽,启动xbl_a/xbl_b,再启动abl_a/abl_b。 |
boot_a/boot_b:包含kernel和recovery的ramdisk。recovery打包在boot分区中,则不再需要recovery分区。并且recovery系统也不再负责OTA升级(由Android系统中update_engine服务负责),仅负责双清等其他操作。 |
system_a/system_b:功能同non-A/B的system分区,只是区分了A和B两个槽。 |
vendor_a/vendor_b:功能同non-A/B的vendor分区,只是区分了A和B两个槽。 |
userdata:功能同non-A/B的userdata,并且用户数据仅存储一份,不区分A/B。 |
misc:功能同non-A/B的misc,不区分A/B。 |
persist:用来存储一些持久化数据,不会随着双清、OTA等操作被清除。不区分A/B。 |
这里需要说明一下,recovery集成在boot中,是由TARGET_NO_RECOVERY和BOARD_USES_RECOVERY_AS_BOOT等变量决定的。如下图所示为Google官方对于A/B分区结构的配置: