Android8.0.0-r4的OTA升级--差分包升级

一、系统更新update.zip包的两种方式

       1. 制作一个update.zip升级包用于升级系统。Android在升级系统时获得update.zip包的方式有两种。一种是离线升级,即手动拷贝升级包到SD卡(或NAND)中,通过settings-->Aboutphone-->SystemUpdate-->选择从SD卡升级。另一种是在线升级,即OTA Install(over the  air)。用户通过在线下载升级包到本地,然后更新。这种方式下的update.zip包一般被下载到系统的/CACHE分区下。

       2.  无论将升级包放在什么位置,在使用update.zip更新时都会重启并进入Recovery模式,然后启动recovery服务(/sbin/recovery)来安装update.zip包。

二、Android系统中三种启动模式

    Android系统启动后可能会进入的几种工作模式:


     由上图可知Android系统启动后可能进入的模式有以下几种:
      (一) MAGIC KEY(组合键):
             即用户在启动后通过按下组合键,进入不同的工作模式,具体有两种:            
        1、camera + power:若用户在启动刚开始按了camera+power组合键则会进入bootloader模式,并可进一步进入fastboot(快速刷机模式)。
          2、home +power :若用户在启动刚开始按了home+power组合键,系统会直接进入Recovery模式。以这种方式进入Recovery模式时系统会进入一个简单的UI(使用了minui)界面,用来提示用户进一步操作。在开发板中提供了一下几种选项操作:

                              “reboot system now”
                              “apply update from sdcard”
                              “wipe data/factory reset”
                              “wipe cache partition”


      (二) 正常启动:
      若启动过程中用户没有按下任何组合键,bootloader会读取位于MISC分区的启动控制信息块BCB(BootloaderControl Block)。它是一个结构体,存放着启动命令command。根据不同的命令,系统又可以进入三种不同的启动模式。我们先看一下这个结构体的定义。
/bootable/recovery/bootloader_message/include/bootloader_message/bootloader_message.h
                                    struct bootloader_message {
                                                char command[32];//存放不同的启动命令
                                                char status[32];//update-radio或update-hboot完成存放执行结果
                                                char recovery[768]; //存放/cache/recovery/command中的命令
                                                char stage[32];
                                                char reserved[1184];

                                     };

        “恢复”字段以前是1024字节。它只用于存储恢复命令行,因此768字节应该足够。我们保留256个字节来存储阶段字符串(用于多级包)和将来可能的扩展。整个bootloader_message结构等于2048字节

        command可能的值有两种,与值为空(即没有命令)一起区分三种启动模式
        1. command=="boot-recovery"时,系统会进入Recovery模式。Recovery服务会具体根据/cache/recovery/command中的命令执行相应的操作(例如,升级update.zip或擦除cache,data等)。
        2. command=="update-radia"或"update-hboot"时,系统会进入更新firmware(更新bootloader),具体由bootloader完成。
        3. command为空时,即没有任何命令,系统会进入正常的启动,最后进入主系统(mainsystem)。这种是最通常的启动流程。

        Android系统不同的启动模式的进入是在不同的情形下触发的,从SD卡中升级我们的update.zip时会进入Recovery模式是其中一种,其他的比如:系统崩溃,或则在命令行输入启动命令式也会进入Recovery或其他的启动模式

三、Recovery模式

3.1 Recovery模式中的三个部分

        Recovery的工作需要整个软件平台的配合,从通信架构上来看,主要有三个部分。
       ①MainSystem:即上面提到的正常启动模式(BCB中无命令),是用boot.img启动的系统,Android的正常工作模式。更新时,在这种模式中我们的上层操作就是使用OTA或则从SD卡中升级update.zip包。在重启进入Recovery模式之前,会向BCB中写入命令,以便在重启后告诉bootloader进入Recovery模式。
       ②Recovery:系统进入Recovery模式后会装载Recovery分区,该分区包含recovery.img(同boot.img相同,包含了标准的内核和根文件系统)。进入该模式后主要是运行Recovery服务(/sbin/recovery)来做相应的操作(重启、升级update.zip、擦除cache分区等)。
       ③Bootloader:除了正常的加载启动系统之外,还会通过读取MISC分区(BCB)获得来至Main system和Recovery的消息。
