转载请注明来源:cuixiaolei的技术博客

这篇文章主要通过分析高通recovery目录下的recovery.cpp源码,对recovery启动流程有一个宏观的了解。MTK和高通的recovery几乎一样,只是使用自己家的mt_xxx文件。

为什么要分析recovery.cpp这个文件?

我们知道,当我们通过按键或者应用进入recovery模式,实质是kernel后加载recovery.img,kernel起来后执行的第一个进程就 是init,此进程会读入init.rc启动相应的服务。在recovery模式中,启动的服务是执行recovery可执行文件,此文件是

bootable/recovery/recovery.cpp文件生成,我们就从recovery.cpp文件开始分析。此出可参考我的另一篇文章android-ramdisk.img分析、recovery.img&boot.img执行过程

下面的代码位于bootable/recovery/etc/init.rc,由此可知,进入recovery模式后会执行sbin /recovery,此文件是bootable/recovery/recovery.cpp生成(可查看对应目录的Android.mk查看),所以recovery.cpp是recovery模式的入口。

service recovery /sbin/recovery
seclabel u:r:recovery:s0

开始主题

bootable/recovery/recovery.cpp

int
main(int argc, char **argv) {
time_t start = time(NULL); redirect_stdio(TEMPORARY_LOG_FILE); // If this binary is started with the single argument "--adbd",        如果二进制文件使用单个参数"--adbd"启动
// instead of being the normal recovery binary, it turns into kind     而不是正常的recovery启动(不带参数即为正常启动)
// of a stripped-down version of adbd that only supports the           它变成精简版命令时只支持sideload命令。它必须是一个正确可用的参数
// 'sideload' command. Note this must be a real argument, not         不在/cache/recovery/command中,也不受B2B控制
// anything in the command file or bootloader control block; the      
// only way recovery should be run with this argument is when it       是apply_from_adb()的副本
// starts a copy of itself from the apply_from_adb() function.
if (argc == && strcmp(argv[], "--adbd") == ) {
adb_main(, DEFAULT_ADB_PORT);
return ;
} printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start)); load_volume_table();                //加载并建立分区表
get_args(&argc, &argv);             //从传入的参数或/cache/recovery/command文件中得到相应的命令 const char *send_intent = NULL;
const char *update_package = NULL;
bool should_wipe_data = false;
bool should_wipe_cache = false;
bool show_text = false;
bool sideload = false;
bool sideload_auto_reboot = false;
bool just_exit = false;
bool shutdown_after = false; int arg;
while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -) {         //while循环解析command或者传入的参数,并把对应的功能设置为true或给相应的变量赋值
switch (arg) {
case 'i': send_intent = optarg; break;
case 'u': update_package = optarg; break;
case 'w': should_wipe_data = true; break;
case 'c': should_wipe_cache = true; break;
case 't': show_text = true; break;
case 's': sideload = true; break;
case 'a': sideload = true; sideload_auto_reboot = true; break;
case 'x': just_exit = true; break;
case 'l': locale = optarg; break;
case 'g': {
if (stage == NULL || *stage == '\0') {
char buffer[] = "1/";
strncat(buffer, optarg, sizeof(buffer)-);
stage = strdup(buffer);
}
break;
}
case 'p': shutdown_after = true; break;
case 'r': reason = optarg; break;
case '?':
LOGE("Invalid command argument\n");
continue;
}
} if (locale == NULL) {          //设置语言
load_locale_from_cache();
}
printf("locale is [%s]\n", locale);
printf("stage is [%s]\n", stage);
printf("reason is [%s]\n", reason);

  /*初始化UI*/
