Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1292128
  • 博文数量: 478
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 4833
  • 用 户 组: 普通用户
  • 注册时间: 2014-06-28 11:12
文章分类

全部博文(478)

文章存档

2019年(1)

2018年(27)

2017年(21)

2016年(171)

2015年(258)

我的朋友

分类: Android平台

2016-04-19 13:41:30

作者: 宋立新

Email

前言

       bootloader 进入Recovery 模式后,首先也是运行Linux内核,该内核跟普通模式没有区别(减轻了BSP开发者的任务)。区别从执行文件系统开始。 Recovery 模式的细节就隐藏在其根文件系统中。

       下面,我们就看看进入Recovery 根文件系统都干些啥。

 

init.rc

       和正常启动一样,内核进入文件系统会执行/init, init 的配置文件就是 /init.rc, 前面文章讲过,这个文件来自:bootable/recovery/etc/init.rc,下面,我们看看它的内容。

 

   
   on init
       export PATH /sbin
       export ANDROID_ROOT /system
       export ANDROID_DATA /data
       export EXTERNAL_STORAGE /sdcard
   
       symlink /system/etc /etc
   
      mkdir /sdcard
      mkdir /system
      mkdir /data
      mkdir /cache
      mount /tmp /tmp tmpfs
  
  on boot
  
      ifup lo
      hostname localhost
      domainname localdomain
  
      class_start default
  
  
  service recovery /sbin/recovery
  
  service adbd /sbin/adbd recovery
      disabled
  
  on property:persist.service.adb.enable=1
      start adbd
  
  on property:persist.service.adb.enable=0
      stop adbd

 

可以看到,它很非常简单:

1)   设置几个环境变量。备用。

2)   建立 etc 链接。

3)   造几个目录。备用。

4)   Mount /tmp 目录为内存文件系统 tmpfs,后面会用到。

5)   Trival 设置,不必关心。

6)   启动 recovery主程序。

7)   如果是eng模式(此时persist.service.adb.enable),启动adb

当然,init主程序还会装载属性配置文件 /default.prop, 它包含了很多系统属性设置,比如,ro.build.* 等等。

 

很明显,这里最重要的就是recovery主程序,下面,我们分析它。

先看一段注释