3.2 Recovery模式中的两个通信接口
       在Recovery服务中上述的三个实体之间的通信是必不可少的,他们相互之间又有以下两个通信接口。
      (一)通过CACHE分区中的三个文件
            Recovery通过/cache/recovery/目录下的三个文件与mian system通信。具体如下            
           ①/cache/recovery/command:这个文件保存着Main system传给Recovery的命令行,每一行就是一条命令,支持一下几种的组合。
               --send_intent=anystring  //write the text out to recovery/intent     在Recovery结束时在finish_recovery函数中将定义的intent字符串作为参数传进来,并写入到/cache/recovery/intent中
               --update_package=root:path  //verify install an OTA package file     Main system将这条命令写入时,代表系统需要升级,在进入Recovery模式后,将该文件中的命令读取并写入BCB中,然后进行相应的更新update.zip包的操作。
               --wipe_data    //erase userdata(and cache),then reboot。擦除用户数据。擦除data分区时必须要擦除cache分区。
               --wipe_cache   //wipe cache(butnot user data),then reboot。擦除cache分区。
           ②/cache/recovery/log:Recovery模式在工作中的log打印。在recovery服务运行过程中,stdout以及stderr会重定位到/tmp/recovery.log在recovery退出之前会将其转存到/cache/recovery/log中,供查看。
           ③/cache/recovery/intent:Recovery传递给Main system的信息。作用不详。
    (二)通过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
                             <recovery command>\n
                             <recovery command>”

           该文件存储的就是一个字符串,必须以recovery\n开头,否则这个字段的所有内容域会被忽略。“recovery\n”之后的部分,是/cache/recovery/command支持的命令。可以将其理解为Recovery操作过程中对命令操作的备份。Recovery对其操作的过程为:先读取BCB然后读取/cache/recovery/command,然后将二者重新写回BCB,这样在进入Main system之前,确保操作被执行。在操作之后进入Main system之前,Recovery又会清空BCB的command域和recovery域,这样确保重启后不再进入Recovery模式。

3.3 如何从Main System重启并进入Recovery模式

      以上三个部分是怎样进行通信的:

                     

            从Main System如何进入Recovery模式。先从Main System开始看,当在Main System使用update.zip包进行升级时,系统会重启并进入Recovery模式。在系统重启之前,Main System定会向BCB中的command域写入boot-recovery(粉红色线),用来告知Bootloader重启后进入recovery模式。重启进入Recovery模式后Bootloader会从/cache/recovery/command中读取值并放入到BCB的recovery域。而Main System在重启之前肯定会向/cache/recovery/command中写入Recovery将要进行的操作命令。

      update.zip包来源有两种,一个是OTA在线下载(一般下载到/CACHE分区),一个是手动拷贝到SD卡中。不论是哪种方式获得update.zip包,在进入Recovery模式前,都未对这个zip包做处理。只是在重启之前将zip包的路径告诉了Recovery服务(通过将--update_package=CACHE:some_filename.zip或--update_package=SDCARD:update.zip命令写入到/cache/recovery/command中)。假设update.zip包已经制作好并拷贝到了SD卡中,并以Settings-->AboutPhone-->SystemUpdate-->Installed From SDCARD方式升级。

四、OTA升级流程