Device* device = make_device();
ui = device->GetUI();
gCurrentUI = ui;
show_text = true;
ui->SetLocale(locale);
ui->Init(); int st_cur, st_max;
if (stage != NULL && sscanf(stage, "%d/%d", &st_cur, &st_max) == ) {
ui->SetStage(st_cur, st_max);
} ui->SetBackground(RecoveryUI::NONE);           //设置recovery界面背景
if (show_text) ui->ShowText(true);             //设置界面上是否能够显示字符,使能ui->print函数开关 struct selinux_opt seopts[] = {                //设置selinux权限,以后会有专门的文章或专题讲解selinux,这里不做讲解    
{ SELABEL_OPT_PATH, "/file_contexts" }
}; sehandle = selabel_open(SELABEL_CTX_FILE, seopts, ); if (!sehandle) {
ui->Print("Warning: No file_contexts\n");
} device->StartRecovery();       //此函数为空,没做任何事情 printf("Command:");                      //打印/cache/recovery/command的参数
for (arg = ; arg < argc; arg++) {
printf(" \"%s\"", argv[arg]);
}
printf("\n"); if (update_package) {                          //根据下面的注释可知,对old "root" 路径进行修改,把其放在/cache/文件中 。  当安装包的路径是以CACHE:开头,把其改为/cache/开头                               
// For backwards compatibility on the cache partition only, if
// we're given an old 'root' path "CACHE:foo", change it to
// "/cache/foo".
if (strncmp(update_package, "CACHE:", ) == ) {
int len = strlen(update_package) + ;
char* modified_path = (char*)malloc(len);
strlcpy(modified_path, "/cache/", len);
strlcat(modified_path, update_package+, len);
printf("(replacing path \"%s\" with \"%s\")\n",
update_package, modified_path);
update_package = modified_path;
}
}
printf("\n"); property_list(print_property, NULL);              //打印属性列表,其实现没有找到代码在哪里,找到后会更新此文章
printf("\n"); ui->Print("Supported API: %d\n", RECOVERY_API_VERSION); int status = INSTALL_SUCCESS;    //设置标志位,默认为INSTALL_SUCCESS if (update_package != NULL) {     //install package情况
status = install_package(update_package, &should_wipe_cache, TEMPORARY_INSTALL_FILE, true);     //安装ota升级包
if (status == INSTALL_SUCCESS && should_wipe_cache) {   //如果安装前点击了清楚缓存,执行下面的语句,安装成功后清楚缓存
wipe_cache(false, device);   
}
if (status != INSTALL_SUCCESS) {                  //安装失败,打印log,并根据is_ro_debuggable()决定是否打开ui->print信息(此信息显示在屏幕上)
ui->Print("Installation aborted.\n");
if (is_ro_debuggable()) {
ui->ShowText(true);
}
}
} else if (should_wipe_data) {     //只清除用户数据
if (!wipe_data(false, device)) {
status = INSTALL_ERROR;
}
} else if (should_wipe_cache) {    //只清除缓存
if (!wipe_cache(false, device)) {
status = INSTALL_ERROR;
}
} else if (sideload) {       //执行adb reboot sideload命令后会跑到这个代码段
// 'adb reboot sideload' acts the same as user presses key combinations
// to enter the sideload mode. When 'sideload-auto-reboot' is used, text
// display will NOT be turned on by default. And it will reboot after
// sideload finishes even if there are errors. Unless one turns on the
// text display during the installation. This is to enable automated
// testing.
if (!sideload_auto_reboot) {
ui->ShowText(true);
}
status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE);
if (status == INSTALL_SUCCESS && should_wipe_cache) {
if (!wipe_cache(false, device)) {
status = INSTALL_ERROR;
}
}
ui->Print("\nInstall from ADB complete (status: %d).\n", status);
if (sideload_auto_reboot) {
ui->Print("Rebooting automatically.\n");
}
} else if (!just_exit) {              //当command命令中有just_exit字段
status = INSTALL_NONE; // No command specified
ui->SetBackground(RecoveryUI::NONE); if (is_ro_debuggable()) { ui->ShowText(true);
}
} if (!sideload_auto_reboot && (status == INSTALL_ERROR || status == INSTALL_CORRUPT)) {   //安装失败,复制log信息到/cache/recovery/。如果进行了wipe_data/wipe_cache/apply_from_sdcard(也就是修改了flash),
//直接return结束recovery,否则现实error背景图片
copy_logs();
ui->SetBackground(RecoveryUI::ERROR);
} Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT; 
if ((status != INSTALL_SUCCESS && !sideload_auto_reboot) || ui->IsTextVisible()) {       //status在just_exit中已经变为none,会执行此if语句
#ifdef SUPPORT_UTF8_MULTILINGUAL
ml_select(device);
#endif
Device::BuiltinAction temp = prompt_and_wait(device, status);       //prompt_and_wait()函数是个死循环 开始显示recovery选项 并处理用户通过按键或者触摸屏的选项,如Reboot system等
if (temp != Device::NO_ACTION) {
after = temp;
}
} finish_recovery(send_intent); switch (after) {
case Device::SHUTDOWN:
ui->Print("Shutting down...\n");
property_set(ANDROID_RB_PROPERTY, "shutdown,");
break; case Device::REBOOT_BOOTLOADER:
ui->Print("Rebooting to bootloader...\n");
property_set(ANDROID_RB_PROPERTY, "reboot,bootloader");
break; default:
char reason[PROPERTY_VALUE_MAX];
snprintf(reason, PROPERTY_VALUE_MAX, "reboot,%s", device->GetRebootReason());
ui->Print("Rebooting...\n");
property_set(ANDROID_RB_PROPERTY, reason);
break;
}
sleep();
return EXIT_SUCCESS;
}

