简介

recovery 作为系统的恢复工具,有以下几点功能:

  1. 首先是我们熟悉的恢复工厂设置 –> wipe_data wipe_cache
  2. 刷升级包,可以通过sdcard升级,通常说的卡刷,有些还提供ADB sideload升级;
  3. 可以进行系统的系统的OTA升级,本质上同手动刷包一样;

recovery 与 主系统交互是通过 /cache 目录下的文件:

  1. /cache/recovery/command 作为recovery的输入参数,以行为分割
  2. /cache/recovery/log 收集recovery的日志文件
  3. /cache/recovery/intent 输出后续操作指令

recovery最后是编译成一个可执行的命令,放在recovery文件系统中的/sbin/recovery;所以我们可以在终端中直接运行该命令,具体的参数如下:

1
2
3
4
5
6
7
--send_intent=anystring - 传递给recovery的信息
--adbd -adb sideload升级
--update_package=path - 指定OTA升级包
--wipe_data - 清楚用户数据并重启
--wipe_cache - 清楚缓存并重启
--set_encrypted_filesystem=on|off - 使能或者关闭文件系统加密
--just_exit - 退出并重启

recovery生成

先看一下 build/core/Makefile 的依赖文件:

1
2
3
4
5
6
7
8
9
10
11
12
$(INSTALLED_RECOVERYIMAGE_TARGET): $(MKBOOTFS) $(MKBOOTIMG) $(MINIGZIP) \
$(INSTALLED_RAMDISK_TARGET) \
$(INSTALLED_BOOTIMAGE_TARGET) \
$(INTERNAL_RECOVERYIMAGE_FILES) \
$(recovery_initrc) $(recovery_sepolicy) $(recovery_kernel) \
$(INSTALLED_2NDBOOTLOADER_TARGET) \
$(recovery_build_prop) $(recovery_resource_deps) \
$(recovery_fstab) \
$(recovery_fstab_mtd) \
$(recovery_fstab_emmc) \
$(RECOVERY_INSTALL_OTA_KEYS)
$(call build-recoveryimage-target, $@)

1
2
3
4
5
6
7
8
9
10
1.MKBOOTFS, MINIGZIP, MKBOOTIMG,PC端工具软件
2.INSTALLED_RAMDISK_TARGET,标准根文件系统 ramdisk.img
3.INSTALLED_BOOTIMAGE_TARGET,即boot.img,标准内核及标准根文件系统
4. recovery_binary, Recovery可执行程序,源码位于:bootable/recovery
5. recovery_initrc,recovery模式的init.rc, 位于 bootable/recovery/etc/init.rc
6. recovery_kernel, recovery 模式的kernel, 同标准内核
7. INSTALLED_2NDBOOTLOADER_TARGET,我们不用
8. recovery_build_prop, recovery 模式的build.prop, 同标准模式
9. recovery_resource_deps, recovery 模式使用的res, 位于:recovery/custom/{product_name}/res
10. RECOVERY_INSTALL_OTA_KEYS, ota 密钥
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
recovery
├── charger -> /sbin/healthd
├── data
├── default.prop
├── dev
├── drmboot.ko
├── etc
│   ├── recovery.emmc.fstab
│   └── recovery.fstab
├── file_contexts
├── fstab.rk30board.bootmode.emmc
├── fstab.rk30board.bootmode.unknown
├── init
├── init.bootmode.emmc.rc
├── init.bootmode.unknown.rc
├── init.rc
├── oem
├── proc
├── property_contexts
├── res
│   ├── images
│   │   ├── erasing_text.png
│   │   ├── error_text.png
│   │   ├── font.png
│   │   ├── icon_error.png
│   │   ├── icon_installing.png
│   │   ├── installing_text.png
│   │   ├── no_command_text.png
│   │   ├── progress_empty.png
│   │   ├── progress_fill.png
│   │   ├── stage_empty.png
│   │   └── stage_fill.png
│   └── keys
├── rk30xxnand_ko.ko
├── sbin
│   ├── adbd
│   ├── busybox
│   ├── e2fsck
│   ├── healthd
│   ├── mkdosfs
│   ├── mkfs.f2fs
│   ├── recovery
│   ├── resize2fs
│   ├── sh
│   ├── ueventd -> ../init
│   └── watchdogd -> ../init
├── seapp_contexts
├── selinux_version
├── sepolicy
├── service_contexts
├── sys
├── system
├── tmp
├── ueventd.rc
└── ueventd.rk30board.rc

recovery根文件系统

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

init.rc

和正常启动一样,内核进入文件系统会执行/init, init 的配置文件就是 /init.rc

主要功能为:

  1. 设置环境变量;
  2. 建立 etc 链接;
  3. 挂载文件系统并创建文件夹目录;
  4. 启动 recovery主程序;

执行流程分析

在Android源码环境中,recovery的源码主要在bootable/recovery文件下,另外在device目录下,会根据各个设备定制自己的接口以及UI界面,也就是文章后半部分分析的界面定制的内容;

在bootable/recovery目录下,看Android.mk文件的源文件列表:

1
2
3
4
5
6
7
8
9
10
11
12
LOCAL_SRC_FILES := \
adb_install.cpp \
asn1_decoder.cpp \
bootloader.cpp \
device.cpp \
fuse_sdcard_provider.c \
install.cpp \
recovery.cpp \
roots.cpp \
screen_ui.cpp \
ui.cpp \
verifier.cpp \

主要执行流程

  1. 标准错误输出重定向

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

  2. miniui初始化

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

  3. 解析参数

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

  4. 设备定制文件初始化

    device_recovery_start() 它給设备制造商提供了一个调用机会,可写入设备相关初始化代码。

  5. 根据命令参数,执行命令

    根据用户提供参数,调用各项功能,比如,安装一个升级包,擦除cache分区, 擦除user data分区,install_package比较复杂。

  6. 完成指令后,等待用户后续指令

    如果前面已经做了某项操作并且成功,则进入重启流程。否则,等待用户选择具体操作。而用户可选操作为: reboot, 安装update.zip,除cache分区, 擦除user data分区,如前所述,只有安装package 比较复杂,其它简单。

  7. 结束recovery

    finish_recovery(send_intent); 它的功能如下:

    1. 将前面定义的intent字符串写入(如果有的话):CACHE:recovery/command
    2. 将 /tmp/recovery.log 复制到 "CACHE:recovery/log";
    3. 清空 misc 分区,这样重启就不会进入recovery模式4)删除command 文件:CACHE:recovery/command;
    

核心函数 really_install_package

  1. 更新UI显示

    ui->Print 更新升级时UI界面显示

  2. 确保所有分区正确挂载

    ensure_path_mounted ,主要是cache分区或者SD分区

  3. 读取update.zip文件

  4. load_keys 装载公钥

    verify_file 注释很清楚,就是签名的验证;

  5. 打开升级包

    mzOpenZipArchive,将相关信息存到 ZipArchive 数据结构中,便于后面处理。

  6. 调用try_update_binary继续执行

    如果升级包中包含脚本文件,则解压执行;主要功能都在这个函数中完成,接下去继续分析;

  7. 复制脚本文件

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

  8. 创建新的进程,执行:/tmp/update_binary

    主进程与子进程通过管道进行进程间通信;原进程变成一个服务进程,它提供UI更新服务;
    包括:更新精度条,打印提示信息,清除缓存,清除显示等

  9. 执行update_binary

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