Recovery.c 中,作者写了一段注释,对我们理解recovery的实现很有帮助,下面看一下:(我就不翻译了)

 /*
   * The recovery tool communicates with the main system through /cache files.
   *   /cache/recovery/command - INPUT - command line for tool, one arg per line
   *   /cache/recovery/log - OUTPUT - combined log file from recovery run(s)
   *   /cache/recovery/intent - OUTPUT - intent that was passed in
   *
   * The arguments which may be supplied in the recovery.command file:
   *   --send_intent=anystring - write the text out to recovery.intent
   *   --update_package=root:path - verify install an OTA package file
   *   --wipe_data - erase user data (and cache), then reboot
   *   --wipe_cache - wipe cache (but not user data), then reboot
  *   --set_encrypted_filesystem=on|off - enables / diasables encrypted fs
  *
  * After completing, we remove /cache/recovery/command and reboot.
  * Arguments may also be supplied in the bootloader control block (BCB).
  * These important scenarios must be safely restartable at any point:
  *
  * FACTORY RESET
  * 1. user selects "factory reset"
  * 2. main system writes "--wipe_data" to /cache/recovery/command
  * 3. main system reboots into recovery
  * 4. get_args() writes BCB with "boot-recovery" and "--wipe_data"
  *    -- after this, rebooting will restart the erase --
  * 5. erase_root() reformats /data
  * 6. erase_root() reformats /cache
  * 7. finish_recovery() erases BCB
  *    -- after this, rebooting will restart the main system --
  * 8. main() calls reboot() to boot main system
  *
  * OTA INSTALL
  * 1. main system downloads OTA package to /cache/some-filename.zip
  * 2. main system writes "--update_package=CACHE:some-filename.zip"
  * 3. main system reboots into recovery
  * 4. get_args() writes BCB with "boot-recovery" and "--update_package=..."
  *    -- after this, rebooting will attempt to reinstall the update --
  * 5. install_package() attempts to install the update
  *    NOTE: the package install must itself be restartable from any point
  * 6. finish_recovery() erases BCB
  *    -- after this, rebooting will (try to) restart the main system --
  * 7. ** if install failed **
  *    7a. prompt_and_wait() shows an error icon and waits for the user
  *    7b; the user reboots (pulling the battery, etc) into the main system
  * 8. main() calls maybe_install_firmware_update()
  *    ** if the update contained radio/hboot firmware **:
  *    8a. m_i_f_u() writes BCB with "boot-recovery" and "--wipe_cache"
  *        -- after this, rebooting will reformat cache & restart main system --
  *    8b. m_i_f_u() writes firmware image into raw cache partition
  *    8c. m_i_f_u() writes BCB with "update-radio/hboot" and "--wipe_cache"
  *        -- after this, rebooting will attempt to reinstall firmware --
  *    8d. bootloader tries to flash firmware
  *    8e. bootloader writes BCB with "boot-recovery" (keeping "--wipe_cache")
  *        -- after this, rebooting will reformat cache & restart main system --
  *    8f. erase_root() reformats /cache
  *    8g. finish_recovery() erases BCB
  *        -- after this, rebooting will (try to) restart the main system --
  * 9. main() calls reboot() to boot main system
  *
  * ENCRYPTED FILE SYSTEMS ENABLE/DISABLE
  * 1. user selects "enable encrypted file systems"
  * 2. main system writes "--set_encrypted_filesystem=on|off" to
  *    /cache/recovery/command
  * 3. main system reboots into recovery
  * 4. get_args() writes BCB with "boot-recovery" and
  *    "--set_encrypted_filesystems=on|off"
  *    -- after this, rebooting will restart the transition --
  * 5. read_encrypted_fs_info() retrieves encrypted file systems settings from /data
  *    Settings include: property to specify the Encrypted FS istatus and
  *    FS encryption key if enabled (not yet implemented)
  * 6. erase_root() reformats /data
  * 7. erase_root() reformats /cache
  * 8. restore_encrypted_fs_info() writes required encrypted file systems settings to /data
  *    Settings include: property to specify the Encrypted FS status and
  *    FS encryption key if enabled (not yet implemented)
  * 9. finish_recovery() erases BCB
  *    -- after this, rebooting will restart the main system --
  * 10. main() calls reboot() to boot main system
  */

 