上面的代码中已经把recovery启动后的流程描述的差不多了,下面是一点细节性的描述

1.获取command命令

get_args(&argc, &argv);

此函数没有什么可说的,先判断事都有参数传进来,如果有解析传入的命令,否走从/cache/recovery/command文件中解析命令

注意,此函数会先把struct bootloader_message boot写入到misc分区,目的是防止断电等原因导致关机,开机后lk会从misc分区中读取相关信息,如果发现是"boot-recovery"会再次进入recovery模式,misc分区会在退出recovery时被清除,以至于可以正常开机,如果手机每次都是进入recovery而不能正常开机,可以分析是否没有清楚misc分区。

struct bootloader_message {
char command[];
char status[];
char recovery[]; // The 'recovery' field used to be 1024 bytes. It has only ever
// been used to store the recovery command line, so 768 bytes
// should be plenty. We carve off the last 256 bytes to store the
// stage string (for multistage packages) and possible future
// expansion.
char stage[];
char reserved[];
};
// command line args come from, in decreasing precedence:
// - the actual command line
// - the bootloader control block (one per line, after "recovery")
// - the contents of COMMAND_FILE (one per line)
static void
get_args(int *argc, char ***argv) {
struct bootloader_message boot;
memset(&boot, , sizeof(boot));
get_bootloader_message(&boot); // this may fail, leaving a zeroed structure
stage = strndup(boot.stage, sizeof(boot.stage)); if (boot.command[] != && boot.command[] != ) {
LOGI("Boot command: %.*s\n", (int)sizeof(boot.command), boot.command);
} if (boot.status[] != && boot.status[] != ) {
LOGI("Boot status: %.*s\n", (int)sizeof(boot.status), boot.status);
} // --- if arguments weren't supplied, look in the bootloader control block
if (*argc <= ) {
boot.recovery[sizeof(boot.recovery) - ] = '\0'; // Ensure termination
const char *arg = strtok(boot.recovery, "\n");
if (arg != NULL && !strcmp(arg, "recovery")) {
*argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
(*argv)[] = strdup(arg);
for (*argc = ; *argc < MAX_ARGS; ++*argc) {
if ((arg = strtok(NULL, "\n")) == NULL) break;
(*argv)[*argc] = strdup(arg);
}
LOGI("Got arguments from boot message\n");
} else if (boot.recovery[] != && boot.recovery[] != ) {
LOGE("Bad boot message\n\"%.20s\"\n", boot.recovery);
}
} // --- if that doesn't work, try the command file
if (*argc <= ) {
FILE *fp = fopen_path(COMMAND_FILE, "r");
if (fp != NULL) {
char *token;
char *argv0 = (*argv)[];
*argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
(*argv)[] = argv0; // use the same program name char buf[MAX_ARG_LENGTH];
for (*argc = ; *argc < MAX_ARGS; ++*argc) {
if (!fgets(buf, sizeof(buf), fp)) break;
token = strtok(buf, "\r\n");
if (token != NULL) {
(*argv)[*argc] = strdup(token); // Strip newline.
} else {
--*argc;
}
} check_and_fclose(fp, COMMAND_FILE);
LOGI("Got arguments from %s\n", COMMAND_FILE);
}
} // --> write the arguments we have back into the bootloader control block
// always boot into recovery after this (until finish_recovery() is called)
strlcpy(boot.command, "boot-recovery", sizeof(boot.command));  //***************************************************
strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
int i;
for (i = ; i < *argc; ++i) {
strlcat(boot.recovery, (*argv)[i], sizeof(boot.recovery));
strlcat(boot.recovery, "\n", sizeof(boot.recovery));
}
set_bootloader_message(&boot);
}

