原理分析"/>
Android 9.0 A/B升级原理分析
概述:
A/B 系统更新,也称为无缝更新,用于确保可运行的启动系统在无线 (OTA) 更新期间能够保留在磁盘上。这样可以降低更新之后设备无法启动的可能性,也就是说,用户需要将设备送到维修/保修中心进行更换和刷机的情况将有所减少。
用户在 OTA 期间可以继续使用设备。在更新过程中,仅当设备重新启动到更新后的磁盘分区时,会发生一次宕机情况。即使 OTA 失败,设备也仍然可以使用,因为它会启动到 OTA 之前的磁盘分区。您可以再次尝试下载 OTA。建议仅针对新设备通过 OTA 实现 A/B 系统更新。
A/B 系统更新使用称为 update_engine
的后台守护进程以及两组分区。这两组分区称为插槽,通常为插槽 A 和插槽 B。系统从其中一个插槽(“当前插槽”)运行,但运行的系统不会访问“未使用的”插槽中的分区(用于正常操作)。
传统升级方式与ab升级方式采用的设备分区如下:
ab升级方式中boot和system均保留了两份,所以对设备的存储容量要求会比传统升级方式要大的多。
典型应用场景如下,假定当前从slot B中启动
- 正常情况:系统正在从其当前插槽(插槽 B)运行。目前为止尚未应用任何更新。系统的当前插槽是可启动、成功且活动的插槽。
- 正在更新:系统正在从插槽 B 运行,因此,插槽 B 是可启动、成功且活动的插槽。由于插槽 A 中的内容正在更新,但是尚未完成,因此插槽 A 标记为不可启动。在此状态下,应继续从插槽 B 重新启动。
- 已应用更新,正在等待重新启动:系统正在从插槽 B 运行,插槽 B 的状态为可启动且成功,但是插槽 A 过去标记为活动(因此现在标记为可启动)。插槽 A 尚未被标记为成功,引导加载程序应该尝试从插槽 A 启动几次。
- 系统重新启动到新的更新:系统首次从插槽 A 运行,插槽 B 的状态仍为可启动且成功,而插槽 A 仅可启动,且仍然处于活动但不成功的状态。在进行几次检查之后,用户空间守护进程应将插槽 A 标记为成功。
update升级包分析
一、目录结构
update.zip包的目录结构,如下图所示:
二、目录结构分析
下面分析以全量包升级为准。
1、META文件夹
bootargs.txt bootargs启动参数
filesystem_config.txt system目录文件权限
recovery.fstab 分区表
2、META-INF目录
目录结构如下:|---META-INF/
`|CERT.RSA
`|CERT.SF
`|MANIFEST.MF
`|----com/
`|----android/
`|----metadata
`|----google/
`|----android/
`|----update-binary
`|----updater-script
CERT.RSA:与签名文件相关联的签名程序块文件,它存储了用于签名JAR文件的公共签名。
CERT.SF:这是JAR文件的签名文件,其中前缀CERT代表签名者。
MANIFEST.MF:这个manifest文件定义了与包的组成结构相关的数据。类似Android应用的mainfest.xml文件。
metadata文件:是描述设备信息及环境变量的元数据。主要包括一些编译选项,签名公钥,时间戳以及设备型号等。
updater-script:此文件是一个脚本文件,具体描述了更新过程。我们可以根据具体情况编写该脚本来适应我们的具体需求。
update-binary:是一个二进制文件,相当于一个脚本解释器,能够识别updater-script中描述的操作。
文件怎么来的:
1、CERT.RSA、CERT.SF、MANIFEST.MF、metadata文件是自动生成的(怎么生成详见下文签名部分)
2、update-binary一般是系统编译过程中自动生成的升级脚本,但是这部分是可以通过手动编辑(详见后文update-binary脚本语言详解)
3、update-binary在sdk中哪个部分
./device/hisilicon/bigfish/build/emmc.mk
cp -a $(PRODUCT_OUT)/system/bin/updater $(EMMC_PRODUCT_OUT)/update/file/META-INF/com/google/android/update-binary
又上面脚本部分可知update-binary其实就是updater,updater部分是通过源码编译生成的,源码路径在:
bootable/recovery/updater/
3、system目录
system/目录的内容在升级后会放在系统的system分区。主要用来更新系统的一些应用或则应用会用到的一些库等等。
有的时候会以打包的形式(system.img)存在。
4、userdata目录
userdata目录,用来更新系统中的用户数据部分。这部分内容在更新后会存放在系统的/data目录下。
有的时候会以打包的形式(userdata.img)存在。
5、其他文件
*.img是更新各个分区分区所需要的文件。
三、如何制作一个update升级包
update升级包一般有两种方式得到:
一种是通过编译系统得到update.zip包(make ota-package)
另一种是通过自己手动创建的方式得到update升级包
updater-script语法详解
update升级包主要的启动入口为updater-script,因此先介绍一下updater-script的基础语法。
1、mount
语法:
mount(type, location, mount_point);
说明:
type="MTD" location="<partition>" 挂载yaffs2文件系统分区;
type="vfat" location="/dev/block/<whatever>" 挂载设备。
例如:
mount("MTD", "system", "/system");
挂载system分区,设置返回指针"/system”
mount("vfat", "/dev/block/mmcblk1p2", "/system");
挂载/dev/block/mmcblk1p2,返回指针"/system”
2、Unmount
语法:
unmount(mount_point);
说明:
mount_point是mount所设置产生的指针。其作用与挂载相对应,卸载分区或设备。此函数与mount配套使用。
例如:
unmount("/system");
卸载/system分区
3、Format
语法:
format(type, location);
说明:
type="MTD" location=partition(分区),格式化location参数所代表的分区。
例如:
format("MTD", "system");
格式化system分区
4、Delete
语法:
delete(<path>);
说明:
删除文件<path>
例如:
delete("/data/zipalign.log");
删除文件/data/zipalign.log
5、delete_recursive
语法:
delete_recursive(<path>);
说明:
删除文件夹<path>
例如:
delete_recursive("/data/dalvik-cache");
删除文件夹/data/dalvik-cache
6、show_progress
语法:
show_progress(<fraction>,<duration>);
说明:
为下面进行的程序操作显示进度条,进度条会根据<duration>进行前进<fraction>
例如:
show_progress(0.1, 10);
show_progress下面的操作可能进行10s,完成后进度条前进0.1(也就是10%)
7、package_extract_dir
语法:
package_extract_dir(package_path, destination_path);
说明:
释放文件夹package_path至destination_path
例如:
package_extract_dir("system", "/system");
释放ROM包里system文件夹下所有文件和子文件夹至/system
8、package_extract_file
语法:
package_extract_file(package_path, destination_path);
说明:
解压package_path文件至destination_path
例如:
package_extract_dir("my.zip", "/system");
解压ROM包里的my.zip文件至/system
9、Symlink
语法:
symlink(<target>, <src1>, <src2>,...);
说明:
建立指向target符号链接src1,src2,……
例如:
symlink("toolbox", "/system/bin/ps");
建立指向toolbox的符号链接/system/bin/ps
10、set_perm
语法:
set_perm(<uid>, <gid>,<mode>, <path>);
说明:
设置<path>文件的用户为uid,用户组为gid,权限为mode
例如:
set_perm(1002, 1002, 0440, "/system/etc/dbus.conf");
设置文件/system/etc/dbus.conf的所有者为1002,所属用户组为1002,权限为:所有者有读权限,所属用户组有读权限,其他无任何权限。
11、set_perm_recursive
语法:
set_perm_recursive(<uid>,<gid>,<dir-mode>,<file-mode>,<path>);
说明:
设置文件夹和文件夹内文件的权限
例如:
set_perm_recursive(1000, 1000, 0771, 0644, "/data/app");
设置/data/app的所有者和所属用户组为1000,app文件夹的权限是:所有者和所属组拥有全部权限,其他有执行权限;app文件夹下的文件权限是:所有者有读写权限,所属组有读权限,其他有读权限。
12、ui_print
语法:
ui_print("str");
说明:
屏幕打印输出"str"
例如:
ui_print("It's ready!");
屏幕打印It’s ready!
13、run_program
语法:
run_program(<path>);
说明:
运行<path>脚本
例如:
run_program("/system/xbin/installbusybox.sh");
运行installbusybox.sh脚本文件
14、write_raw_image
语法:
write_raw_image(<path>, partition);
说明:
写入<path>至partition分区
例如:
write_raw_image("/tmp/boot.img", "boot")
将yaffs2格式的boot包直接写入boot分区
15、assert
语法:
assert(<sub1>,<sub2>,<sub3>);
说明:
如果执行sub1不返回错误则执行sub2,如果sub2不返回错误则执行sub3一次类推。
例如:
assert(package_extract_file("boot.img", "/tmp/boot.img"),
write_raw_image("/tmp/boot.img", "boot"),
delete("/tmp/boot.img"));
执行package_extract_file,如果不返回错误则执行write_raw_image,如果write_raw_image不出错则执行delete
16、getprop
语法:
getprop("key")
说明:
通过指定key的值来获取对应的属性信息。
例如:
getprop(“ro.product.device”)
获取ro.product.device的属性值。
17、ifelse
语法:
ifelse(condition, truecondition, falsecondition)
说明:
condition----------------要运算的表达式
Truecondition-----------当值为True时执行的 Edify脚本块
Falsecodnition-----------当值为False时执行的 Edify脚本块
列如:
ifelse(isuserversion(),
ui_print(" ----user version----- "),
ui_print(" --------- ");
set_perm(0, 2000, 04750, "/system/xbin/su");
);
根据isuserversion()返回值判断,如果true,打印" ----user version----- ";如果false,打印" --------- ",并获取su权限。
注意:值得注意的是false分支,执行了两个语句,只需通过‘;’来分割开就可以了。
18、其他
向上一个例子中isuserversion()不是常见的函数,这个是什么呢,怎么识别,这就需要特有的update-binary。
update-binary相当于一个脚本解释器,能够识别updater-script中描述的操作。
ota_from_target_files
OTA包的制作是通过执行ota_from_target_files来实现的。ota_from_target_files文件内容较多,下面分析一下几个主要的方法:
看下参数简介:
Given a target-files zipfile, produces an OTA package that installs that build. An incremental OTA is produced if -i is given, other
更多推荐
Android 9.0 A/B升级原理分析
发布评论