recovery 主程序

 int
 (int , char **)
 {
       = ();
 
     // If these fail, there's not really anywhere to complain...
     (, "a", ); (, );
     (, "a", ); (, );
     (, "Starting recovery on %s", (&));
 
 

将标准输出和标准错误输出重定位到"/tmp/recovery.log",如果是eng模式,就可以通过adb pull /tmp/recovery.log, 看到当前的log信息,这为我们提供了有效的调试手段。后面还会看到,recovery模式运行完毕后,会将其拷贝到cache分区,以便后续分析。

 
     ();
 
 Recovery 使用了一个简单的基于framebufferui系统,叫miniui,这里,进行了简单的初始化(主要是图形部分以及事件部分),并启动了一个 event 线程用于响应用户按键。
 
     (&, &);

misc 分区以及 CACHE:recovery/command 文件中读入参数,写入到argc, argv ,并且,如果有必要,回写入misc分区。这样,如果recovery没有操作成功(比如,升级还没有结束,就拔电池),系统会一直进入recovery模式。提醒用户继续升级,直到成功。

 
     int previous_runs = 0;
     const char *send_intent = ;
     const char *update_package = ;
     int  = 0, wipe_cache = 0;
 
     int ;
     while (( = (, , "", , )) != -1) {
         switch () {
         case 'p': previous_runs = (); break;
         case 's': send_intent = ; break;
         case 'u': update_package = ; break;
         case 'w':  = wipe_cache = 1; break;
         case 'c': wipe_cache = 1; break;
         case '?':
             ("Invalid command argument/n");
             continue;
         }
     }
 
解析参数,p: previous_runs没有用到,其它含义见前面注释。
 
     device_recovery_start();
 
这个函数没干什么。看名字,它給设备制造商提供了一个调用机会,可写入设备相关初始化代码。
 
     (, "Command:");
     for ( = 0;  < ; ++) {
         (, " /"%s/"", []);
     }
     (, "/n/n");
 
打印出命令,比如,正常启动进入recovery模式,会打印:Command: "/sbin/recovery"
     (, );
     (, "/n");
 
打印出所有的系统属性(from default.prop)到log文件。
 
     int  = INSTALL_SUCCESS;
 
     if (update_package != ) {
          = (update_package);
         if ( != INSTALL_SUCCESS) ("Installation aborted./n");
     } else if () {
         if (device_wipe_data())  = INSTALL_ERROR;
         if (("DATA:"))  = INSTALL_ERROR;
         if (wipe_cache && ("CACHE:"))  = INSTALL_ERROR;
         if ( != INSTALL_SUCCESS) ("Data wipe failed./n");
     } else if (wipe_cache) {
         if (wipe_cache && ("CACHE:"))  = INSTALL_ERROR;
         if ( != INSTALL_SUCCESS) ("Cache wipe failed./n");
     } else {
          = INSTALL_ERROR;  // No command specified
     }
 
根据用户提供参数,调用各项功能,比如,安装一个升级包,擦除cache分区, 擦除user data分区,比较复杂,后面专门分析,其它都很简单。忽略。
 
 
     if ( != INSTALL_SUCCESS) (BACKGROUND_ICON_ERROR);
     if ( != INSTALL_SUCCESS) ();
 
如果前面已经做了某项操作并且成功,则进入重启流程。否则,等待用户选择具体操作。
而用户可选操作为: reboot, 安装update.zip,除cache分区, 擦除user data分区,如前所述,只有安装package 比较复杂,其它简单。
 
 
     // Otherwise, get ready to boot the main system...
     (send_intent);
 
它的功能如下:
1)将前面定义的intent字符串写入(如果有的话):CACHE:recovery/command
2)将 /tmp/recovery.log 复制到 "CACHE:recovery/log";
3)清空 misc 分区,这样重启就不会进入recovery模式
4)删除command 文件:CACHE:recovery/command;
 
     ("Rebooting.../n");
     ();
     ();
     return ;
 }
 

重启。

下面我们分析核心函数 

 

 int
 (const char *root_path)
 {
     (BACKGROUND_ICON_INSTALLING);
     ui_print("Finding update package.../n");
     ("Finding update package.../n");
     ();
     ("Update location: %s/n", root_path);
 
更新 UI 显示
     if ((root_path) != 0) {
         ("Can't mount %s/n", root_path);
         ();
         return INSTALL_CORRUPT;
     }
 
 
确保升级包所在分区已经mount,通常为 cache 分区或者 SD 分区
 
     char [] = "";
     if ((root_path, , sizeof()) == ) {
         ("Bad path %s/n", root_path);
         ();
         return INSTALL_CORRUPT;
     }
 
将根分区转化为具体分区信息.这些信息来自:全局数组:
 
     ui_print("Opening update package.../n");
     ("Opening update package.../n");
     ("Update file path: %s/n", );
 
     int numKeys;
     RSAPublicKey* loadedKeys = (, &numKeys);
     if (loadedKeys == ) {
         ("Failed to load keys/n");
         ();
         return INSTALL_CORRUPT;
     }
     ("%d key(s) loaded from %s/n", numKeys, );
 
/res/keys中装载公钥。
 
     // Give verification half the progress bar...
     ui_print("Verifying update package.../n");
     ("Verifying update package.../n");
     (
             ,
             );
 
     int ;
      = (, loadedKeys, numKeys);
     (loadedKeys);
     ("verify_file returned %d/n", );
     if ( != ) {
         ("signature verification failed/n");
         ();
         return INSTALL_CORRUPT;
     }
 
根据公钥验证升级包verify_file的注释说的很明白:
       // Look for an RSA signature embedded in the .ZIP file comment given
       // the path to the zip.  Verify it matches one of the given public
       // keys.
 
     /* Try to open the package.
      */
      zip;
      = (, &zip);
     if ( != 0) {
         ("Can't open %s/n(%s)/n", ,  != -1 ? () : "bad");
         ();
         return INSTALL_CORRUPT;
     }
 
打开升级包,将相关信息存到ZuoArchive数据机构中,便于后面处理。
 
     /* Verify and install the contents of the package.
      */
     int  = (, &zip);
 
处理函数,我们后面继续分析。
 
     (&zip);
     return ;
 }
 