2.解析command命令

while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {...}

可知从/cache/recovery/command文件中获取并与OPTIONS列表参数进行比较,把相应的字符串赋值或者修改相应的变量

 //while循环解析command或者传入的参数,并把对应的功能设置为true或给相应的变量赋值,下面是command中可能的命令及其value /*
static const struct option OPTIONS[] = {
{ "send_intent", required_argument, NULL, 'i' },
{ "update_package", required_argument, NULL, 'u' },
{ "wipe_data", no_argument, NULL, 'w' },
{ "wipe_cache", no_argument, NULL, 'c' },
{ "show_text", no_argument, NULL, 't' },
{ "sideload", no_argument, NULL, 's' },
{ "sideload_auto_reboot", no_argument, NULL, 'a' },
{ "just_exit", no_argument, NULL, 'x' },
{ "locale", required_argument, NULL, 'l' },
{ "stages", required_argument, NULL, 'g' },
{ "shutdown_after", no_argument, NULL, 'p' },
{ "reason", required_argument, NULL, 'r' },
{ NULL, , NULL, },
};
*/

3.安装升级包

status = install_package(update_package, &should_wipe_cache, TEMPORARY_INSTALL_FILE, true);

此函数安装升级包,update_package是路径,从/cache/recovery/command文件中解析

static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install";  TEMPORARY_INSTALL_FILE存放升级时的log信息,后面会把此文件复制到/cache/recovery/文件中

bootable/recovery/install.cpp

int
install_package(const char* path, bool* wipe_cache, const char* install_file,
bool needs_mount)
{
modified_flash = true;  FILE* install_log = fopen_path(install_file, "w");        //打开log文件
if (install_log) {
fputs(path, install_log);                             //向log文件中写入安装包路径
fputc('\n', install_log);
} else {
LOGE("failed to open last_install: %s\n", strerror(errno));
}
int result;
if (setup_install_mounts() != ) {                       //mount /tmp和/cache ,成功返回0
LOGE("failed to set up expected mounts for install; aborting\n");
result = INSTALL_ERROR;
} else {
result = really_install_package(path, wipe_cache, needs_mount);       //执行安装
}
if (install_log) {             //向log文件写入安装结果,成功写入1,失败写入0
fputc(result == INSTALL_SUCCESS ? '' : '', install_log);
fputc('\n', install_log);
fclose(install_log);
}
return result;
}
int setup_install_mounts() {                         //挂在/cache   /tmp分区
if (fstab == NULL) {
LOGE("can't set up install mounts: no fstab loaded\n");
return -;
}
for (int i = ; i < fstab->num_entries; ++i) {
Volume* v = fstab->recs + i; if (strcmp(v->mount_point, "/tmp") == ||
strcmp(v->mount_point, "/cache") == ) {
if (ensure_path_mounted(v->mount_point) != ) {
LOGE("failed to mount %s\n", v->mount_point);
return -;
} } else {
if (ensure_path_unmounted(v->mount_point) != ) {
LOGE("failed to unmount %s\n", v->mount_point);
return -;
}
}
}
return ;
}
static int
really_install_package(const char *path, bool* wipe_cache, bool needs_mount)
{
ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);                   //设置背景为安装背景,就是小机器人
ui->Print("Finding update package...\n");             
// Give verification half the progress bar...
ui->SetProgressType(RecoveryUI::DETERMINATE);                            //初始化升级时进度条       
ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);        //设置进度条时间
LOGI("Update location: %s\n", path); // Map the update package into memory.
ui->Print("Opening update package...\n"); if (path && needs_mount) {                            //判断升级包所在路径是否被挂在
ensure_path_mounted((path[] == '@') ? path + : path);
} MemMapping map;                                 //把升级包路径映射到内存中
if (sysMapFile(path, &map) != ) {
LOGE("failed to map file\n");
return INSTALL_CORRUPT;
} int numKeys;                                   //加载密钥
Certificate* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);
if (loadedKeys == NULL) {
LOGE("Failed to load keys\n");
return INSTALL_CORRUPT;
}
LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE); ui->Print("Verifying update package...\n"); int err;                                  //校验升级包是否被修改,一般在调试ota升级时会把这段代码进行屏蔽,使本地编译的升级包可以正常升级
err = verify_file(map.addr, map.length, loadedKeys, numKeys);
free(loadedKeys);
LOGI("verify_file returned %d\n", err);
if (err != VERIFY_SUCCESS) {
LOGE("signature verification failed\n");
sysReleaseMap(&map);
return INSTALL_CORRUPT;
} /* Try to open the package.
*/
ZipArchive zip;                 //打开升级包
err = mzOpenZipArchive(map.addr, map.length, &zip);
if (err != ) {
LOGE("Can't open %s\n(%s)\n", path, err != - ? strerror(err) : "bad");
sysReleaseMap(&map);          //这行代码很重要,只有失败时才释放map内存,结束安装。提前释放map内存会导致下面代码无法正常进行,界面上会显示失败。
return INSTALL_CORRUPT;
} /* Verify and install the contents of the package.
*/
ui->Print("Installing update...\n");
ui->SetEnableReboot(false);
int result = try_update_binary(path, &zip, wipe_cache);        //执行安装包内的执行脚本
ui->SetEnableReboot(true);
ui->Print("\n"); sysReleaseMap(&map); #ifdef USE_MDTP
/* If MDTP update failed, return an error such that recovery will not finish. */
if (result == INSTALL_SUCCESS) {
if (!mdtp_update()) {
ui->Print("Unable to verify integrity of /system for MDTP, update aborted.\n");
return INSTALL_ERROR;
}
ui->Print("Successfully verified integrity of /system for MDTP.\n");
}
#endif /* USE_MDTP */ return result;
}

