一、Android Recovery升级流程
Android系统正常启动时,BootLoader会去读取Misc分区,判断是否有boot-recovery等字段,如果有则设置bootargs为recovery系统的启动参数,反之则设置为kernel启动参数。
const char *boot_select(void)
{
int ret;
int count = 0;
unsigned int len = 0;
unsigned char *buf;
unsigned char *buf1;
int keycode = -1;
HI_HANDLE flashhandle;
HI_Flash_InterInfo_S pFlashInfo;
unsigned int pagesize;
unsigned int erasesize;
flashhandle = HI_Flash_OpenByName(FASTBOOT_MISC_NAME);
if ((0 == flashhandle) || (HI_INVALID_HANDLE == flashhandle)) {
printf("HI_Flash_Open partion %s error\n", FASTBOOT_MISC_NAME);
} else {
ret = HI_Flash_GetInfo(flashhandle, &pFlashInfo);
if (ret) {
printf("HI_Flash_GetInfo %s error\n",
FASTBOOT_MISC_NAME);
}
switch (pFlashInfo.FlashType) {
//省略若干case...
case HI_FLASH_TYPE_EMMC_0:
len = 6 * SZ_1KB;
buf =
(unsigned char *)malloc(len *
sizeof(unsigned char));
if (!buf) {
printf("malloc buf failed\n");
HI_Flash_Close(flashhandle);
break;
}
ret =
HI_Flash_Read(flashhandle, FASTBOOT_MISC_OFFSET,
buf, len, HI_FLASH_RW_FLAG_RAW); //读取Misc分区
if (ret == HI_FAILURE) {
//省略异常处理
break;
}
int len1 = MISC_EMMC_COMMAND_PAGE * SZ_1KB;
int len2 = MISC_EMMC_WRITE_LABEL_COMMAND_PAGE * SZ_1KB;
if (0 == (memcmp(&buf[len1], "boot-recovery", 13))) {
free(buf);
buf = NULL;
HI_Flash_Close(flashhandle);
return "recovery"; //如果Misc分区内容是boot-recovery则进入recovery系统
} else {
free(buf);
buf = NULL;
HI_Flash_Close(flashhandle);
break;
}
default:
HI_Flash_Close(flashhandle);
break;
}
}
//省略若干其他case
return "kernel";
}
上面的代码是某芯片厂家recovey的玩法,各个厂家处理基本类似,主要实现两个功能1. 读取Misc分区,解析字串,判断是否为boot-recovery串。2. 如果是recovery模式根据加载地址,将recovery分区read出来,并且写入到loader地址,设置bootm环境。
经过上面的处理,那么我们的BootLoader加载了recovery系统,那么recovery系统里面又是如何操作的呢?
1. recovery流程
图1. recovery升级流程图
recovery升级流程如图1.所示,接下来将按照流程图示分析recovery流程流程。
(1). 解析command & BCB参数
int
main(int argc, char **argv) {
time_t start = time(NULL);
redirect_stdio(TEMPORARY_LOG_FILE); /输出重定向,可以更改重定向文件描述符,从而定向到文件或者串口输出.
load_volume_table(); //读取/etc/recovery.fstab文件,加载fstab
//...省略若干代码
get_args(&argc, &argv); //解析Command & BCB参数
//根据解析出来的arg参数,设置标志
const char *send_intent = NULL;
const char *update_package = NULL;
int wipe_data = 0, wipe_cache = 0, show_text = 0;
bool just_exit = false;
bool shutdown_after = false;
int arg;
while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
switch (arg) {
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 't': show_text = 1; break;
case 'x': just_exit = true; break;
case 'l': locale = optarg; break;
case 'g': {
if (stage == NULL || *stage == '\0') {
char buffer[20] = "1/";
strncat(buffer, optarg, sizeof(buffer)-3);
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();
}
通过get_args接口读取Command以及BCB信息,并且保存BCB信息,代码如下:
static void
get_args(int *argc, char ***argv) {
struct bootloader_message boot;
memset(&boot, 0, sizeof(boot));
get_bootloader_message(&boot); //读取BCB信息
stage = strndup(boot.stage, sizeof(boot.stage));
// --- if arguments weren't supplied, look in the bootloader control block
//省略若干代码...
//读取Command文件
if (*argc <= 1) {
FILE *fp = fopen_path(COMMAND_FILE, "r");
if (fp != NULL) {
char *token;
char *argv0 = (*argv)[0];
*argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
(*argv)[0] = argv0; // use the same program name
char buf[MAX_ARG_LENGTH];
for (*argc = 1; *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);
}
}
//将解析出来的BCB信息重新写入Misc分区
strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
int i;
for (i = 1; i < *argc; ++i) {
strlcat(boot.recovery, (*argv)[i], sizeof(boot.recovery));
strlcat(boot.recovery, "\n", sizeof(boot.recovery));
}
set_bootloader_message(&boot);
}
至此我们的BCB信息已经读取并且重写完毕,重写BCB主要是预防异常情况下,recovery升级失败后,BootLoader能够重新进入recovery进行升级。例如断电,emmc写入失败等场景。
回到Recovery.main,我们解析完BCB信息,recovery系统将进行Device初始化,主要是用于展示升级界面背景图以及当出现升级异常情况下展示人机交互界面。
(2) Device初始化 & 背景展示
//创建DefaultDevice实例,并且在构造函数中创建ScreenRecoveryUI实例
Device* device = make_device();
ui = device->GetUI(); //获取ScreenRecoveryUI实例
gCurrentUI = ui;
ui->Init(); //ScreenRecoveryUI初始化
ui->SetLocale(locale); //设置语言,从command文件解析出来或者读取上一次last_locale文件解析语言识别码
//...省略若干代码
device->StartRecovery();//开始recovery,DefaultDevice继承于Device,此接口为空实现的虚函数.
在这里我们创建了DefaultDevice设备对象实例以及ScreenRecoveryUI实例对象的创建,那么我们来看看ScreenRecoveryUI类的init接口都做了哪些操作呢?
void ScreenRecoveryUI::Init()
{
//framebuffer初始化,打开framebuffer,读取版本信息等,mmap内存映射,判断framebuffer长度
//是否支持双Buffer形式,如果支持双Buffer则将buffer[1]传递给用户层面操作,
//设置Buffer[0]用于与底层更新操作.
gr_init();
gr_font_size(&char_width, &char_height);
text_col = text_row = 0;
text_rows = gr_fb_height() / char_height;
if (text_rows > kMaxRows) text_rows = kMaxRows;
text_top = 1;
text_cols = gr_fb_width() / char_width;
if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1;
//加载资源文件
backgroundIcon[NONE] = NULL;
LoadBitmapArray("icon_installing", &installing_frames, &installation);
backgroundIcon[INSTALLING_UPDATE] = installing_frames ? installation[0] : NULL;
backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE];
LoadBitmap("icon_error", &backgroundIcon[ERROR]);
backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR];
LoadBitmap("progress_empty", &progressBarEmpty);
LoadBitmap("progress_fill", &progressBarFill);
LoadBitmap("stage_empty", &stageMarkerEmpty);
LoadBitmap("stage_fill", &stageMarkerFill);
LoadLocalizedBitmap("installing_text", &backgroundText[INSTALLING_UPDATE]);
LoadLocalizedBitmap("erasing_text", &backgroundText[ERASING]);
LoadLocalizedBitmap("no_command_text", &backgroundText[NO_COMMAND]);
LoadLocalizedBitmap("error_text", &backgroundText[ERROR]);
//创建进度更新线程
pthread_create(&progress_t, NULL, progress_thread, NULL);
//调用父类Init接口,初始化input监听
RecoveryUI::Init();
}
ScreenRecoveryUI::Init接口主要做了四件事情1. 打开framebuffer并且初始化 2.加载资源文件 3. 创建进度条线程 4. 调用RecoveryUI::Init()初始化input监听。
回到Recovery.main代码流程,我们分析完Device设备创建流程以及UI初始化后,接下来就进入到升级流程里面最关键的步骤,升级包升级。
(3). recovery参数处理
if (update_package != NULL) {
//安装升级包入口,详细分析
status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE, true);
if (status == INSTALL_SUCCESS && wipe_cache) {
if (erase_volume("/cache")) {
LOGE("Cache wipe (requested by package) failed.");
}
}
if (status != INSTALL_SUCCESS) {
ui->Print("Installation aborted.\n");
//升级失败,根据版本状态判断是否展示提示语;
char buffer[PROPERTY_VALUE_MAX+1];
property_get("ro.build.fingerprint", buffer, "");
if (strstr(buffer, ":userdebug/") || strstr(buffer, ":eng/")) {
ui->ShowText(true);
}
}
} else if (wipe_data) { //如果解析ARGS不进行升级,只是恢复出厂设置则清理/data,/cache分区
if (device->WipeData()) status = INSTALL_ERROR;
if (erase_volume("/data")) status = INSTALL_ERROR;
if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
if (erase_persistent_partition() == -1 ) status = INSTALL_ERROR;
if (status != INSTALL_SUCCESS) ui->Print("Data wipe failed.\n");
} else if (wipe_cache) {//清理cache分区
if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
if (status != INSTALL_SUCCESS) ui->Print("Cache wipe failed.\n");
} else if (!just_exit) {
status = INSTALL_NONE; // No command specified
ui->SetBackground(RecoveryUI::NO_COMMAND);
}
上面的代码就是我们解析Args后,根据args来处理recovery请求,从代码上面可以看到当update_package!=NULL时也就意味着我们是需要进行升级操作的,接下来分析升级过程具体包含哪些操作?
(4). 升级流程
install_package接口代码如下:
int
install_package(const char* path, int* wipe_cache, const char* install_file,
bool needs_mount)
{
FILE* install_log = fopen_path(install_file, "w");//日志记录
if (install_log) {
fputs(path, install_log);
fputc('\n', install_log);
} else {
LOGE("failed to open last_install: %s\n", strerror(errno));
}
int result;
if (setup_install_mounts() != 0) { //确定cache分区是否挂载?
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) {
fputc(result == INSTALL_SUCCESS ? '1' : '0', install_log);
fputc('\n', install_log);
fclose(install_log);
}
return result;
}
install_packeage主要是实现两个功能1. 确定cache分区是否已经挂载 2. 安装升级包。接下来继续看升级包是如何被安装的?
really_install_package代码如下:
static int
really_install_package(const char *path, int* wipe_cache, bool needs_mount)
{
//省略背景设置,分区挂载
//打开文件, 获取文件起始地址与结束地址,文件长度,使用mmap将升级包文件映射至内存
MemMapping map;
if (sysMapFile(path, &map) != 0) {
LOGE("failed to map file\n");
return INSTALL_CORRUPT;
}
//读取/res/keys文件,解析RSA加密KEY以及KEY数量,如果不存在KEYS则失败返回
int numKeys;
Certificate* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);
if (loadedKeys == NULL) {
LOGE("Failed to load keys\n");
return INSTALL_CORRUPT;
}
//读取升级包,校验footer,校验升级包签名是否与支持的KEYS匹配
int err;
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);//根据ZIP协议将升级包解析成ZIP
if (err != 0) {
LOGE("Can't open %s\n(%s)\n", path, err != -1 ? strerror(err) : "bad");
sysReleaseMap(&map);
return INSTALL_CORRUPT;
}
ui->Print("Installing update...\n");
ui->SetEnableReboot(false);
int result = try_update_binary(path, &zip, wipe_cache);//查找update-binary文件,尝试升级
ui->SetEnableReboot(true);
ui->Print("\n");
sysReleaseMap(&map); //释放资源
return result;
}
really_install_package接口主要做了五件事,1. 读取update.zip文件,获取文件起始,结束地址,通过mmap映射至内存;2.读取/res/keys文件,解析recovery支持的签名KEY;3. 校验升级包foot是否正常,校验签名与keys是否匹配;4. 根据zip协议解析update.zip文件并记录值ZipArchive zip变量 5. 查找update-binary文件,尝试升级。
接下来继续分析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); //查找META-INF/com/google/android/update-binary文件
if (binary_entry == NULL) {
mzCloseZipArchive(zip);
return INSTALL_CORRUPT;
}
const char* binary = "/tmp/update_binary";
unlink(binary);
int fd = creat(binary, 0755);//创建update_binary文件,后续会将update.zip中的update_binary写入到update_binary.
if (fd < 0) {
mzCloseZipArchive(zip);
LOGE("Can't make %s\n", binary);
return INSTALL_ERROR;
}
//将META-INF/com/google/android/update-binary文件读取出来并且写入到/tmp/update_binary文件
bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);
close(fd);
mzCloseZipArchive(zip);
if (!ok) {
LOGE("Can't copy %s\n", ASSUMED_UPDATE_BINARY_NAME);
return INSTALL_ERROR;
}
//创建匿名管道,用于recovery进程与update_binary进程进行通信.
//默认pipefd[0]用于read,pipefd[1]用于write
//recovery进程关闭pipefd[1],只读, update_binary进程向pipefd[1]写入数据,从而实现两个进程间通信.
int pipefd[2];
pipe(pipefd);
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]); //传递recovery进程pipefd,update_binary进程向其写入数据,通过打印的方式写入.
args[2] = temp;
args[3] = (char*)path;//升级包路径
args[4] = NULL;
pid_t pid = fork(); //fork子进程
if (pid == 0) {
umask(022);
close(pipefd[0]);
execv(binary, (char* const*)args); //使用update_binary替换recovery进程空间
fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno));
_exit(-1);
}
close(pipefd[1]);
*wipe_cache = 0;
//使用fdopen打开pepefd[0],读取update_binary进程写入数据.解析update_binary进程写入数据,执行操作
char buffer[1024];
FILE* from_child = fdopen(pipefd[0], "r");
while (fgets(buffer, sizeof(buffer), from_child) != NULL) {
char* command = strtok(buffer, " \n");
if (command == NULL) {
continue;
} else if (strcmp(command, "progress") == 0) {
char* fraction_s = strtok(NULL, " \n");
char* seconds_s = strtok(NULL, " \n");
float fraction = strtof(fraction_s, NULL);
int seconds = strtol(seconds_s, NULL, 10);
ui->ShowProgress(fraction * (1-VERIFICATION_PROGRESS_FRACTION), seconds);
} else if (strcmp(command, "set_progress") == 0) {
char* fraction_s = strtok(NULL, " \n");
float fraction = strtof(fraction_s, NULL);
ui->SetProgress(fraction);
} else if (strcmp(command, "ui_print") == 0) {
char* str = strtok(NULL, "\n");
if (str) {
ui->Print("%s", str);
} else {
ui->Print("\n");
}
fflush(stdout);
} else if (strcmp(command, "wipe_cache") == 0) {
*wipe_cache = 1;
} else if (strcmp(command, "clear_display") == 0) {
ui->SetBackground(RecoveryUI::NONE);
} else if (strcmp(command, "enable_reboot") == 0) {
// 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, 0);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status));
return INSTALL_ERROR;
}
return INSTALL_SUCCESS;
}
至此我们的update.zip升级完成,回到recovery.main,继续分析代码
if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
copy_logs();
ui->SetBackground(RecoveryUI::ERROR);
}
Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;
if (status != INSTALL_SUCCESS || ui->IsTextVisible()) {
Device::BuiltinAction temp = prompt_and_wait(device, status);//弹出提示语,等待用户操作
if (temp != Device::NO_ACTION) after = temp;
}
// Save logs and clean up before rebooting or shutting down.
finish_recovery(send_intent);//保存日志,清理BCB防止多次进入recovery.
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:
ui->Print("Rebooting...\n");
property_set(ANDROID_RB_PROPERTY, "reboot,");
break;
}
sleep(5); // should reboot before this finishes
return EXIT_SUCCESS;
升级包升级完成后,我们根据升级状态,成功或者失败来决定是否进入人机交互界面以及等待用户操作行为,在用户做出对应的选择后,保存日志,清理BCB信息,根据用户行为选择重启方式。
(5). 人机交互界面
通过分析recovery流程我们发现,升级失败后则会进入到人机交互界面中,那么人机交互界面又做了哪些操作呢?
prompt_and_wait 接口代码如下:
static Device::BuiltinAction
prompt_and_wait(Device* device, int status) {
const char* const* headers = prepend_title(device->GetMenuHeaders());//获取人机交互提示语
for (;;) {
//省略若干代码
//展示标题,获取item
int chosen_item = get_menu_selection(headers, device->GetMenuItems(), 0, 0, device);
//根据item转换用户行为,此处item实际是用户选择的标题索引值
Device::BuiltinAction chosen_action = device->InvokeMenuItem(chosen_item);
//执行用户选择
int wipe_cache = 0;
switch (chosen_action) {
//处理各类Action,包括REBOOT,WIPE_DATA等,添加各类Action时需要注意同步添加索引标题.
}
}
}
从上面的代码来看,我们人机交互的实现还是在get_menu_selection接口中,那么我们的get_menu_selection接口做了哪些操作呢?其代码如下
static int
get_menu_selection(const char* const * headers, const char* const * items,
int menu_only, int initial_selection, Device* device) {
// throw away keys pressed previously, so user doesn't
// accidentally trigger menu items.
ui->FlushKeys(); //刷新Key
ui->StartMenu(headers, items, initial_selection); //展示人机交互界面提示语以及各选项标题
int selected = initial_selection;
int chosen_item = -1;
while (chosen_item < 0) { //如果用户按确定键则返回索引值给上一级处理
int key = ui->WaitKey(); //获取键值
int visible = ui->IsTextVisible(); //是否展示人机交互提示语标志
//省略键值异常处理
//
int action = device->HandleMenuKey(key, visible);//转换键值
//处理Action,如果是up,down则更新到对应的提示语索引以及焦点
//如果是kInvokeItem则代表确定操作,返回当前索引值,由上层InvokeMenuItem转换Action,执行用户操作
if (action < 0) {
switch (action) {
case Device::kHighlightUp:
--selected;
selected = ui->SelectMenu(selected);//更新焦点
break;
case Device::kHighlightDown:
++selected;
selected = ui->SelectMenu(selected);
break;
case Device::kInvokeItem:
chosen_item = selected;
break;
case Device::kNoAction:
break;
}
} else if (!menu_only) {
chosen_item = action;
}
}
ui->EndMenu();
return chosen_item;
}
至此我们的人机交互逻辑基本分析完毕,用户成功某一个操作后,根据选择执行对应的Action,退出人机交互界面,保存日志,清理BCB文件,退出recovery。
二、Recovery update-script
Recovery升级中,描述升级行为的是updater-script脚本,解析器为update_binary进程,接下来分析一下update_binary进程都有哪些需要注意的,我们常用的updater-script命令以及如何添加updater-script命令。
(1). 常用update-script命令
1. ui_print(char *str)
功能:打印信息。
参数: str 指针指向要打印的信息地址。
2. show_progress (char *sec, char *total)
功能:显示进度条。
参数:
sec:多少秒更新一次进度条,一般为 1。
total:升级所耗时间(根据升级包大小来确定)。
3. format(char *fs_type, char *partition_type, char *location,char * fs_size, char
*mount_point)
功能:格式化分区。
参数:
fs_type:文件系统类型(“ubifs” or“ext4”,“raw”)。
NAND Flash 器件:裸分区: raw;文件系统分区: ubifs。
eMMC 器件:裸分区:不支持;文件系统分区: ext4。
partition_type:器件类型(“MTD” or“EMMC”)。
location:分区名或者分区对应的设备节点。
NAND Flash 器件,分区名: system。
eMMC 器件分区,对应的设备节点: /dev/block/platform/hi_mci.1/byname/syste。
fs_size: 0 表示擦除整个分区。
mount_point:分区挂载点。
4. package_extract_file(char *package_path, char *destination_path)
功能:从 zip 包中提取单个文件。
参数:
package_path: 解压的文件。
destination_path: 解压到的目标路径。
5. write_raw_image (char *file, char *partition)
功能:将单个文件写入分区。
参数:
file:欲写入的文件。
partition:欲写入的分区。
6. mount(char *fs_type, char *partition_type, char *location, char *mount_point)
功能:挂载特定分区到某目录下
参数:
fs_type:文件系统类型(“ubifs” or “ext4”)。
partition_type:器件类型(“MTD” or “EMMC”)。
location:分区名字或者分区对应的设备节点。
NAND Flash 器件,分区名: system
eMMC 器件分区,对应的设备节点:
/dev/block/platform/hi_mci.1/by-name/system
mount_point:挂载点。
7. unmount(char *mount_point)
功能:卸载分区。
参数:
mount_point:分区挂载点。
8. package_extract_dir(char *package_path, char *destination_path)
功能:直接提取一文件夹并直接解压到相应目录。
参数:
package_path: zip 压缩包里面要提取的文件夹名。
destination_path:解压到的目录。
9. symlink(char *name, char *argv[])
功能:将 argv* 指向的内容全部链接到 name 文件。
参数:
name:想要链接到的文件名。
argv:想要链接的文件。
10. set_perm_recursive (int uid, int gid, int dir_mode, int file_mode,char *path)
功能:修改目录权限及目录内文件的权限。
参数:
uid:用户 id。
gid:组 id。
dir_mode:目录权限。
file_mode:目录内文件权限。
path:目录路径。
11. partchange(char *partition_type, char *new_partition)
功能:依据传入的分区信息,在内核中建立新的分区
参数:
partition_type:器件类型(“MTD” or “EMMC”)
new_partiton:分区信息
EMMC 器件:关键字是: dev/block/mmcblk0
MTD 器件:关键字是: hinand
partchange 函数不支持 spi 器件,支持 Nand Flash,eMMC 器件
12. setmisc(char *partition_type, char *location)
功能:写 misc 标记位
partition_type:器件类型(“MTD” or“EMMC”)
location:分区名字或者分区对应的设备节点。
NAND Flash 器件,分区名: misc
eMMC 器件分区,对应的设备节点:/dev/block/platform/hi_mci.1/by-name/misc
13. delete_recursive<path>
功能:删除文件或者文件夹
参数:
path:删除文件路径
14. delete<file>
功能:删除文件
参数:
file:文件名
上面就是我们常用的updater-script命令啦,接下来再来看看update-binary进程到底是如何工作的?
(2). update-binary
在上一节中我们提到update-binary进程是由recovery进程从update.zip包中提取出来,并且孵化的子进程,那么update-binary进程都做了哪些操作呢?
//…未完待续
三、Recovery升级注意事项
- 升级系统时,尽可能不要升级recovery分区。
- 升级系统时,如果涉及到recovery签名的变更应注意在update.zip升级包中支持多key(/res/keys),避免因为升级过程中断电,上电后无法继续升级问题。
- 关于BCB分区的擦除应放在升级结束后进行操作。