关闭zip包,结束处理。

 static int
 (const char *,  *zip)
 {
     // Update should take the rest of the progress bar.
     ("Installing update.../n");
 
     int  = (, zip);
     (, );  // Unregister package root
     return ;
 }
 

它主要调用函数完成功能。

 // If the package contains an update binary, extract it and run it.

  static int

  (const char *,  *zip) {

      const * binary_entry =

              (zip, );

      if (binary_entry == ) {

          return INSTALL_CORRUPT;

      }

 

      char*  = "/tmp/update_binary";

      ();

      int  = (, 0755);

      if ( < 0) {

          ("Can't make %s/n", );

          return 1;

      }

       = (zip, binary_entry, );

     ();

     if (!) {

         ("Can't copy %s/n", );

         return 1;

     }

 
将升级包内文件META-INF/com/google/android/update-binary 复制为/tmp/update_binary

 

     int pipefd[2];

     (pipefd);

     // When executing the update binary contained in the package, the

     // arguments passed are:

     //

     //   - the version number for this interface

     //

     //   - an fd to which the program can write in order to update the

     //     progress bar.  The program can write single-line commands:

     //

     //        progress

     //            fill up the next part of of the progress bar

     //            over seconds.  If is zero, use

     //            set_progress commands to manually control the

     //            progress of this segment of the bar

     //

     //        set_progress

     //             should be between 0.0 and 1.0; sets the

     //            progress bar within the segment defined by the most

     //            recent progress command.

     //

     //        firmware <"hboot"|"radio">

     //            arrange to install the contents of in the

     //            given partition on reboot.

     //

     //            (API v2: may start with "PACKAGE:" to

     //            indicate taking a file from the OTA package.)

     //

     //            (API v3: this command no longer exists.)

     //

     //        ui_print

     //            display on the screen.

     //

     //   - the name of the package zip file.

     //

 

注意看这段注释,它解释了以下代码的行为。结合代码,可知:

1)  将会创建新的进程,执行:/tmp/update_binary

2)  同时,会给该进程传入一些参数,其中最重要的就是一个管道fd,供新进程与原进程通信。

3)  新进程诞生后,原进程就变成了一个服务进程,它提供若干UI更新服务:

a)   progress

b)   set_progress

c)   ui_print

这样,新进程就可以通过老进程的UI系统完成显示任务。而其他功能就靠它自己了。

 

     char**  = (sizeof(char*) * 5);

     [0] = ;

     [1] = (RECOVERY_API_VERSION);   // defined in Android.mk

     [2] = (10);

     ([2], "%d", pipefd[1]);

     [3] = (char*);

     [4] = ;

       = ();

     if ( == 0) {

         (pipefd[0]);

         (, );

         (, "E:Can't run %s (%s)/n", , ());

         (-1);

     }

     (pipefd[1]);

     char [1024];

     * from_child = (pipefd[0], "r");

     while ((, sizeof(), from_child) != ) {

         char*  = (, " /n");

         if ( == ) {

             continue;

         } else if ((, "progress") == 0) {

             char* fraction_s = (, " /n");

             char* seconds_s = (, " /n");

             float fraction = (fraction_s, );

             int  = (seconds_s, , 10);

             (fraction * (1-),

                              );

         } else if ((, "set_progress") == 0) {

             char* fraction_s = (, " /n");

             float fraction = (fraction_s, );

             (fraction);

         } else if ((, "ui_print") == 0) {

             char*  = (, "/n");

             if () {

                 ();

             } else {

                 ("/n");

             }

         } else {

             ("unknown command [%s]/n", );

         }

     }

     (from_child);

     int ;

     (, &, 0);

     if (!() || () != 0) {

         ("Error in %s/n(Status %d)/n", , ());

         return INSTALL_ERROR;

     }

     return INSTALL_SUCCESS;

 }

 

这样,我们又回到了升级包中的文件:META-INF/com/google/android/update-binary,下回继续研究。

阅读(2005) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~