install_package流程:

1).设置ui界面,包括背景和进度条

2).检查是否挂在tmp和cache,tmp存放升级log,cache存放升级包

3).加载密钥并校验升级包,防止升级包被用户自己修改

4).打开升级包,并执行升级包内的安装程序

4.执行升级包中的升级文件

try_update_binary()

try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache) {
const ZipEntry* binary_entry =                                     //在升级包中查找是否存在META-INF/com/google/android/update-binary文件
mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);
if (binary_entry == NULL) {
mzCloseZipArchive(zip);
return INSTALL_CORRUPT;
} const char* binary = "/tmp/update_binary";      //在tmp中创建临时文件夹,权限755
unlink(binary);
int fd = creat(binary, );
if (fd < ) {
mzCloseZipArchive(zip);
LOGE("Can't make %s\n", binary);
return INSTALL_ERROR;
}
bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);     //把update.zip升级包解压到/tmp/update_binary文件夹中
sync();
close(fd);
mzCloseZipArchive(zip); if (!ok) {
LOGE("Can't copy %s\n", ASSUMED_UPDATE_BINARY_NAME);
return INSTALL_ERROR;
} int pipefd[];
pipe(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 <frac> <secs>
// fill up the next <frac> part of of the progress bar
// over <secs> seconds. If <secs> is zero, use
// set_progress commands to manually control the
// progress of this segment of the bar.
//
// set_progress <frac>
// <frac> 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"> <filename>
// arrange to install the contents of <filename> in the
// given partition on reboot.
//
// (API v2: <filename> may start with "PACKAGE:" to
// indicate taking a file from the OTA package.)
//
// (API v3: this command no longer exists.)
//
// ui_print <string>
// display <string> on the screen.
//
// wipe_cache
// a wipe of cache will be performed following a successful
// installation.
//
// clear_display
// turn off the text display.
//
// enable_reboot
// packages can explicitly request that they want the user
// to be able to reboot during installation (useful for
// debugging packages that don't exit).
//
// - the name of the package zip file.
// const char** args = (const char**)malloc(sizeof(char*) * );          //创建指针数组,并分配内存
args[] = binary;                                                     //[0]存放字符串 "/tmp/update_binary" ,也就是升级包解压的目的地址
args[] = EXPAND(RECOVERY_API_VERSION); // defined in Android.mk    //[1]存放RECOVERY_API_VERSION,在Android.mk中定义,我的值为3  RECOVERY_API_VERSION := 3
char* temp = (char*)malloc();
sprintf(temp, "%d", pipefd[]);
args[] = temp;
args[] = (char*)path;                                                //[3]存放update.zip路径
args[] = NULL; pid_t pid = fork();                                                   //创建一个新进程,为子进程
if (pid == ) {       //进程创建成功,执行META-INF/com/google/android/update-binary脚本,给脚本传入参数args
umask();
close(pipefd[]);
execv(binary, (char* const*)args);
fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno));
_exit(-);
}
close(pipefd[]); *wipe_cache = false; char buffer[];
FILE* from_child = fdopen(pipefd[], "r");
while (fgets(buffer, sizeof(buffer), from_child) != NULL) {                    //父进程通过管道pipe读取子进程的值,使用strtok分割函数把子进程传过来的参数进行解析,执行相应的ui修改
char* command = strtok(buffer, " \n"); 
if (command == NULL) {
continue;
} else if (strcmp(command, "progress") == ) {
char* fraction_s = strtok(NULL, " \n");
char* seconds_s = strtok(NULL, " \n"); float fraction = strtof(fraction_s, NULL);
int seconds = strtol(seconds_s, NULL, ); ui->ShowProgress(fraction * (-VERIFICATION_PROGRESS_FRACTION), seconds);
} else if (strcmp(command, "set_progress") == ) {
char* fraction_s = strtok(NULL, " \n");
float fraction = strtof(fraction_s, NULL);
ui->SetProgress(fraction);
} else if (strcmp(command, "ui_print") == ) {
char* str = strtok(NULL, "\n");
if (str) {
ui->Print("%s", str);
} else {
ui->Print("\n");
}
fflush(stdout);
} else if (strcmp(command, "wipe_cache") == ) {
*wipe_cache = true;
} else if (strcmp(command, "clear_display") == ) {
ui->SetBackground(RecoveryUI::NONE);
} else if (strcmp(command, "enable_reboot") == ) {
// packages can explicitly request that they want the user
// to be able to reboot during installation (useful for
// debugging packages that don't exit).
ui->SetEnableReboot(true);
} else {
LOGE("unknown command [%s]\n", command);
}
}
fclose(from_child); int status;
waitpid(pid, &status, );
if (!WIFEXITED(status) || WEXITSTATUS(status) != ) {
LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status));
return INSTALL_ERROR;
} return INSTALL_SUCCESS;
}

