平述factory reset ——从main system到重引导流程

关于Android或linux的引导流程,网上大都是从开机开始讲述的,或者直接跳过bootloader引导阶段,直接从init进程开始说起。这里我从手机正常运行状态开始,到重启状态以及重启之后的状态略做陈述,意在给读者展开一个更加直白的整机引导框架。 
一、device重启之前 
在手机的“setting–>备份与重置—>恢复出厂设置”里可以找到该设置,一旦执行了该设置,我们的手机便会恢复到原出厂设置状态,当然里面的用户数据、我们自行安装的应用等都将被全部清除(有些选项是可选择性删除的,eg:内部空间上的音乐、图片等)。下面一起看下恢复出厂设置的工作流程。 
操作中是从setting中进行的,当然代码中我们也从settings中开始看起。 
settings中涉及到恢复出厂设置的源码流程文件在MasterClearConfirm.java中。我们可以根据settings中的privacy_settings.xml进行查找,privacy_settings.xml是settings中主布局文件中的“备份与重置”Fragment选项,通过它我们可以找到“factory reset”的PreferenceScreen标签:

<!-- Factory reset -->
    <PreferenceScreen
        android:key="factory_reset"
        android:title="@string/master_clear_title"
        settings:keywords="@string/keywords_factory_data_reset"
        android:fragment="com.android.settings.MasterClear" />
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

从中我们会发现对应Fragment是“com.android.settings.MasterClear”。到此我们就可以就找到了对应的java文件了——MasterClear.java。进入到该java文件后我们发现,在showFinalConfirmation()函数中真正加载的Fragment是“MasterClearConfirm.class”,如下所示:

private void showFinalConfirmation() {
        Preference preference = new Preference(getActivity());
        preference.setFragment(MasterClearConfirm.class.getName());
        preference.setTitle(R.string.master_clear_confirm_title);
        preference.getExtras().putBoolean(ERASE_EXTERNAL_EXTRA, mExternalStorage.isChecked());
        ((SettingsActivity) getActivity()).onPreferenceStartFragment(null, preference);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

我们找到MasterClearConfirm.java就可以看到里面有我们想要的恢复出厂设置的操作。 
在函数doMasterClear()中会发送ACTION_MASTER_CLEAR广播,而接收者可以在framework/base/core/res/ AndroidManifest.xml中找到:

<receiver android:name="com.android.server.MasterClearReceiver"
            android:permission="android.permission.MASTER_CLEAR">
            <intent-filter
                    android:priority="100" >
                <!-- For Checkin, Settings, etc.: action=MASTER_CLEAR -->
                <action android:name="android.intent.action.MASTER_CLEAR" />

                <!-- MCS always uses REMOTE_INTENT: category=MASTER_CLEAR -->
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <category android:name="android.intent.category.MASTER_CLEAR" />
            </intent-filter>
        </receiver>

        <service android:name="com.android.internal.os.storage.ExternalStorageFormatter"
            android:permission="android.permission.MASTER_CLEAR"
            android:exported="true" />
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

从中可以看出接收者就是MasterClearReceiver,它是框架层的一个service。打开MasterClearReceiver.java,里面只重载了一个onReceive()函数。里面开辟了一个新的线程进行rebootWipeUserData的操作。该函数中组织好参数后通过bootCommand()往cache中的command文件中写指令,并远程调用PowerManagerService.java中的reboot()重启至recovery模式。走到这里,不熟悉Android系统启动流程的屌丝们也许这里就走不动了,唯一得到的信息就是附近应该有IPowerManager.aidl文件。其实,在这里是远程调用了一个系统级的服务——Powermanager。按照应用层的逻辑,我们会找对应的aidl文件,并继续寻找其中reboot功能函数的具体实现方法进一步去找onBind函数或asInterface的连接服务函数。但这里我们是找不到的。因为,该服务不是应用层的服务,是在系统启动的时候,zygote进程起来后,通过systemManager直接加载到服务列表中的,这里直接进行了使用(如何从PowerManager.java调到PowerManagerService.java中的,具体参看zygote进程的启动过程,在此过程中有PowerManager服务的注册流程)。按图索骥,我们可以找到PowerManagerService.java文件,该文件就是PowerManager服务的具体实现。在其中,我们可以找到reboot函数。该函数中的前半部分都是对权限的check,往后看会发现shutdownOrRebootInternal函数。在该函数中,设计者直接new出了一个Runnable线程,顺次看下求,在run函数中有ShutdownThread(ShutdownThread继承自Thread,是一个线程类)的reboot函数分支。在其中,将reason赋值给mRebootReason之后,进行了shutdownInner处理。该函数中,我们只需要看其最后一句:beginShutdownSequence(),点进去,进一步我们会发现在该函数最后启动了一个ShutdownThread线程实例sInstance。下面我们直接跳转到其run()函数。在该运行实体的最后会来到rebootOrShutdown()函数,该函数中,我们会发现lowLevelReboot分支和最后的lowLevelShutdown函数,这两个函数里面做的工作十分类似,都是去设置相应的Prop项。在这里我们只看lowLevelReboot分支。该函数中有详细说明:

if (reason.equals(PowerManager.REBOOT_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.
            //
            // This preparation can take more than 20 seconds if
            // there's a very large update package, so lengthen the
            // timeout.
            SystemProperties.set("ctl.start", "pre-recovery");
            duration = 120 * 1000L;
        } else {
            SystemProperties.set("sys.powerctl", "reboot," + reason);
            duration = 20 * 1000L;
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

所以,接下来直接看set函数。在该函数中最终还是调用了JNI的东西(Android4.4之前是在ShutdownThread.java文件中的lowLevelReboot函数中直接调用的): 
这里写图片描述 
仅从函数名判断不出什么,再看native_set函数的定义: 
这里写图片描述 
这里申明了一个native类型的函数,但怎么去找到其native函数的具体实现呢?观其文件名SystemProperties.java可以得知其对应注册的native函数命中应该是SystemProperties开头的。我们找到AndroidRuntime.cpp文件,里面有大量的native函数的注册(这里为什么直接定位到了AndroidRuntime.cpp文件?也跟Android启动流程中涉及到的systemmanager注册服务机制有关,具体需要研究下这块)。从中可以看到register_android_os_SystemProperties()函数的声明。点击去会发现里面也是调用了AndroidRuntime::registerNativeMethods()函数(该函数是native函数注册的一个工具函数,很多函数注册时都是通过它)。再看其参数method_table,该变量是一JNINativeMethod类型的数组,里面盛放的就是需要注册的函数列表: 
这里写图片描述 
从中可以找到SystemProperties_set函数,它就是我们要找的对应java侧SystemProperties.java文件中native_set()函数的native实体。进入到该函数会发现其最终也是通过property_set(key, val)系统函数将”ctl.start”, “pre-recovery”key, val或”sys.powerctl”, “reboot,”设置下去的。至此,在device重启之前的factory reset流程我们便走完了(具体设置完这些属性后,device为什么就会重启了呢?需要深入研究下device的电源管理或Android的关机流程这块了,这里不做分析)。至此,整个factory reset流程,我们才分析完一半,而另一半分布在device重启后的过程中,下面展开分析。 
二、Device重启之后 
提到重启,就不得不提bootloader。它是系统刚启动时运行的一段或几段程序,主要用来初始化硬件设备、引导系统内核启动。下面简单介绍下bootloader文件的一般组成: 
bootloader一般有好多个文件组成,如Android手机一般会有:PBL(Prime Bootloader), SBL1/2/3(Second Bootloader), APPSBL(有的也称为aboot、hboot), HLOS(基带baseband相关)和TZ(TrustZone相关的镜像)。而iphone手机一般是:BootRom(PBL, SecureROM), LLB(Low Level Bootloader),iBoot(stage 2 bootloader,常用于recovery模式), iBBS(精简版的ibOOT)和iBEC(用于从DFU-Device Firmware Upgrade模式恢复)。对于我的Exynos板子,由于其并非手机设备,包含的bootloader相对较少,有:PBL( 也叫bl0,烧在iROM的只读代码), BL1(stage 1 bootloader), BL2(stage 2 bootloader,就是uboot中的spl), tzsw(trustzone firmware)和uboot。Bootloader分为多阶段的引导,这部分除了正常的硬件初始化工作外,还有我们更关注的一点是签名验证。每一阶段都先验证下一阶段的镜像病验证通过后才加载,形成一个安全信任链,保证这些bootloader和后面的内核的完整性。这里之根据factory reset中涉及到的流程做浅尝解析。 
bootloader启动时汇编中入口文件为arch\arm\crt0.S,忽略其前期对硬件和环境的初始化,直接看跳往c语言的函数kmain:”bl Kmain“,该函数位于main.c文件中。 
进入到kmain函数中,会发现函数体中调用的大多数都是”_init”结尾的函数,顾名思义,他们都是为了初始化环境而存在的(该部分省略不议)。我们直接看到该函数最后,在快结束的地方发现它thread_create了一个线程,该线程的名字就叫bootstrap2,点击bootstrap2函数进入。与前面kmain函数类似,一直都是*_init(该部分见名知意,都是平台相关的初始化环节),我们忽略前面的,只看最后一个apps_init()。这里apps_init 是关键,对 LK 中所谓app 初始化并运行起来,而 aboot_init 就将在这里开始被运行,android linux 内核的加载工作就在 aboot_init 中完成的 。该函数中包含两个for函数,且循环条件一致:

/* call all the init routines */
    for (app = &__apps_start; app != &__apps_end; app++) {
        if (app->init)
            app->init(app);
    }

    /* start any that want to start on boot */
    for (app = &__apps_start; app != &__apps_end; app++) {
        if (app->entry && (app->flags & APP_FLAG_DONT_START_ON_BOOT) == 0) {
            start_app(app);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

该循环条件到底是什么意思呢?本人也是在网上大量的搜索,问google,求度娘,然而得到的最多的就是这么一句话:“至于会有那些 app 被放入 boot thread section, 则定义在 include/app.h 中的 APP_START(appname)”。但到app.h文件中却只找到:

#define APP_START(appname) struct app_descriptor _app_##appname __SECTION(".apps") = { .name = #appname,
#define APP_END };
  • 1
  • 2
  • 1
  • 2

一直不明白其中原理。直到找到对应的system-onesegment.ld文件(该文件在”bootable\bootloader\lk\ build-目标平台”目录下),该问题才有了眉目:

.rodata : { 
        *(.rodata .rodata.* .gnu.linkonce.r.*)
        . = ALIGN(4);
        __commands_start = .;
        KEEP (*(.commands))
        __commands_end = .;
        . = ALIGN(4);
        __apps_start = .;
        KEEP (*(.apps))
        __apps_end = .;
        . = ALIGN(4); 
        __rodata_end = . ;  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

原来,在其最终的连接文件里,是将需要启动的apps括在了SECTIONS下的.rodata段中,且以__apps_start为开头,以__apps_end标志结束(这里涉及到文件结构的部分内容,内容拓展可以看《程序员的自我修养—链接、装载与库》一书)。此时再结合网上所说的app.h中的那句话也就明了了许多了。正如网上所说“在 app 中只要像 app/aboot/aboot.c 指定就会在 bootloader bootup 时放入 thread section 中被执行”。这点我们可以直接在整个lk中搜索关键字“APP_START”会发现我们的bootloader中到底有多少个类似这样的app(不同的bootloader情况有所不同): 
这里写图片描述 
如上图,我们可以得知满足条件的app有aboot、clocktests、pcitests、shell、stringtests和tests,我们这里只关注aboot。 
我们找到aboot.c文件,找到aboot_init()函数。根据源码注释,依次实现了: 
1、设置NAND/EMMC读取信息页面大小:

/* Setup page size information for nv storage */
    if (target_is_emmc_boot())
    {
        page_size = mmc_page_size();
        page_mask = page_size - 1;
    }
    else
    {
        page_size = flash_page_size();
        page_mask = page_size - 1;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

2、读取按键信息,判断是正常开机,还是进入 fastboot ,还是进入recovery 模式: 
在函数体内,除了上半部分对按键进行判断以确定模式走向外,还有对BCB区域中command指令的读取来判断:init.c文件中的check_hard_reboot_mode():

unsigned check_hard_reboot_mode(void)
{
    uint8_t hard_restart_reason = 0;
    uint8_t value = 0;

    /* Read reboot reason and scrub it
      * Bit-5, bit-6 and bit-7 of SOFT_RB_SPARE for hard reset reason
      */
    value = pm8x41_reg_read(PON_SOFT_RB_SPARE);
    hard_restart_reason = value >> 5;
    pm8x41_reg_write(PON_SOFT_RB_SPARE, value & 0x1f);

    return hard_restart_reason;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

或者check_reboot_mode():

unsigned check_reboot_mode(void)
{
    uint32_t restart_reason = 0;
    uint32_t soc_ver = 0;
    uint32_t restart_reason_addr;

    soc_ver = board_soc_version();

    if (platform_is_8974() && BOARD_SOC_VERSION1(soc_ver))
        restart_reason_addr = RESTART_REASON_ADDR;
    else
        restart_reason_addr = RESTART_REASON_ADDR_V2;

    /* Read reboot reason and scrub it */
    restart_reason = readl(restart_reason_addr);
    writel(0x00, restart_reason_addr);

    return restart_reason;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

从上面代码会发现最终的返回值restart_reason_addr是从RESTART_REASON_ADDR或者RESTART_REASON_ADDR_V2中取得的,这里就是BCB中的上个command里的内容了。也是在这里开始决定接下来系统会走向main system、Recovery还是fastboot模式的。如果启动的是main system或者Recovery模式,则会执行下文的后续步骤;如果是fastboot模式,则通过fastboot_init()直接初始化并启动到fastboot阶段,该模式下没有内核的加载和启动。 
3、加载内核:如果是启动main system则执行boot_linux_from_mmc()进行加载,如果是启动Recovery模式则通过boot_linux_from_flash()加载, 
4、启动内核:

boot_linux((void *)hdr->kernel_addr, (void *)hdr->tags_addr,
           (const char *)hdr->cmdline, board_machtype(),
           (void *)hdr->ramdisk_addr, hdr->ramdisk_size);
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

这里linux kernel起来之后,会初始化init进程,并布置ramdisk文件系统等等后续应用工作,这里具体流程请参看linux kernel启动流程。 
三、结束语 
至此,从main system到bootloader然后再次到模式选择分析结束,接下来我们可以选择具体的main system 、recovery或者fastboot模式继续往下分析,具体分析这里不再继续。main system模式具体见网上Android或linux的启动流程。而recovery模式接下的具体流程可以参见recovery源码流程分析。

posted on 2016-06-11 12:13  王亮1  阅读(855)  评论(0编辑  收藏  举报

导航