4.1 从System Update到Reboot
       当依次选择Settings-->About Phone-->SystemUpdate-->Installed From SDCARD后会弹出一个对话框,提示已有update.zip包是否现在更新,对话框的源码是SystemUpdateInstallDialog.java。
       ①在mNowButton按钮的监听事件里,会调用mService.rebootAndUpdate(new  File(mFile))。这个mService就是SystemUpdateService的实例。这  个类所在的源码文件是SystemUpdateService.java。这个函数的参数是一个文件。就是update.zip包。
       ②mFile的值:在SystemUpdateInstallDialog.java中的ServiceConnection中mFile的值有两个来源。
                来源一:
                 mFile的一个来源是这个是否立即更新提示框接受的上一个Activity以“file”标记传来的值。这个Activity就是SystemUpdate.java。它是一个PreferenceActivity类型的。在其onPreferenceChange函数中定义了向下一个Activity传送的值,这个值是根据不同的选择而定的。如果在之前选择了从SD卡安装,则这个传下去的“file”值为“/sdcard/update.zip”。如果选择了从NAND安装,则对应的值为“/nand/update.zip”。
                来源二:
                另个一来源是从mService.getInstallFile()获得。进一步跟踪就可发现上面这个函数获得的值就是“/cache”+ mUpdateFileURL.getFile();这就是OTA在线下载后对应的文件路径。不论参数mFile的来源如何,可以发现在mNowButton按钮的监听事件里是将整个文件,也就是我们的update.zip包作为参数往rebootAndUpdate()中传递的。
      ③rebootAndUpdate:在这个函数中Main System做了重启前的准备。继续跟踪下去会发现,在SystemUpdateService.java中的rebootAndUpdate函数中新建了一个线程,在这个线程中最后调用的就是RecoverySystem.installPackage(mContext,mFile),update.zip包也被传递进来了。
 ④RecoverySystem类:RecoverySystem类的源码所在文件路径为:gingerbread0919/frameworks/base/core/java/android/os/RecoverySystem.java。我们关心的是installPackage(Context context,FilepackageFile)函数。这个函数首先根据我们传过来的包文件,获取这个包文件的绝对路径filename。然后将其拼成arg=“--update_package=”+filename。它最终会被写入到BCB中。这个就是重启进入Recovery模式后,Recovery服务要进行的操作。它被传递到函数bootCommand(context,arg)。
    ⑤bootCommand():在这个函数中才是Main System在重启前真正做的准备。主要做了以下事情,首先创建/cache/recovery/目录,删除这个目录下的command和log(可能不存在)文件在sqlite数据库中的备份。然后将上面④步中的arg命令写入到/cache/recovery/command文件中。下一步就是真正重启了。接下来看一下在重启函数reboot中所做的事情。
    ⑥pm.reboot():重启之前先获得了PowerManager(电源管理)并进一步获得其系统服务。然后调用了pm.reboot(“recovery”)函数。他就是/bionic/libc/bionic/reboot.cpp中的reboot函数。这个函数实际上是一个系统调用,即__reboot(LINUX_REBOOT_MAGIC1,LINUX_REBOOT_MAGIC2,mode,NULL);从这个函数我们可以看出前两个参数就代表了我们的组合键,mode就是我们传过来的“recovery”。再进一步跟踪就到了汇编代码了,我们无法直接查看它的具体实现细节。但可以肯定的是这个函数只将“recovery”参数传递过去了,之后将“boot-recovery”写入到了MISC分区的BCB数据块的command域中。这样在重启之后Bootloader才知道要进入Recovery模式。
        在这里我们无法肯定Main System在重启之前对BCB的recovery域是否进行了操作。其实在重启前是否更新BCB的recovery域是不重要的,因为进入Recovery服务后,Recovery会自动去/cache/recovery/command中读取要进行的操作然后写入到BCB的recovery域中。

        至此,Main System就开始重启并进入Recovery模式。在这之前Main System做的最实质的就是两件事,

                     一 、是将“boot-recovery”写入BCB的command域,
        二、是将--update_package=/cache/update.zip”或则“--update_package=/sdcard/update.zip”写入/cache/recovery/command文件中。

4.2 从reboot到Recovery服务
      从Bootloader开始如果没有组合键按下,就从MISC分区读取BCB块的command域(在主系统时已经将“boot-recovery”写入)。然后就以Recovery模式开始启动。与正常启动不同的是Recovery模式下加载的镜像是recovery.img。这个镜像同boot.img类似,也包含了标准的内核和根文件系统。其后就与正常的启动系统类似,也是启动内核,然后启动文件系统。在进入文件系统后会执行/init,init的配置文件就是/init.rc。这个配置文件来自/bootable/recovery/etc/init.rc。这个文件做的事情很简单:
      ①设置环境变量。
      ②建立etc连接。
      ③新建目录,备用。
      ④挂载/tmp为内存文件系统tmpfs
      ⑤启动recovery(/sbin/recovery)服务。
      ⑥启动adbd服务(用于调试)。
      这里最重要的就是当然就recovery服务了。在Recovery服务中将要完成升级工作。
4.3 Recovery服务流程细节

         Recovery服务毫无疑问是Recovery启动模式中最核心的部分。它完成Recovery模式所有的工作。Recovery程序对应的源码文件位于:/bootable/recovery/recovery.cpp。

一、 Recovery的三类服务:

Recovery的服务内容主要有三类:
        FACTORY RESET,恢复出厂设置。
        OTA INSTALL,即update.zip包升级。
        ENCRYPTED FILE SYSTEM ENABLE/DISABLE,使能/关闭加密文件系统。

二、Recovery服务的通用流程:

     以OTAINSTALL的流程为例具体分析。并从相关函数的调用过程图:

         