try_update_binary流程:

1.查找META-INF/com/google/android/update-binary二进制脚本

2.解压update.zip包到/tmp/update_binary

3.创建子进程,执行update-binary二进制安装脚本,并通过管道与父进程通信,父进程更新ui界面。

到此,android 的 Recovery的流程已经分析完了,知道流程后再去分析Recovery的相关问题就比较容易了。

Recovery启动流程(3)--recovery.cpp分析的更多相关文章

  1. Recovery启动流程--recovery.cpp分析

    这篇文章主要通过分析高通recovery目录下的recovery.cpp源码,对recovery启动流程有一个宏观的了解. 当开机以后,在lk阶段,如果是recovery,会设置boot_into_r ...

  2. Recovery启动流程(2)---UI界面

    转载请注明来源:cuixiaolei的技术博客 Recovery启动流程系列文章把recvoery目录下文件分成小块讲解,最后再以一条主线贯穿所有的内容.这篇文章主要讲解Recovery-UI的相关内 ...

  3. Recovery启动流程(2)---UI界面【转】

    Recovery启动流程系列文章把recvoery目录下文件分成小块讲解,最后再以一条主线贯穿所有的内容.这篇文章主要讲解Recovery-UI的相关内容. 我们知道,当我们通过按键或者应用进入rec ...

  4. Recovery启动流程(1)--- 应用层到开机进入recovery详解

    转载请注明来源:cuixiaolei的技术博客 进入recovery有两种方式,一种是通过组合键进入recovery,另一种是上层应用设置中执行安装/重置/清除缓存等操作进行recovery.这篇文档 ...

  5. Spark启动流程(Standalone)-分析

    1.start-all.sh脚本,实际上执行java -cp Master 和 java -cp Worker 2.Master 启动时首先穿件一个RpcEnv对象,负责管理所有通信逻辑 3.Mast ...

  6. Android系统Recovery工作原理之使用update.zip升级过程分析(六)---Recovery服务流程细节【转】

    本文转载自:http://blog.csdn.net/mu0206mu/article/details/7465439  Android系统Recovery工作原理之使用update.zip升级过程分 ...

  7. 【嵌入式开发】 Bootloader 详解 ( 代码环境 | ARM 启动流程 | uboot 工作流程 | 架构设计)

    作者 : 韩曙亮 博客地址 : http://blog.csdn.net/shulianghan/article/details/42462795 转载请著名出处 相关资源下载 :  -- u-boo ...

  8. elasticsearch indices.recovery 流程分析(索引的_open操作也会触发recovery)——主分片recovery主要是从translog里恢复之前未写完的index,副分片recovery主要是从主分片copy segment和translog来进行恢复

    摘自:https://www.easyice.cn/archives/231 elasticsearch indices.recovery 流程分析与速度优化 目录 [隐藏] 主分片恢复流程 副本分片 ...

  9. lk启动流程详细分析

    转载请注明来源:cuixiaolei的技术博客 这篇文章是lk启动流程分析(以高通为例),将会详细介绍下面的内容: 1).正常开机引导流程 2).recovery引导流程 3).fastboot引导流 ...

