Recovery流程梳理

1. Recovery介绍

1.进入方式:reset + vol-, 或者根据各个平台而定
2.用途:
2.1恢复系统到出厂模式,即擦除用户数据和缓存数据
2.2系统升级
系统升级大体来说包含两种方式:1.在线升级,利用网络,系统自动连接服务区下载ota升级包;2.离线升级,将下载到的ota包放在指定位置,然后进入升级模式。每次启动,flash_image程序会检查recovery分区中的image的header。如果与备份的recovery.img不符就会把备份写道recovery分区。出于安全方面的考虑,ota是有签名的,recovery对签名是有要求的。如果想要破解的话就是更换一个不检查签名的recovery的程序
3.结构
从架构上面来说recovery分为三个部分
1.main system:用boot.img驱动linux系统,这种属于android启动的正常工作模式。
2.recovery:用recovery.img启动的linux系统,主要是运行recovery程序
3.bootloader:除了加载/启动系统,还会通过读取flash的misc分区来获得main system和recover的消息,根据读取结果觉得何种操作。

2. Framework recovery 分析

Framework层中的Recovery做的事情比较少,主要作用就是写命令,然后重启。

2.1 Framework Recovery流程
Created with Raphaël 2.2.0 RecoverySystem verifyPackage verify result InstallPackage Write recovery commands Reboot System OTA failed yes no
2.2 Framework Recovery 分析

android/framework/base/core/java/android/os/RecoverySystem.java通过浏览的方式选择好升级包之后就会调用下面的函数:

    public static void installPackage(Context context, File packageFile)
        throws IOException {
        String filename = packageFile.getCanonicalPath(); //获取升级包的路径及名称。这里面在升级时,如果升级包是放在sd卡中,获取到的地址是/storage。会导致后面异常提示找不到文件
        FileWriter uncryptFile = new FileWriter(UNCRYPT_FILE);
        try {
            uncryptFile.write(filename + "\n");
        } finally {
            uncryptFile.close();
        }
        Log.w(TAG, "devin !!! REBOOTING TO INSTALL " + "sdcard" + " !!!");

        // If the package is on the /data partition, write the block map file
        // into COMMAND_FILE instead.
        if (filename.startsWith("/data/")) {//如果升级包是放在data目录下面,则将文件地址转化为下面的值,也可能导致文件找不到异常
            filename = "@/cache/recovery/block.map";
        }
        //填写参数
        final String filenameArg = "--update_package=" + filename;
        final String localeArg = "--locale=" + Locale.getDefault().toString();
        //这个函数比较重要下面分析一下
        bootCommand(context, filenameArg, localeArg);
    }