顺着流程图分析,从recovery.cpp的main函数开始:

         1.   ui_init():Recovery服务使用了一个基于framebuffer的简单ui(miniui)系统。这个函数对其进行了简单的初始化。在Recovery服务的过程中主要用于显示一个背景图片(正在安装或安装失败)和一个进度条(用于显示进度)。另外还启动了两个线程,一个用于处理进度条的显示(progress_thread),另一个用于响应用户的按键(input_thread)。

         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:解析获得参数。注册所解析的命令(register_update_command

         4.   if(update_package)判断update_package是否有值,若有就表示需要升级更新包,此时就会调用install_package()(即图中红色的第二个阶段)。在这一步中将要完成安装实际的升级包。这是最为复杂,也是升级update.zip包最为核心的部分。

         5.   if(wipe_data/wipe_cache):这一步判断实际是两步,在源码中是先判断是否擦除data分区(用户数据部分)的,然后再判断是否擦除cache分区。值得注意的是在擦除data分区的时候必须连带擦除cache分区。在只擦除cache分区的情形下可以不擦除data分区。

         6.   maybe_install_firmware_update():如果升级包中包含/radio/hboot firmware的更新,则会调用这个函数。

                ①先向BCB中写入“boot-recovery”和“—wipe_cache”之后将cache分区格式化,然后将firmware image 写入原始的cache分区中。
                ②将命令“update-radio/hboot”和“—wipe_cache”写入BCB中,然后开始重新安装firmware并刷新firmware。
                ③之后又会进入图示中的末尾,即finish_recovery()。

         7.   prompt_and_wait():这个函数是在一个判断中被调用的。其意义是如果安装失败(update.zip包错误或验证签名失败),则等待用户的输入处理(如通过组合键reboot等)。

         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模式调用的函数是一样的,但是其方向是不一样的。所以参数也就不一样。查看源码发现,其重启模式是RB_AUTOBOOT。这是一个系统的宏。

三、 Recovery服务的核心install_package(升级update.zip特有)

             和Recovery服务中的wipe_data、wipe_cache不同,install_package()是升级update.zip特有的一部分,也是最核心的部分。

                        

           这一部分的源码文件位于:/bootable/recovery/install.cpp

            流程:

           ensure_path_mount():先判断所传的update.zip包路径所在的分区是否已经挂载。如果没有则先挂载。

           load_keys():加载公钥源文件,路径位于/res/keys。这个文件在Recovery镜像的根文件系统中。

           verify_file():对升级包update.zip包进行签名验证。

           mzOpenZipArchive():打开升级包,并将相关的信息拷贝到一个临时的ZipArchinve变量

           try_update_binary():在这个函数中才是对update.zip升级的地方。这个函数一开始先根据上一步获得的zip包信息,以及升级包的绝对路径将update_binary文件拷贝到内存文件系统的/tmp/update_binary中。以便后面使用。

           pipe():创建管道,用于下面的子进程和父进程之间的通信。

           fork():创建子进程。其中的子进程主要负责执行binary(execv(binary,args),即执行安装命令脚本),父进程负责接受子进程发送的命令去更新ui显示(显示当前的进度)。子父进程间通信依靠管道。

           其中,在创建子进程后,父进程有两个作用。一是通过管道接受子进程发送的命令来更新UI显示。二是等待子进程退出并返回INSTALL SUCCESS。其中子进程在解析执行安装脚本的同时所发送的命令有以下几种:

                    progress  <frac> <secs>:根据第二个参数secs(秒)来设置进度条。
                    set_progress  <frac>:直接设置进度条,frac取值在0.0到0.1之间。
                    firmware<”hboot”|”radio”><filename>:升级firmware时使用,在API  V3中不再使用。
                    ui_print <string>:在屏幕上显示字符串,即打印更新过程。

               execv(binary,args)的作用就是去执行binary程序,这个程序的实质就是去解析update.zip包中的updater-script脚本中的命令并执行。由此,Recovery服务就进入了实际安装update.zip包的过程。

四、update_binary的执行过程

      Recovery服务在做这一部分工作的时候是先将包中update-binary拷贝到内存文件系统中的/tmp/update_binary,然后再执行的。update_binary程序的源码位于/bootable/recovery/updater/updater.cpp:
       执行过程:
       ①函数参数以及版本的检查:当前updater binary API所支持的版本号有1,2,3这三个。
       ②获取管道并打开:在执行此程序的过程中向该管道写入命令,用于通知其父进程根据命令去更新UI显示。
       ③读取updater-script脚本:从update.zip包中将updater-script脚本读到一块动态内存中,供后面执行。
       ④Configure edify’s functions:注册脚本中的语句处理函数,即识别脚本中命令的函数。主要有以下几类
                 RegisterBuiltins():注册程序中控制流程的语句,如ifelse、assert、abort、stdout等。
               RegisterInstallFunctions():实际安装过程中安装所需的功能函数,比如mount、format、set_progress、set_perm等等。
                RegisterDeviceExtensions():与设备相关的额外添加項,在源码中并没有任何实现。
                FinishRegistration():结束注册
     ⑤Parsethe script:调用yy*库函数解析脚本,并将解析后的内容存放到一个Expr类型的python类中。主要函数是yy_scan_string()和yyparse()。
       ⑥执行脚本:核心函数是Evaluate(),它会调用其他的callback函数,而这些callback函数又会去调用Evaluate去解析不同的脚本片段,从而实现一个简单的脚本解释器。
       ⑦错误信息提示:最后就是根据Evaluate()执行后的返回值,给出一些打印信息。

           这一执行过程非常简单,最主要的函数就是Evaluate。它负责最终执行解析的脚本命令。而安装过程中的命令就是updater-script。

五、update-script脚本

一、update-script脚本语法简介:

       1.assert(condition):如果condition参数的计算结果为False,则停止脚本执行,否则继续执行脚本。
       2.show_progress(frac,sec):frac表示进度完成的数值,sec表示整个过程的总秒数。主要用与显示UI上的进度条。
     3.format(fs_type,partition_type,location):fs_type,文件系统类型,取值一般为“yaffs2”或“ext4”。Partition_type,分区类型,一般取值为“MTD”或则“EMMC”。主要用于格式化为指定的文件系统。事例如下:format(”yaffs2”,”MTD”,”system”)。
       4.mount(fs_type,partition_type,location,mount_point):前两个参数同上,location要挂载的设备,mount_point挂载点。作用:挂载一个文件系统到指定的挂载点。
       5.package_extract_dir(src_path,destination_path):src_path,要提取的目录,destination_path目标目录。作用:从升级包内,提取目录到指定的位置。示例:package_extract_dir(“system”,”/system”)。
       6.symlink(target,src1,src2,……,srcN):target,字符串类型,是符号连接的目标。SrcX代表要创建的符号连接的目标点。示例:symlink(“toolbox”,”/system/bin/ps”),建立指向toolbox符号连接/system/bin/ps,值得注意的是,在建立新的符号连接之前,要断开已经存在的符号连接。
       7.set_perm(uid,gid,mode,file1,file2,……,fileN):作用是设置单个文件或则一系列文件的权限,最少要指定一个文件。
       8.set_perm_recursive(uid,gid,mode,dir1,dir2,……,dirN):作用同上,但是这里同时改变的是一个或多个目录及其文件的权限。
       9.package_extract_file(srcfile_path,desfile_paht):srcfile_path,要提取的文件,desfile_path,提取文件的目标位置。示例:package_extract_file(“boot.img”,”/tmp/boot.img”)将升级包中的boot.img文件拷贝到内存文件系统的/tmp下。
      10.write_raw_image(src-image,partition):src-image源镜像文件,partition,目标分区。作用:将镜像写入目标分区。示例:write_raw_image(“/tmp/boot.img”,”boot”)将boot.img镜像写入到系统的boot分区。
    11.getprop(key):通过指定key的值来获取对应的属性信息。示例:getprop(“ro.product.device”)获取ro.product.device的属性值。
二、updater-script脚本执行流程分析:
         make otapackage生成的升级脚本的执行过程:
        ①比较时间戳:如果升级包较旧则终止脚本的执行。
        ②匹配设备信息:如果和当前的设备信息不一致,则停止脚本的执行。
        ③显示进度条:如果以上两步匹配则开始显示升级进度条。
        ④格式化system分区并挂载。
        ⑤提取包中的recovery以及system目录下的内容到系统的/system下。
        ⑥为/system/bin/下的命令文件建立符号连接。
        ⑦设置/system/下目录以及文件的属性。
        ⑧将包中的boot.img提取到/tmp/boot.img。
        ⑨将/tmp/boot.img镜像文件写入到boot分区。
        ⑩完成后卸载/system。
        以上就是updater-script脚本中的语法,及其执行的具体过程。通过分析其执行流程,我们可以发现在执行过程中,并未将升级包另外解压到一个地方,而是需要什么提取什么。值得主要的是在提取recovery和system目录中的内容时,一并放在了/system/下。在操作的过程中,并未删除或改变update.zip包中的任何内容。在实际的更新完成后,我们的update.zip包确实还存在于原来的位置。


猜你喜欢

转载自blog.csdn.net/nwpushuai/article/details/79703043
今日推荐