赞
踩
2013年7月至2015年6月在长虹担任Android系统研发工程师,主要负责长虹智能电视升级(OTA升级),研发平台是MST 628 和 MTK 5327等。
随着Android系统的快速发展,越来越多的智能终端设备搭载Android平台。Android系统升级的可以优化智能电视系统性能、更新系统内容。因此,Android系统升级在Android系统开发领域极其重要。如何保证Android系统升级的安全性、可靠性与可操作性。本文详细介绍了Android系统升级包的制作、Recovery模式的工作原理,TV终端升级包的下载、升级包的具体升级过程等。
Android系统升级的本质是对system、perm、data分区下的文件进行升级,按照升级包的差异来分可以分为整包升级与查分升级。整包升级首先将分区格式化,然后将升级包中的文件拷贝进去。差分升级则只升级文件的差异部分。如图(1.1)所示,系统升级主要包括升级包的制作与发布、终端升级包的下载以及Recovery模式下的具体升级几个过程。本文分九个部分讲述Android系统的系统升级。
第一部分是系统概述;
第二部分讲述Recovery模式,包括如何进入Recovery模式,进入Recovery模式后的操作以及Recovery模式的通信接口;
第三部分主要讲述升级包的制作;
第四部分讲述TV终端升级包的获取及升级策略;
第五部分讲述Android系统的三种启动模式;
第六部分讲述Android系统的模式服务;
第七部分讲述从reboot到Recovery;
第八部分讲述 Recovery服务的核心install_package函数;
第九部分讲述升级程序update_binary的执行过程。
图1-1
2.1 Recovery模式中的三个部分
Recovery的工作需要整个软件平台的配合,从通信架构上来看,主要有三个部分。
1) MainSystem:即正常启动模式(BCB中无命令),是用boot.img启动的系统,Android的正常工作模式。更新时,在这种模式中我们的上层操作就是使用OTA或则从SD卡中升级升级包。在重启进入Recovery模式之前,会向BCB中写入命令,以便在重启后告诉bootloader进入Recovery模式。
2) Recovery:系统进入Recovery模式后会装载Recovery分区,该分区包含recovery.img(同boot.img相同,包含了标准的内核和根文件系统)。进入该模式后主要是运行 Recovery服务(/sbin/recovery)来做相应的操作(重启、升级升级、擦除cache分区、擦除data分区等)。
3) Bootloader:除了正常的加载启动系统之外,还会通过读取MISC分区(BCB)获得来至Main system和Recovery的消息。
2.2 Recovery模式中的两个通信接口
在Recovery服务中上述的三个实体之间的通信是必不可少的,他们相互之间又有以下
两个通信接口。
通过CACHE分区中的三个文件:
1) Recovery通过/cache/recovery/目录下文件与Mainsystem进行通信,过程如下 :
/cache/recovery/command:这个文件保存着Main system传给Recovery的命令行。 –update_package=root:path: Main system将这条命令写入时,代表系统需要升级,进入Recovery模式后,将该文件中的命令读取并写入BCB中,然后进行相应的更新升级包的操作。
–wipe_data:擦除用户数据。擦除data分区时必须要擦除cache分区。
–wipe_cache: 擦除cache分区。
2)通过BCB(Bootloader Control Block):
BCB是bootloader与Recovery的通信接口,也是Bootloader与Main system之间的通信接口。存储在flash中的MISC分区,占用三个page,其本身就是一个结构体,具体成员 以及各成员含义如下:
struct bootloader_message{
char command[32];
char status[32];
char recovery[1024];
};
command成员:即当我们想要在重启进入Recovery模式时,会更新这个成员的值。另外在成功更新后结束Recovery时,会清除这个成员的值,防止重启时再次进入Recovery模式。
status:在完成相应的更新后,Bootloader会将执行结果写入到这个字段。
recovery:可被Main System写入,也可被Recovery服务程序写入。该文件内容格式为:
“recovery\n
\n
”
该文件存储的就是一个字符串,必须以recovery\n开头,否则这个字段的所有内容域会被忽略。“recovery\n”之后的部分,是/cache/recovery/command支持的命令。可以将其理解为Recovery操作过程中对命令操作的备份。Recovery对其操作的过程为:先读取BCB然后读取/cache/recovery/command,然后将二者重新写回BCB,这样在进入Main system之前,确保操作被执行。在操作之后进入Main system之前,Recovery又会清空BCB的command域和recovery域,这样确保重启后不再进入Recovery模式。
2.3 从Main System重启并进入Recovery模式
如图(2.1)所示为以上三个部分的通信过程
图(2.1)
首先从Main System开始,在Main System使用升级包进行升级时,系统会重启并进入Recovery模式。在系统重启之前,Main System定会向BCB中的command域写入boot-recovery(粉红色线),用来告知Bootloader重启后进入recovery模式。这一步是必须的。至于Main System是否向recovery域写入值我们在源码中不能肯定这一点。即便如此,重启进入Recovery模式后Bootloader会从/cache/recovery/command中读取值并放入到BCB的recovery域。而Main System在重启之前肯定会向/cache/recovery/command中写入Recovery将要进行的操作命令。
3.1 升级包的目录结构
|—-boot.img
|—-system/
|—-recovery/
|----recovery-from-boot.p
|—-etc/
|----install-recovery.sh
|CERT.RSA
|---META-INF/
|CERT.SF
|MANIFEST.MF
|----com/
|—-google/
|----android/
|—-update-binary
|----updater-script
|—-android/
`|—-metadata
以上是用命令make otapackage 制作的升级包的标准目录结构。
1)boot.img是更新boot分区所需要的文件。这个boot.img主要包括kernel+ramdisk。
2)system/目录的内容在升级后会放在系统的system分区。主要用来更新系统的一些应用或则应用会用到的一些库等等。
3)recovery/目录中的recovery-from-boot.p是boot.img和recovery.img的补丁(patch),主要用来更新recovery分区,其中etc/目录下的install-recovery.sh是更新脚本。
4)update-binary是一个二进制文件,相当于一个脚本解释器,能够识别updater-script描述的操作。
5)updater-script:此文件是一个脚本文件,具体描述了更新过程。我们可以根据具体情况编写该脚本来适应我们的具体需求。
6) metadata文件是描述设备信息及环境变量的元数据。主要包括一些编译选项,签名公钥,时间戳以及设备型号等。
7)我们还可以在包中添加userdata目录,来更新系统中的用户数据部分。这部分内容更新后会存放在系统的/data目录下。
8)升级包的签名:升级更新包在制作完成后需要对其签名,否则在升级时会出现认证失败的错误提示。而且签名要使用和目标板一致的加密公钥。加密公钥及加密需要的三个文件在Android源码编译后生成的具体路径为:
out/host/linux-x86/framework/signapk.jar
build/target/product/security/testkey.x509.pem
build/target/product/security/testkey.pk8 。
命令make otapackage制作生成的升级包是已签过名的,如果自己做升级包时必须手动对其签名。 以上命令在升级包所在的路径下执行,其中signapk.jar testkey.x509.pem以及testkey.pk8文件的引用使用绝对路径。升级 是我们已经打好的包,update_signed.zip包是命令执行完生成的已经签过名的包。
9)MANIFEST.MF:这个manifest文件定义了与包的组成结构相关的数据。类似Android应用的mainfest.xml文件。
10)CERT.RSA:与签名文件相关联的签名程序块文件,它存储了用于签名JAR文件的公共签名。
11)CERT.SF:这是JAR文件的签名文件,其中前缀CERT代表签名者。
4.1 功能
TV终端升级包是由终端升级程序upgradesystem.apk控制的,如图(4.1)所示,该程序完成一下主要功能:
1) 后台下载策略文件,依据策略文件判断是否需要升级,采用差分还是整包级;
2) 断点续传下载需要的系统升级包,并进行checksum校验确保其下载的正确性;
3) 文件校验功能,对下载的升级包进行校验,确保下载文件的正确性。
4) Recovery升级功能,需要recovery升级时,写入recovery升级标识。
5) 写入cache/recovery/command命令,便于下次重启时升级;
5.1 Android系统启动后可能进入的模式有以下几种
1) MAGIC KEY(组合键):即用户在启动后通过按下组合键,进入不同的工作模式,具体有两种:
Vol+ + Power :若用户在启动刚开始按了Vol+ + Power组合键,系统会直接进入Recovery模式。以这种方式进入Recovery模式时系统会进入一个简单的UI(使用了minui)界面,用来提示用户进一步操作。有如下几种操作选项:
“reboot system now”
“apply update from sdcard”
“wipe update from cache”
“wipe cache”
“wipe data”
2) 正常启动:若启动过程中用户没有按下任何组合键,bootloader会读取位于MISC分区的启动控制信息块BCB(Bootloader Control Block)。它是一个结构体,存放着启动命令command。根据不同的命令,系统又 可以进入三种不同的启动模式。这个结构体的定义。
struct bootloader_message{
char command[32]; //存放不同的启动命令
char status[32]; //update-radio或update-hboot完成存放执行结果
char recovery[1024]; //存放/cache/recovery/command中的命令
};
command可能的值有两种, command==”boot-recovery”时,系统会进入Recovery模式。Recovery服务会具体根据/cache/recovery/command中的命令执行相应的操作(例如,升级升级或擦除cache,data等)。command为空时,即没有任何命令,系统会进入正常的启动,最后进入主系统(main system)。这种是最通常的启动流程。
Android系统不同的启动模式的进入是在不同的情形下触发的,从SD卡中升级升级时会进入Recovery模式是其中一种,其他的比如:系统崩溃,或则在命令行输入启动命令式也会进入Recovery或其他的启动模式。
升级包来源有两种,一个是OTA在线下载,一个是手动拷贝到SD卡或者U盘中。不论是哪种方式获得升级包,在进入Recovery模式前,都未对这个zip包做处理。只是在重启之前将zip包的路径告诉了Recovery服务下面具体分析这种升级方式下, 升级是怎样从上层一步步进入到Recovery模式的。
6.1、从updatesystem应用到Reboot
当我们依次选择工具箱–>支持–>系统升级–>在线升级后会弹出一个对话框,提示已有升级包是否现在更新
1) 如果用户选择立即更新,后台下载升级包升级,将升级包保存到/cache目录下。
2) 升级文件下载完成后,在Android下对升级包进行sha1正确行校验。校验通过后,调用了pm.reboot(“recovery”)函数,这个函数只将“recovery”参数传递过去了,之后将“boot-recovery”写入到了MISC分区的BCB数据块的command域中。这样在重启之后Bootloader才知道要进入Recovery模式。
至此,Main System就开始重启并进入Recovery模式。在这之前Main System做的最实质的就是两件事,一是将“boot-recovery”写入BCB的command域,二是将–update_package=/cache/升级”写入/cache/recovery/command文件中。下面的部分就开始重启并进入Recovery服务了。
6.2从reboot到Recovery服务
从Bootloader开始如果没有组合键按下,就从MISC分区读取BCB块的command域(在主系统时已经将“boot-recovery”写入)。然后就以Recovery模式开始启动。与正常启动不同的是Recovery模式下加载的镜像是recovery.img。这个镜像同boot.img类似,也包含了标准的内核和根文件系统。其后就与正常的启动系统类似,也是启动内核,然后启动文件系统。在进入文件系统后会执行/init,init的配置文件就是/init.rc。这个配置文件来自bootable/recovery/etc/init.rc。这个文件做了如下几件事情。
1)设置环境变量。
2)建立etc连接。
3)新建目录,备用。
4)挂载/tmp为内存文件系统tmpfs
5)启动recovery(/sbin/recovery)服务。
6)启动adbd服务(用于调试)。
7.1 Recovery服务的通用流程
从recovery.c的main函数开始:
1) ui_init():Recovery服务使用了一个基于framebuffer的简单ui(miniui)系统。这个函数对其进行了简单的初始化。在Recovery服务的过程中主要用于显示一个背景图片和一个进度条。另外还启动了两个线程,一个用于处理进度条的显示,另一个用于响应用户的按键。
2) get_arg():这个函数主要做了上图中get_arg()往右往下直到parse arg/v的工作。
get_bootloader_message():主要工作是根据分区的文件格式类型(mtd或emmc)从MISC分区中读取BCB数据块到一个临时的变量中。然后开始判断Recovery服务是否有带命令行的参数(/sbin/recovery,根据现有的逻辑是没有的),若没有就从BCB中读取recovery域。如果读取失败则从/cache/recovery/command中读取然后。这样这个BCB的临时变量中的recovery域就被更新了。在将这个BCB的临时变量写回真实的BCB之前,又更新的这个BCB临时变量的command域为“boot-recovery”。这样做的目的是如果在升级失败(比如升级还未结束就断电了)时,系统在重启之后还会进入Recovery模式,直到升级完成。在这个BCB临时变量的各个域都更新完成后使用set_bootloader_message()写回到真正的BCB块中。
3) parserargc/argv:解析我们获得参数。注册所解析的命令(,在下面的操作中会根据这一步解析的值进行一步步的判断,然后进行相应的操作。
4) if(update_package):判断update_package是否有值,若有就表示需要升级更新包,此时就会调用install_package()。在这一步中将要完成安装实际的升级包。这是最为复杂,也是升级升级包最为核心的部分。
5) if(wipe_data/wipe_cache):这一步判断实际是两步,在源码中是先判断是否擦除data分区(用户数据部分)的,然后再判断是否擦除cache分区。
6) maybe_install_firmware_update():如果升级包中包含/radio/hboot firmware的更新,则会调用这个函数。
7) prompt_and_wait():这个函数是在一个判断中被调用的。其意义是如果安装失败,则等待用户的输入处理。
8) finish_recovery():这是Recovery关闭并进入Main System的必经之路。其大体流程如下:
将intent(字符串)的内容作为参数传进finish_recovery中。如果有intent需要告知Main System,则将其写入/cache/recovery/intent中。将内存文件系统中的Recovery服务的日志(/tmp/recovery.log)拷贝到cache(/cache/recovery/log)分区中,以便告知重启后的Main System发生过什么。擦除MISC分区中的BCB数据块的内容,以便系统重启后不在进入Recovery模式而是进入更新后的主系统。删除/cache/recovery/command文件。这一步也是很重要的,因为重启后Bootloader会自动检索这个文件,如果未删除的话又会进入Recovery模式。原理在上面已经讲的很清楚了。
9) reboot():这是一个系统调用。在这一步Recovery完成其服务重启并进入Main System。这次重启和在主系统中重启进入Recovery模式调用的函数是一样的,但是其方向是不一样的。所以参数也就不一样。
install_package()是升级升级特有的一部分,也是最核心的部分。在这一步才真正开始对升级包进行处理。下面就开始分析这一部分。如图(8.1)所示:
下面顺着上面的流程图和源码来分析这一流程:
1)ensure_path_mount():先判断所传的升级包路径所在的分区是否已经挂载。如果没有则先挂载。
2)load_keys():加载公钥源文件,路径位于/res/keys。这个文件在Recovery镜像的根文件系统中。
3)verify_file():对升级包升级包进行签名验证。
4)mzOpenZipArchive():打开升级包,并将相关的信息拷贝到一个临时的ZipArchinve变量中。这一步并未对我们的升级包解压。
5)try_update_binary():在这个函数中才是对我们的升级升级的地方。这个函数一开始先根据我们上一步获得的zip包信息,以及升级包的绝对路径将update_binary文件拷贝到内存文件系统的/tmp/update_binary中。以便后面使用。
6)pipe():创建管道,用于下面的子进程和父进程之间的通信。
7)fork():创建子进程。其中的子进程主要负责执行binary(execv(binary,args),即执行我们的安装命令脚本),父进程负责接受子进程发送的命令去更新ui显示(显示当前的进度)。子父进程间通信依靠管道。
8)其中,在创建子进程后,父进程有两个作用。一是通过管道接受子进程发送的命令来更新UI显示。二是等待子进程退出并返回INSTALL SUCCESS。其中子进程在解析执行安装脚本的同时所发送的命令有以下几种:
execv(binary,args)的作用就是去执行binary程序,这个程序的实质就是去解析升级包中的updater-script脚本中的命令并执行。由此,Recovery服务就进入了实际安装升级包的过程。
子进程所执行的程序binary为升级包中的update-binary。Recovery服务在做这一部分工作的时候是先将包中update-binary拷贝到内存文件系统中的/tmp/update_binary,然后再执行的。
分析下这个程序的执行过程:
1)函数参数以及版本的检查。
2)获取管道并打开:在执行此程序的过程中向该管道写入命令,用于通知其父进程根据命令去更新UI显示。
3)读取updater-script脚本:从升级包中将updater-script脚本读到一块动态内存中,供后面执行。
4)Configure edify’s functions:注册脚本中的语句处理函数,即识别脚本中命令的函数。主要有以下几类
RegisterBuiltins():注册程序中控制流程的语句,如ifelse、assert、abort、stdout等。
RegisterInstallFunctions():实际安装过程中安装所需的功能函数,比如mount、format、set_progress、set_perm等。
RegisterDeviceExtensions():与设备相关的额外添加項,在源码中并没有任何实现。
FinishRegistration():结束注册。
5)Parsethe script:调用yy*库函数解析脚本,并将解析后的内容存放到一个Expr类型的python类中。主要函数是yy_scan_string()和yyparse()。
6)执行脚本:核心函数是Evaluate(),它会调用其他的callback函数,而这些callback函数又会去调用Evaluate去解析不同的脚本片段,从而实现一个简单的脚本解释器。
7)错误信息提示:最后就是根据Evaluate()执行后的返回值,给出一些打印信息。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。