将上述的参数写入到文件中/cache/recovery/command,然后调用powermanager重启系统

    private static void bootCommand(Context context, String... args) throws IOException {
        RECOVERY_DIR.mkdirs();  //创建文件目录/cache/recovery
        COMMAND_FILE.delete();  // 删除之前的/cache/recovery/command文件。这个文件中包含了recovery需要的参数
        LOG_FILE.delete();//删除log文件

        FileWriter command = new FileWriter(COMMAND_FILE);//创建新的command文件
        try {
            for (String arg : args) {
                if (!TextUtils.isEmpty(arg)) {
                    command.write(arg);
                    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(PowerManager.REBOOT_RECOVERY);//文件写完之后,调用powermanager重启

        throw new IOException("Reboot failed (no permissions?)");
    }

通过powermanager最终会调用到PowerManagerService中的reboot函数

PowerManager.java
public void reboot(String reason) {
        try {
            mService.reboot(false, reason, true);
        } catch (RemoteException e) {
        }
    }
PowerManagerService.java
public void reboot(boolean confirm, String reason, boolean wait) {
        //检查权限代码省略
            try {
                shutdownOrRebootInternal(false, confirm, reason, wait);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }

private void shutdownOrRebootInternal(final boolean shutdown, final boolean confirm,final String reason, boolean wait) {
    //其它检查错误的代码省略
Runnable runnable = new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    if (shutdown) {
                        ShutdownThread.shutdown(mContext, confirm);
                    } else {//因为是reboot,因此会走这个分支
                        ShutdownThread.reboot(mContext, reason, confirm);
                    }
                }
            }
        };
    }
ShutdownThread.java
public static void reboot(final Context context, String reason, boolean confirm) {
        shutdownInner(context, confirm);
}

static void shutdownInner(final Context context, boolean confirm) {
//这个函数里面处理了一些ui方面的东东,主要的就是下面的函数
            beginShutdownSequence(context);
}
在这里建立一个ShutdownThread的实例
private static final ShutdownThread sInstance = new ShutdownThread();

private static void beginShutdownSequence(Context context) {
 // start the thread that initiates shutdown
        sInstance.mHandler = new Handler() {//给上面的sInstance创建一个handler用来起线程
        };
        sInstance.start();然后就执行这个线程
}

public void run() {
···
    rebootOrShutdown(mContext, mReboot, mRebootReason);
}

public static void rebootOrShutdown(final Context context, boolean reboot, String reason) {
        deviceRebootOrShutdown(reboot, reason);
        if (reboot) {//调用PowerManagerService中的接口进入recovery
            PowerManagerService.lowLevelReboot(reason);
            Log.e(TAG, "Reboot failed, will attempt shutdown instead");
        } else if (SHUTDOWN_VIBRATE_MS > 0 && context != null) {
        }
        // Shutdown power
        Log.i(TAG, "Performing low-level shutdown...");
        PowerManagerService.lowLevelShutdown();
}

PowerManagerService.java
public static void lowLevelReboot(String reason) {
        if (reason.equals(PowerManager.REBOOT_RECOVERY)) {//进入recovery
            // If we are rebooting to go into recovery, instead of
            // setting sys.powerctl directly we'll start the
            // pre-recovery service which will do some preparation for
            // recovery and then reboot for us.
            SystemProperties.set("ctl.start", "pre-recovery");//调用init.rc中注册的service
        } else {//直接重启
            SystemProperties.set("sys.powerctl", "reboot," + reason);
        }
    }

至此在framework中的出厂设置流程就走完了。
接下来就是运行init.rc中的service
首先看一下pre-recovery这个servce是怎么写的

service pre-recovery /system/bin/uncrypt --reboot
    class main
    disabled 
    oneshot

uncrypt.cpp
int main(int argc, char** argv) {
    if (argc != 3 && argc != 1 && (argc == 2 && strcmp(argv[1], "--reboot") != 0)) {
        fprintf(stderr, "usage: %s [--reboot] [<transform_path> <map_file>]\n", argv[0]);
        return 2;
    }
    // When uncrypt is started with "--reboot", it wipes misc and reboots.
    // Otherwise it uncrypts the package and writes the block map.
    if (argc == 2) {
        if (read_fstab() == NULL) {
            return 1;
        }
        wipe_misc();//这里面就是擦除/misc分区
        reboot_to_recovery();
    } else {
    }
    return 0;
}
启动另外一个服务
static void reboot_to_recovery() {
    ALOGI("rebooting to recovery");
    property_set("sys.powerctl", "reboot,recovery");
    sleep(10);
}

init.rc
on property:sys.powerctl=*
    powerctl ${sys.powerctl}

根据android/system/core/init/keywords.h中的定义
KEYWORD(powerctl, COMMAND, 1, do_powerctl)会调用 do_powerctl这个函数

android/system/core/init/builtins.cpp
int do_powerctl(int nargs, char **args)
{
    res = expand_props(command, args[1], sizeof(command));
    if (strncmp(command, "shutdown", 8) == 0) {
        cmd = ANDROID_RB_POWEROFF;
        len = 8;
    } else if (strncmp(command, "reboot", 6) == 0) {//走这个分支
        cmd = ANDROID_RB_RESTART2;
        len = 6;
    } else {
        ERROR("powerctl: unrecognized command '%s'\n", command);
        return -EINVAL;
    }
    return android_reboot(cmd, 0, reboot_target);

android/system/core/libcutils/android_reboot.c
int android_reboot(int cmd, int flags UNUSED, const char *arg)
{
    int ret;

    sync();
    remount_ro();

    switch (cmd) {
        case ANDROID_RB_RESTART:
            ret = reboot(RB_AUTOBOOT);
            break;

        case ANDROID_RB_POWEROFF:
            ret = reboot(RB_POWER_OFF);
            break;

        case ANDROID_RB_RESTART2:
            ret = syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
                           LINUX_REBOOT_CMD_RESTART2, arg);
            break;

        default:
            ret = -1;
    }

    return ret;
}

kernel/kernel/sys.c

SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,void __user *, arg){
    case LINUX_REBOOT_CMD_RESTART2:
        if (strncpy_from_user(&buffer[0], arg, sizeof(buffer) - 1) < 0) {
            ret = -EFAULT;
            break;
        }
        buffer[sizeof(buffer) - 1] = '\0';

        kernel_restart(buffer);
        break;
}

