全部博文(478)
分类: Android平台
2016-04-19 13:41:30
作者: 宋立新
Email:
从bootloader 进入Recovery 模式后,首先也是运行Linux内核,该内核跟普通模式没有区别(减轻了BSP开发者的任务)。区别从执行文件系统开始。 Recovery 模式的细节就隐藏在其根文件系统中。
下面,我们就看看进入Recovery 根文件系统都干些啥。
和正常启动一样,内核进入文件系统会执行/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
*/
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 使用了一个简单的基于framebuffer的ui系统,叫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
// over
// set_progress commands to manually control the
// progress of this segment of the bar
//
// set_progress
//
// progress bar within the segment defined by the most
// recent progress command.
//
// firmware <"hboot"|"radio">
// arrange to install the contents of
// given partition on reboot.
//
// (API v2:
// indicate taking a file from the OTA package.)
//
// (API v3: this command no longer exists.)
//
// ui_print
// display
//
// - 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,下回继续研究。