随机推荐

  1. tomcat server获取用户的请求地址

    当用户 与 tomcat之间 用 nginx做跳转时, HttpServletRequest 中的 getRemoteHost()方法获取到的只是nginx的地址,而不能拿到用户真正的请求地址 解决方 ...

  2. append some buttons to the standard datagrid pager bar

    <script type="text/javascript">  $(function(){   var pager = $('#dg').datagrid('getP ...

  3. 现代程序设计 homework-07

    现代程序设计 homework-07 这次作业是要阅读C++11的新特性,按照老师blog提供的链接稍微学习了一下,一下就是一些学习总结(或者说就是介绍)之类的:由于英文能力有限,并且很多中文资料也都 ...

  4. POJ 2421 Constructing Roads (最小生成树)

    Constructing Roads 题目链接: http://acm.hust.edu.cn/vjudge/contest/124434#problem/D Description There ar ...

  5. Spring EL hello world example

    The Spring EL is similar with OGNL and JSF EL, and evaluated or executed during the bean creation ti ...

  6. POJ 3312 Mahershalalhashbaz, Nebuchadnezzar, and Billy Bob Benjamin Go to the Regionals (水题,贪心)

    题意:给定 n 个字符串,一个k,让你把它们分成每组k个,要保证每组中每个字符串长度与它们之和相差不能超2. 析:贪心策略就是长度相差最小的放上块. 代码如下: #pragma comment(lin ...

  7. 哥也能写KMP了——实现strstr()

    经过上次去面试,面试官要求实现strstr(),当场就蒙了.这个题目是模式匹配问题,<算法导论>里列出了几种字符串匹配算法: 朴素算法 |  Rabin-Karp | 有限自动机算法 | ...

  8. C#IEnumerator.MoveNext 方法 ()

    将枚举数推进到集合的下一个元素. 命名空间:   System.Collections程序集:  mscorlib(mscorlib.dll 中) 语法: bool MoveNext() 返回值 Ty ...

  9. 字符串匹配 - KMP算法

    #include<cstdio> #include<cstring> #include<cstdlib> void GetNext(char *t,int *nex ...

  10. iOS开发笔记系列-基础5(分类和协议)

    分类 在Objective-C中,除了通过新建子类的方式来向类添加新方法外,还可以通过分类的方式.分类提供了一种简单的方式,将类的定义模块化到相关方法的组或分类中,它还提供了扩展现有类定义的简便方式, ...