void kernel_restart(char *cmd)
{
    kernel_restart_prepare(cmd);
    migrate_to_reboot_cpu();
    syscore_shutdown();
    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);//recovery
}
void machine_restart(char *cmd)
{
    preempt_disable();
    smp_send_stop();

    /* Flush the console to make sure all the relevant messages make it
     * out to the console drivers */
    arm_machine_flush_console();

    arm_pm_restart(reboot_mode, cmd);

    /* Give a grace period for failure to restart of 1s */
    mdelay(1000);

    /* Whoops - the platform was unable to reboot. Tell the user! */
    printk("Reboot failed -- System halted\n");
    local_irq_disable();
    while (1);
}
看一下arm_pm_restart这个函数是如何实现的,搜索发现这个函数是msm_restart_probe注册进来的
static int msm_restart_probe(struct platform_device *pdev)
{
...
    pm_power_off = do_msm_poweroff;
    arm_pm_restart = do_msm_restart;
...
}

static void do_msm_restart(enum reboot_mode reboot_mode, const char *cmd)
{
...
    msm_restart_prepare(cmd);
...
}

static void msm_restart_prepare(const char *cmd)
{
    if (cmd != NULL) {
        if (!strncmp(cmd, "bootloader", 10)) {
            qpnp_pon_set_restart_reason(
                PON_RESTART_REASON_BOOTLOADER);
            __raw_writel(0x77665500, restart_reason);
        } else if (!strncmp(cmd, "recovery", 8)) {
            qpnp_pon_set_restart_reason(
                PON_RESTART_REASON_RECOVERY);
            __raw_writel(0x77665502, restart_reason);//走这个分支,向这个地址写入“recovery”
        } else if (!strcmp(cmd, "rtc")) {

        } else if (!strncmp(cmd, "oem-", 4)) {

        } else if (!strncmp(cmd, "edl", 3)) {
#ifdef CONFIG_REBOOT_POWEROFF
        } else if (!strcmp(cmd, "chargemode")) {
        } else if (!strcmp(cmd, "hotbatt")) {
#endif
        } else {
        }
    }
}

开机之后的lk阶段会判断这个地址的值,从而进入recovery模式。

3. Recovery分析

3.1 Recovery流程

enter description here

3.2 Recovery源码分析

系统重启之后最终会调用到android/bootable/recovery/recovery.cpp。

int main(int argc, char **argv) {
    load_volume_table();//加载分区列表/etc/recovery.fstab,这个分区表是之前就写好的,放在device/qcom/msm8952_64/recovery.fstab
    get_args(&argc, &argv);//读取/cache/recovery/command中的指令(上面framework写进去的),填写bootloader信息
主要的函数是这个
    install_package(update_package, &should_wipe_cache, TEMPORARY_INSTALL_FILE, mount_required);
    Device::BuiltinAction temp = prompt_and_wait(device, status);//这个就是在升级完img之后,进入到recovery界面
    之后安装成功,复制升级时输出的log信息到 cache/recovery目录
}

static int
really_install_package(const char *path, bool* wipe_cache, bool needs_mount)
{   
    MemMapping map;
    if (sysMapFile(path, &map) != 0) {
        LOGE("failed to map file\n");
        return INSTALL_CORRUPT;
    }
    int numKeys;
    Certificate* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);
    int err;
    err = verify_file(map.addr, map.length, loadedKeys, numKeys);//验证升级包的完整性
    free(loadedKeys);
    ZipArchive zip;
    err = mzOpenZipArchive(map.addr, map.length, &zip);//解析压缩包
    int result = try_update_binary(path, &zip, wipe_cache);
    return result;
}
//这函数的主要作用就是开启一个新的线程去做真正的升级,在这个函数里面会监听得到的消息,用来更新ui或者log信息。
static int try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache) {
    bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);//将压缩包读到fd中,通过代码分析,它是将升级文件放到/tmp/update_binary目录下面
