完全升级OTA包的安装流程(上)

来源:互联网 发布:查看 linux path 编辑:程序博客网 时间:2024/05/03 03:25
完全升级OTA包的安装流程(上)
从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

service recovery /sbin/recovery
service adbd /sbin/adbd recovery
    disabled
    socket adbd stream 660 system system

# Always start adbd on userdebug and eng builds
on property:ro.debuggable=1
    write /sys/class/android_usb/android0/enable 1
    start 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 主程序
int
main(int argc, char **argv)
{
     time_t start = time(NULL);
     // If these fail, there's not really anywhere to complain...
     freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL);
     freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL);
     fprintf(stderr, "Starting recovery on %s", ctime(&start));
将标准输出和标准错误输出重定位到"/tmp/recovery.log",如果是eng模式,就可以通过adb pull /tmp/recovery.log, 看到当前的log信息,这为我们提供了有效的调试手段。后面还会看到,recovery模式运行完毕后,会将其拷贝到cache分区,以便后续分析。
     ui_init();
 Recovery 使用了一个简单的基于framebuffer的ui系统,叫miniui,这里,进行了简单的初始化(主要是图形部分以及事件部分),并启动了一个 event 线程用于响应用户按键。
     get_args(&argc, &argv);
从misc 分区以及 CACHE:recovery/command 文件中读入参数,写入到argc, argv ,并且,如果有必要,回写入misc分区。这样,如果recovery没有操作成功(比如,升级还没有结束,就拔电池),系统会一直进入recovery模式。提醒用户继续升级,直到成功。
     int previous_runs = 0;
     const char *send_intent = NULL;
     const char *update_package = NULL;
     int wipe_data = 0, wipe_cache = 0;
     int arg;
     while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
         switch (arg) {
         case 'p': previous_runs = atoi(optarg); break;
         case 's': send_intent = optarg; break;
         case 'u': update_package = optarg; break;
         case 'w': wipe_data = wipe_cache = 1; break;
         case 'c': wipe_cache = 1; break;
         case '?':
             LOGE("Invalid command argument/n");
             continue;
         }
     }
     device_recovery_start();
这个函数没干什么。看名字,它給设备制造商提供了一个调用机会,可写入设备相关初始化代码。
     fprintf(stderr, "Command:");
     for (arg = 0; arg < argc; arg++) {
         fprintf(stderr, " /"%s/"", argv[arg]);
     }
     fprintf(stderr, "/n/n");
打印出命令,比如,正常启动进入recovery模式,会打印:Command: "/sbin/recovery"
     property_list(print_property, NULL);
     fprintf(stderr, "/n");
打印出所有的系统属性(from default.prop)到log文件。
     int status = INSTALL_SUCCESS;
     if (update_package != NULL) {
status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE);
         if (status != INSTALL_SUCCESS) ui_print("Installation aborted./n");
     } else if (wipe_data) {
         if (device_wipe_data()) status = INSTALL_ERROR;
         if (erase_root("DATA:")) status = INSTALL_ERROR;
         if (wipe_cache && erase_root("CACHE:")) status = INSTALL_ERROR;
         if (status != INSTALL_SUCCESS) ui_print("Data wipe failed./n");
     } else if (wipe_cache) {
         if (wipe_cache && erase_root("CACHE:")) status = INSTALL_ERROR;
         if (status != INSTALL_SUCCESS) ui_print("Cache wipe failed./n");
     } else {
         status = INSTALL_ERROR;  // No command specified
     }
根据用户提供参数,调用各项功能,比如,安装一个升级包,擦除cache分区, 擦除user data分区,install_package比较复杂,后面专门分析,其它都很简单。忽略。
 if (status != INSTALL_SUCCESS) ui_set_background(BACKGROUND_ICON_ERROR);

 if (status != INSTALL_SUCCESS) prompt_and_wait();
如果前面已经做了某项操作并且成功,则进入重启流程。否则,等待用户选择具体操作。
而用户可选操作为: reboot, 安装update.zip,除cache分区, 擦除user data分区,如前所述,只有安装package 比较复杂,其它简单。
     // Otherwise, get ready to boot the main system...
     finish_recovery(send_intent);
它的功能如下:
1)将前面定义的intent字符串写入(如果有的话):CACHE:recovery/command
2)将 /tmp/recovery.log 复制到 "CACHE:recovery/log";
3)清空 misc 分区,这样重启就不会进入recovery模式
4)删除command 文件:CACHE:recovery/command;
     ui_print("Rebooting.../n");
     sync();
     reboot(RB_AUTOBOOT);
     return EXIT_SUCCESS;
}
重启。

下面我们分析核心函数 install_package
int
install_package(const char* path, int* wipe_cache, const char* install_file)
{
FILE* install_log = fopen_path(install_file, "w");
//保存fota升级的log信息
    if (install_log) {
        fputs(path, install_log);
        fputc('\n', install_log);
    } else {
        LOGE("failed to open last_install: %s\n", strerror(errno));
}
int result = really_install_package(path, wipe_cache);
    if (install_log) {
        fputc(result == INSTALL_SUCCESS ? '1' : '0', install_log);
        fputc('\n', install_log);
        fclose(install_log);
    }
return result;
  }
该函数主要调用了really_install_package
static int
really_install_package(const char *path, int* wipe_cache)
{
   ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
   //设置升级过程中背景
   ui->SetProgressType(RecoveryUI::INDETERMINATE);
   //设置升级的进度
   Certificate* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);
从/res/keys中装载公钥。
  err = verify_file(path, loadedKeys, numKeys);
根据公钥验证升级包verify_file的注释说的很明白:
     ZipArchive zip;
     err = mzOpenZipArchive(path, &zip);
打开升级包,将相关信息存到ZuoArchive数据机构中,便于后面处理。
int result = try_update_binary(path, zip);
它主要调用函数try_update_binary完成功能。
     mzCloseZipArchive(&zip);
关闭zip包,结束处理。     

try_update_binary

static int
try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) {

  const ZipEntry* binary_entry =
            mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);
    const char* binary = "/tmp/update_binary";
    unlink(binary);
    int fd = creat(binary, 0755);
    bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);
    close(fd);
    mzCloseZipArchive(zip);
将升级包内文件META-INF/com/google/android/update-binary 复制为/tmp/update_binary
    int pipefd[2];
    pipe(pipefd);
注意看这段注释,它解释了以下代码的行为。结合代码,可知:
1)  将会创建新的进程,执行:/tmp/update_binary
2)  同时,会给该进程传入一些参数,其中最重要的就是一个管道fd,供新进程与原进程通信。
3)  新进程诞生后,原进程就变成了一个服务进程,它提供若干UI更新服务:
a)   progress
b)   set_progress
c)   ui_print
这样,新进程就可以通过老进程的UI系统完成显示任务。而其他功能就靠它自己了。
const char** args = (const char**)malloc(sizeof(char*) * 5);
    args[0] = binary;
    args[1] = EXPAND(RECOVERY_API_VERSION);   // defined in Android.mk
    char* temp = (char*)malloc(10);
    sprintf(temp, "%d", pipefd[1]);
    args[2] = temp;
    args[3] = (char*)path;
args[4] = NULL;

pid_t pid = fork();
    if (pid == 0) {
        close(pipefd[0]);
        execv(binary, (char* const*)args);
        fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno));
        _exit(-1);
    }
    close(pipefd[1]);

1,通过execv(binary, (char* const*)args);调用META-INF/com/google/android路径下的可执行文件:update-binary;
2,由update-binary对META-INF/com/google/android路径下的的脚本文件(updater-script)进行解析。

0 0