平述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源码流程分析。
平述factory reset ——从main system到重引导流程的更多相关文章
- Bootloader - main system - Recovery的三角关系【转】
本文转载自:http://blog.csdn.net/u012719256/article/details/52304273 一.MTD分区: BOOT: boot.img,Linux ...
- Bootloader - Main system - Recovery的三角关系
原文地址:http://blog.csdn.net/myarrow/article/details/8115610 一.MTD分区:BOOT: boot.img,Linux kernel ...
- 学习:Log中'main', 'system', 'radio', 'events'
在Android中不同的log写到不同的设备中,共有/dev/log/system, /dev/log/main, /dev/log/radion, /dev/log/events四中类型.其中默认L ...
- 菜鸟nginx源代码剖析 框架篇(一) 从main函数看nginx启动流程
菜鸟nginx源代码剖析 框架篇(一) 从main函数看nginx启动流程 Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.c ...
- 菜鸟nginx源码剖析 框架篇(一) 从main函数看nginx启动流程(转)
俗话说的好,牵牛要牵牛鼻子 驾车顶牛,处理复杂的东西,只要抓住重点,才能理清脉络,不至于深陷其中,不能自拔.对复杂的nginx而言,main函数就是“牛之鼻”,只要能理清main函数,就一定能理解其中 ...
- spark提交任务的流程
1.spark提交流程 sparkContext其实是与一个集群建立一个链接,当你停掉它之后 就会和集群断开链接,则属于这个资源的Excutor就会释放掉了,Driver 向Master申请资源,Ma ...
- android recovery 主系统代码分析
阅读完上一篇文章: http://blog.csdn.net/andyhuabing/article/details/9226569 我们已经清楚了如何进入正常模式和Recovery模式已有深刻理解了 ...
- Android5.0 Recovery源代码分析与定制(一)【转】
本文转载自:http://blog.csdn.net/morixinguan/article/details/72858346 版权声明:本文为博主原创文章,如有需要,请注明转载地址:http://b ...
- android recovery 主系统代码分析【转】
本文转载自:http://blog.csdn.net/andyhuabing/article/details/9248713 阅读完上一篇文章: http://blog.csdn.net/andyhu ...
随机推荐
- JAVA NIO工作原理及代码示例
简介:本文主要介绍了JAVA NIO中的Buffer, Channel, Selector的工作原理以及使用它们的若干注意事项,最后是利用它们实现服务器和客户端通信的代码实例. 欢迎探讨,如有错误敬请 ...
- C++中的各种可调用对象
概述 一组执行任务的语句都可以视为一个函数,一个可调用对象.在程序设计的过程中,我们习惯于把那些具有复用性的一组语句抽象为函数,把变化的部分抽象为函数的参数. 函数的使用能够极大的极少代码重复率,提高 ...
- Mybatis Generator 代码生成配置
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration ...
- mysql 拼接
SELECT RTRIM(CONCAT(belong_master_ip ,'(',host_name,')')) AS cloudIP FROM `cloud_master_cfg`
- c# 操作数据库
查询 string strConnection = "Data Source=(local);Initial Catalog=zpractice;Integrated Security=SS ...
- 利用css3+js实现简单带立体过渡效果的图片切换(chrome浏览器)
<!DOCTYPE HTML> <html lang="en-US"> <head> <meta charset="UTF-8& ...
- IntelliJ IDEA的编译设置
好就之前遇到的bug,eclipse转idea时,idea不编译一个项目! 查了半天是IntelliJ IDEA的编译设置问题,不设置编译该文件,idea就不编译!
- 利用create-react-app结合react-redux、react-router4构建单页应用
1.创建项目: a.全局安装create-react-app: npm install create-react-app -g b.执行create-react-app my-projectN ...
- RHEL Linux常用指令
查询已安装软件包 rpm -qa|grep * 安装软件 rpm -ivh * 查询Linux版本 uname -a lsb_release -a cat /etc/redhat-release ca ...
- Cookie&Session(会话技术)
一.Cookie技术 从打开一个游览器访问某个站点,到关闭这个游览器的整个过程成为一次会话 会话技术分为Cookie和Session Cookie:数据存储在客服端本地,减少对服务端的存储的压力,安全 ...