下面就是开启一个新的线程去写数据升级系统
    pid_t pid = fork();
    if (pid == 0) {
        umask(022);
        close(pipefd[0]);
        execv(binary, (char* const*)args);//这里面的binary就是下面将要介绍的updater
        fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno));
        _exit(-1);
    }
}

真正执行升级系统的程序是updater。文件路径为:
android/bootable/recovery/updater/updater.c
这个文件的主要作用就是解析压缩包中的META-INF/com/google/android/updater-script文件
在同级目录下面的Makefile中有下列语句:

updater_src_files := \
       install.c \
       blockimg.c \
       updater.c

LOCAL_MODULE := updater//编译生成的文件名称

在android/buildscripts/build_android.sh 文件中有下面语句:

cp out/target/product/msm8952_64/obj_arm/EXECUTABLES/updater_intermediates/updater $EMMC_FILES_FOLDER/UpdateFactoryRom/META-INF/com/google/android/update-binary
cp $PRODUCT_DEVICE/recovery/patch_script/UpdateFactoryRom/updater-script $EMMC_FILES_FOLDER/UpdateFactoryRom/META-INF/com/google/android/updater-script

4. 升级的执行者 Updater分析

看到在最后的时候将中间生成的updater、updater-script拷贝到META-INF/com/google/android/中,并且重命名。
updater.c分析

int main(int argc, char** argv) {
   ...
    int fd = atoi(argv[2]);
    FILE* cmd_pipe = fdopen(fd, "wb");
    setlinebuf(cmd_pipe);

    // Extract the script from the package.

    const char* package_filename = argv[3];
    printf(“ package_filename: %s\n", package_filename);//sdcard/update.zip
    MemMapping map;
    //映射文件到内存当中
    if (sysMapFile(package_filename, &map) != 0) {
    }
    //将映射到内存的文件放入到za结构当中
    err = mzOpenZipArchive(map.addr, map.length, &za);
    }
    const ZipEntry* script_entry = mzFindZipEntry(&za, SCRIPT_NAME);
    char* script = malloc(script_entry->uncompLen+1);
    if (!mzReadZipEntry(&za, script_entry, script, script_entry->uncompLen)) {
        printf("failed to read script from package\n");
        return 5;
    }
    script[script_entry->uncompLen] = '\0';

    // Configure edify's functions.
    //注册函数,用来解析updater-script
    RegisterBuiltins();
    RegisterInstallFunctions();
    RegisterBlockImageFunctions();
    RegisterDeviceExtensions();
    FinishRegistration();//重新排序

    // Parse the script.
    //将脚本中的内容放入到root中
    int error = parse_string(script, &root, &error_count);
    // Evaluate the parsed script.
    char* result = Evaluate(&state, root);// 在这个函数里面调用刚才注册进来的函数。也就是在这里进行真正的升级
    if (result == NULL) {//升级失败
    } else {//升级成功
    }
....
}

5. OTA 目录结构

解压一个升级包,看到他的目录结构为:
update压缩包目录结构
其中在patch/system中存放的都是patch。在META_INFO中是升级程序以及验证信息。
在上面提到的script就是updater-script脚本。这个脚本大体分为三个部分:
1.验证patch的有效性
2.打patch
3.更改连接和文件的属性
在第2步打patch中最终会调用bootable/recovery/applypatch/applypatch.c 文件中的applypatch函数。

6. 涉及到的其它内容

在recovery中涉及到了BCB(Bootloader Control Block),BCB是bootloader和recovery通讯的接口,也是boot loader与MainSystem之间的通讯接口。存储在flash中的MISC分区中,占用3个page。BCB结构为:

/* Recovery Message */
struct recovery_message {
    char command[32];
    char status[32];
    char recovery[1024];
};

  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值