Android上层进入recovery流程
2017-12-09 11:47
127 查看
recovery介绍
recovery是android的一种特殊模式,我称之为还原升级模式。这里从recovery的功能说起:1.擦除用户数据
设置菜单中的恢复到出厂模式,即擦除用户数据
2.系统升级
设置菜单中的系统升级功能;OTA INSTALL,即使用update.zip包升级
recovery的详细功能在源码 bootable/recovery/recovery.c 文件的注释中有详细介绍
uboot、main system、recovery
将android系统分成三个部分:1、Main system:用boot.img启动的Linux系统,Android的正常工作模式;
2、Recovery:用recovery.img启动的Linux系统,recovery模式;
3、uboot:引导程序,通常情况下是引导kernel uImage,挂载根文件系统,启动android镜像;也可以引导recovery.img,进入recovery模 式
这三个部分通信的两中方式:cache分区、BCB(bootloader control block)数据块
1.cache分区: main system –> recovery
cache分区主要是main system 与recovery通信,main system通过cache向recovery发送消息,控制recovery功能,如:升级或擦除数据。
通信主要通过cache下的三个文件
1)/cache/recovery/command
Main system传给Recovery的命令行,控制recovery的功能 ,如:升级或擦除数据。具体有以下几种:
–send_intent=anystring write the text out to recovery/intent
–update_package=root:path 系统升级,path表示升级包的路径
–wipe_data erase user data (and cache), then reboot
–wipe_cache wipe cache (but not user data), then reboot
2)/cache/recovery/log
存放 recvoery 过程的log。在recovery运行过程中,stdout及stderr会重定位到/tmp/recovery.log文件,Recovery退出之前会将其转储 到/cache/recovery/log中。
3)/cache/recovery/intent
Recovery传给Main system的信息
2.BCB(bootloader control block)数据块
struct bootloader_message { char command[32]; char status[32]; char recovery[1024]; };
BCB是Bootloader与Recovery的通信接口,也是Bootloader与Main system的通信接口,存储在flash中的MISC分区,占用三个page,各成员意义如下:
command:
当Main system想要重启进入recovery模式,或升级radio/bootloader firmware时,会更新这个域。
当firmware更新完毕,为了启动后进入recovery做最终的清除,bootloader还会修改它。
下面是command域的三种情况:
(1). command = “boot-recovery” 时,系统会进入Recovery模式。Recovery服务会具体根据/cache/recovery/command中的命令执行相应的操作
(例如,升级update.zip或擦除cache,data等)。
(2). command = “update-radia” 或 “update-hboot” 时,系统会进入更新firmware(更新bootloader),具体由bootloader完成。
(3). command为空时,即没有任何命令,系统会进入正常的启动,最后进入主系统(main system)。这种是最通常的启动流程。
status:
update-radio或update-hboot完成后,bootloader会写入相应的信息,一
4000
般是一些状态或执行结果。
recovery:
当Main system想要重启进入recovery模式,可能会更新这个域,注意:不是一定更新。必须以“recovery\n”开头,否则这个域的所有内容会被忽略。这一项的内容中“recovery/\n”以后的部分,是/cache/recovery/command支持的命令,可以认为这是在Recovery操作过程中,对命令操作的备份。
Recovery也会更新这个域的信息,执行某操作前把该操作命令写到recovery域,并更新command域,防止recovery过程被中断后系统不能正常启动。操作完成后再清空recovery域及command域,这样在进入Main system之前,就能确保操作被执行。
从android上层进入recvoery具体流程:
android部分:
当我们在设置中点击系统升级时,系统会起一个reboot线程,在线程中根据不同的功能调用RecoverySystem类的方法,目标文件:frameworks/base/services/java/com/android/server/MasterClearReceiver.java;
注意:这里可能不同平台有所不同,但大体思想一致
public void onReceive(final Context context, final Intent intent) { ............ Slog.w(TAG, "!!! FACTORY RESET !!!"); // The reboot call is blocking, so we need to do it on another thread. Thread thr = new Thread("Reboot") { @Override public void run() { try { if("from-sd".equals(intent.getStringExtra("update-type"))) { RecoverySystem.rebootUpdateFromSD(context); /*从sd卡升级*/ } else if("from-udisk".equals(intent.getStringExtra("update-type"))) { RecoverySystem.rebootUpdateFromUDisk(context); /*从u盘升级*/ } else RecoverySystem.rebootWipeUserData(context); /*擦除数据*/ } catch (IOException e) { Slog.e(TAG, "Can't perform master clear/factory reset", e); } } }; thr.start(); }
RecoverySystem类定义于文件:
frameworks/base/core/java/android/os/RecoverySystem.java
public class RecoverySystem { /*这里创建了cache/recovery 下的文件,用于向recovery传递消息*/ private static File RECOVERY_DIR = new File("/cache/recovery"); private static File COMMAND_FILE = new File(RECOVERY_DIR, "command"); private static File LOG_FILE = new File(RECOVERY_DIR, "log"); private static String LAST_PREFIX = "last_"; /*最新的log 以last_开头*/ public static void rebootWipeUserData(Context context) throws IOException { final ConditionVariable condition = new ConditionVariable(); Intent intent = new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION"); context.sendOrderedBroadcastAsUser(intent, UserHandle.OWNER, android.Manifest.permission.MASTER_CLEAR, new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { condition.open(); } }, null, 0, null, null); condition.block(); /*擦除用户分区 走这里*/ bootCommand(context, "--wipe_data\n--locale=" + Locale.getDefault().toString()); } public static void rebootUpdateFromSD(Context context) throws IOException { ............... /*从sd卡升级 走这里* bootCommand(context, "--update_package=/ext_sdcard1/update.zip\n--locale=" + Locale.getDefault().toString()); } public static void rebootUpdateFromUDisk(Context context) throws IOException { .......... /*从u盘升级 走这里*/ bootCommand(context, "--update_package=/udisk1/update.zip\n--locale=" + Locale.getDefault().toString()); } private static void bootCommand(Context context, String arg) throws IOException { RECOVERY_DIR.mkdirs(); // In case we need it COMMAND_FILE.delete(); // In case it's not writable LOG_FILE.delete(); FileWriter command = new FileWriter(COMMAND_FILE); try { command.write(arg); /*到这里可以发现 无论要实现是什么功能,都是往cache/recvoery/command文件中写命令 */ command.write("\n"); } finally { command.close(); } // Having written the command file, go ahead and reboot PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); pm.reboot("recovery"); /*最后调用pm.reboot重启并进入recovery*/ throw new IOException("Reboot failed (no permissions?)"); }
注意:这里容易混淆,一个是进入以后干什么,一个是如何进入
1.调用bootCommand往往cache/recvoery/command文件中写命令是为了进入recovery后,由recovery解析命令,然后执行命令;
2.pm.reboot(“recovery”); 以及下面的所有操作都是为了重启系统,并进入recvoery模式;
PowerManager类定义于文件:
frameworks/base/core/java/android/os/PowerManager.java
public class PowerManager { ... IPowerManager mService; Handler mHandler; public PowerManager(IPowerManager service, Handler handler) { mService = service; mHandler = handler; } ... public void reboot(String reason) { try { mService.reboot(reason); /*reason 是recvoery*/ } catch (RemoteException e) { } } }
mService指向的是PowerManagerService类,这个类定义于文件:
frameworks/base/services/java/com/android/server/power/PowerManagerService.java
public void reboot(boolean confirm, String reason, boolean wait) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null); final long ident = Binder.clearCallingIdentity(); try { shutdownOrRebootInternal(false, confirm, reason, wait); /*走这里*/ } finally { Binder.restoreCallingIdentity(ident); } } shutdownOrRebootInternal(false, confirm, reason, wait); 参数为(false,false,"recovery",true) private void shutdownOrRebootInternal(final boolean shutdown, final boolean confirm, ... Runnable runnable = new Runnable() { @Override public void run() { synchronized (this) { if (shutdown) { ShutdownThread.shutdown(mContext, confirm); } else { /*shutdown应该为false 所以走这里, reason为recovery*/ ShutdownThread.reboot(mContext, reason, confirm); } } } };
ShutdownThread类在文件:
frameworks/base/services/java/com/android/server/power/ShutdownThread.java
public static void reboot(final Context context, String reason, boolean confirm) { //方法中的变量为全局变量 mReboot = true; mRebootSafeMode = false; mRebootUpdate = false; mRebootReason = reason; shutdownInner(context, confirm); //此方法是在手机界面上弹出一个确认框,是否重启 } 这里不再详细跟踪代码了,shutdownInner(context, confirm); -->rebootOrShutdown(mContext, mReboot, mRebootReason); --> PowerManagerService.lowLevelReboot(reason); reason为“recoery”
PowerManagerService.lowLevelReboot方法定义在文件:
./frameworks/base/services/java/com/android/server/power/PowerManagerService.java
public static void lowLevelReboot(String reason) throws IOException { nativeReboot(reason); /*这里说明调用了本地jni库 reason为recovery*/ }
注意:这里不同的平台可能有所不同,本地库的编写不可能都相同,仅做参考
nativeReboot定义在文件
frameworks/base/services/jni/com_android_server_power_PowerManagerService.cpp
static void nativeReboot(JNIEnv *env, jclass clazz, jstring reason) { if (reason == NULL) { /*reason参数为空 正常启动*/ android_reboot(ANDROID_RB_RESTART, 0, 0); } else { ... android_reboot(ANDROID_RB_RESTART2, 0, (char *) chars); /*chars 为 recovery*/ } ... }
android_reboot定义在文件:
system/core/libcutils/android_reboot.c
int android_reboot(int cmd, int flags, char *arg) { int ret; if (!(flags & ANDROID_RB_FLAG_NO_SYNC)) sync(); if (!(flags & ANDROID_RB_FLAG_NO_REMOUNT_RO)) remount_ro(); swich (cmd) { case ANDROID_RB_RESTART: /*正常启动*/ ret = reboot(RB_AUTOBOOT); break; case ANDROID_RB_POWEROFF: /*关机*/ ret = reboot(RB_POWER_OFF); break; case ANDROID_RB_RESTART2: /*特殊启动 recvoery*/ if(0 == strcmp(arg,"recovery")){ writemisc(); /*这里是往misc分区写recovery命令行,在uboot中会检测misc分区中的BCB数据块*/ } /*这里是系统调用,参数LINUX_REBOOT_MAGIC1和LINUX_REBOOT_MAGIC2是个固定的整数,在内核中用来检验;arg为recovery*/ ret = __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,LINUX_REBOOT_CMD_RESTART2, arg); break; default: ret = -1; } return ret; } /*在uboot中会检测BCB数据块,进入recovery模式*/ static int build_recovery_cmd() { memcpy(bm.command, "boot-recovery", 14); /*将"boot-recovery" 写入command域*/ memcpy(bm.recovery, "recovery", 9); return 0; } static int writemisc(){ int phy_block_size = 0; int index, fd=0; fd = open(MISC_PARTITION, O_RDWR); if (fd < 0) return -1; build_recovery_cmd(); lseek(fd, 0x0, SEEK_SET); int cnt = write(fd, &bm, sizeof(bm)); printf("Write %d bytes to misc partition\r\n", cnt); close(fd); return 0; }
__reboot系统调用定义在:
./bionic/libc/arch-arm/syscalls/__reboot.S
#include <sys/linux-syscalls.h> ENTRY(__reboot) .save {r4, r7} stmfd sp!, {r4, r7} ldr r7, =__NR_reboot // 系统调用号 88, binoic/libc/include/sys/linux-syscalls.h swi #0 ldmfd sp!, {r4, r7} movs r0, r0 bxpl lr b __set_syscall_errno END(__reboot)
最终实现在内核中的__NR_reboot
内核部分
__NR_reboot定义在文件:kernel/include/asm-generic/unistd.h
#define __NR_reboot 142 __SYSCALL(__NR_reboot, sys_reboot)
__NR_reboot被映射到sys_reboot,最终的代码实现在文件:
kernel/kernel/sys.c
SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd, void __user *, arg) { char buffer[256]; int ret = 0; ..... /* 这里说明了两个魔数的功能,注意两个地方的魔数定义必须相同*/ if (magic1 != LINUX_REBOOT_MAGIC1 || (magic2 != LINUX_REBOOT_MAGIC2 && magic2 != LINUX_REBOOT_MAGIC2A && magic2 != LINUX_REBOOT_MAGIC2B && magic2 != LINUX_REBOOT_MAGIC2C)) return -EINVAL; .... mutex_lock(&reboot_mutex); switch (cmd) { case LINUX_REBOOT_CMD_RESTART: /*重启*/ kernel_restart(NULL); break; case LINUX_REBOOT_CMD_CAD_ON: C_A_D = 1; break; case LINUX_REBOOT_CMD_CAD_OFF: C_A_D = 0; break; case LINUX_REBOOT_CMD_HALT: kernel_halt(); do_exit(0); panic("cannot halt"); case LINUX_REBOOT_CMD_POWER_OFF: /*关机*/ kernel_power_off(); do_exit(0); break; case LINUX_REBOOT_CMD_RESTART2: /*recovery重启走这里*/ if (strncpy_from_user(&buffer[0], arg, sizeof(buffer) - 1) < 0) { ret = -EFAULT; break; } buffer[sizeof(buffer) - 1] = '\0'; kernel_restart(buffer); /*buffer 为 reovery*/ break; .... default: ret = -EINVAL; break; } mutex_unlock(&reboot_mutex); return ret; } void kernel_restart(char *cmd) /*这里cmd 为 recovery*/ { kernel_restart_prepare(cmd); disable_nonboot_cpus(); if (!cmd) printk(KERN_EMERG "Restarting system.\n"); else printk(KERN_EMERG "Restarting system with command '%s'.\n", cmd); kmsg_dump(KMSG_DUMP_RESTART); machine_restart(cmd); } EXPORT_SYMBOL_GPL(kernel_restart);
machine_restart定义文件为:
kernel/arch/arm/kernel/process.c
void machine_restart(char *cmd) { ... arm_pm_restart(reboot_mode, cmd); /*走这里,cmd 为 recovery*/ .... }
最终是调用arch_reset(mode,cmd),重启系统;cmd为传入的启动参数
目标文件在平台配置文件中,例如:
arch/arm/mach-prima2/include/mach/system.h
/******************** 这里只是让系统重启,没有其他操作; *********************/ void arch_reset(char mode, const char *cmd) { /* * use powerdown watch dog to reset system */ uint32_t u4Test; PDWNC_WRITE32(REG_RW_RESRV1, 0x33633363); PDWNC_WRITE32(REG_RW_WDT, 0xffd00000); for(u4Test = 0; u4Test < 10000; u4Test++) { } PDWNC_WRITE32(REG_RW_WDTSET, 1); while(1); }
各个平台有不同的实现方式,有的是直接设置寄存器,进入recovery;
这里是通过设置BCB进入reovery,在前面已经分析了
接下来可以想象在uboot中肯定会去检测BCB数据块,然后加载recoery镜像,进入recovery模式
uboot部分
uboot中会先后检查三种方式进入recovery是否成立:第一种是kernel直接写一个寄存器来标记下次启动将进入recovery模式;第二种是快捷键:powerkey+downVOL;第三中就是上层应用发送下来的command命令,这个命令在系统重启之前会往MISC分区中command(“boot-recovery”)。这里采用的是第三中方式;
uboot检查代码在文件:
uboot-83xx/lib_arm/board.c;如果没有,就在该文件中搜索“recovery”,应该会有相关函数
BOOL recovery_check_command_trigger(void) { struct misc_message misc_msg; struct misc_message *pmisc_msg = &misc_msg; const unsigned int size = NAND_WRITE_SIZE * MISC_PAGES; unsigned char *pdata; int ret; pdata = (uchar*)malloc(sizeof(uchar)*size); ret = mboot_recovery_load_misc(pdata, size); if (ret < 0) { return FALSE; } memcpy(pmisc_msg, &pdata[NAND_WRITE_SIZE * MISC_COMMAND_PAGE], sizeof(misc_msg)); MSG("Boot command: %.*s\n", sizeof(misc_msg.command), misc_msg.command); MSG("Boot status: %.*s\n", sizeof(misc_msg.status), misc_msg.status); MSG("Boot message\n\"%.20s\"\n", misc_msg.recovery); if(strcmp(misc_msg.command, "boot-recovery")==0) /*判断command域中的值,如果是"boot-recovery",就进入 recvoery*/ { g_boot_mode = RECOVERY_BOOT; } return TRUE; }
该到此结束!
进入recvoery的相关流程,下文分析。
相关文章推荐
- Android OTA升级原理和流程分析(五)---update.zip包从上层进入Recovery服务
- Android OTA升级原理和流程分析(五)---update.zip包从上层进入Recovery服务
- Android------recovery 模式启动进入流程
- android recovery 模式启动进入流程
- android recovery 模式启动进入流程
- Android系统Recovery工作原理之使用update.zip升级过程分析(五)---update.zip包从上层进入Recovery服务
- Android OTA升级原理和流程分析(五)---update.zip包从上层进入Recovery服务
- Android系统Recovery工作原理之使用update.zip升级过程分析(五)---update.zip包从上层进入Recovery服务
- Android系统Recovery工作原理之使用update.zip升级过程分析(五)---update.zip包从上层进入Recovery服务
- Android系统Recovery工作原理之使用update.zip升级过程分析(五)---update.zip包怎样从上层进入Recovery服务
- Android系统Recovery工作原理之使用update.zip升级过程分析(五)---update.zip包从上层进入Recovery服务
- Android系统Recovery工作原理之使用update.zip升级过程分析(五)---update.zip包从上层进入Recovery服务
- Android系统Recovery工作原理之使用update.zip升级过程分析(五)---update.zip包怎样从上层进入Recovery服务
- Android系统Recovery工作原理之使用update.zip升级过程分析(五)---update.zip包从上层进入Recovery服务
- Android系统Recovery工作原理之使用update.zip升级过程分析(五)---update.zip包从上层进入Recovery服务
- Android系统Recovery工作原理之使用update.zip升级过程分析(五)---update.zip包怎样从上层进入Recovery服务
- Android系统Recovery工作原理之使用update.zip升级过程分析(五)---update.zip包怎样从上层进入Recovery服务
- Android系统Recovery工作原理之使用update.zip升级过程分析(五)---update.zip包怎样从上层进入Recovery服务
- Android系统Recovery工作原理之使用update.zip升级过程分析(五)---update.zip包怎样从上层进入Recovery服务
- Android系统Recovery工作原理之使用update.zip升级过程分析(五)---update.zip包怎样从上层进入Recovery服务