zoukankan      html  css  js  c++  java
  • 平述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启动时汇编中入口文件为archarmcrt0.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文件(该文件在”bootableootloaderlk 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源码流程分析。

  • 相关阅读:
    数据结构 【实验 串的基本操作】
    Ioc容器依赖注入-Spring 源码系列(2)
    定时任务管理中心(dubbo+spring)-我们到底能走多远系列47
    jvm内存增长问题排查简例
    Ioc容器beanDefinition-Spring 源码系列(1)
    SPI机制
    java工厂-积木系列
    java单例-积木系列
    利用spring AOP 和注解实现方法中查cache-我们到底能走多远系列(46)
    java 静态代理-积木系列
  • 原文地址:https://www.cnblogs.com/liang123/p/6325212.html
Copyright © 2011-2022 走看看