本文为转载,转载地址:
http://huaqianlee.github.io/2015/08/23/Android/%E9%AB%98%E9%80%9AAndroid%E8%AE%BE%E5%A4%87%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90-%E4%BB%8Epower-on%E4%B8%8A%E7%94%B5%E5%88%B0Home-Lanucher%E5%90%AF%E5%8A%A8/
Platform Information :
System: Android5.1
Platform: Qualcomm msm8916
Author: Andy Lee
Email: huaqianlee@gmail.com
如有错误欢迎指出,共同学习,共同进步
在我第一次接触Android得时候,我就很想知道 Android设备在按下电源键后是怎么启动到主界面的,但是到现在为止也没有完全理清这个过程,所以就决定从按下power键开始来分析一下这个流程。 虽然Android基于Linux内核开发的一个操作系统,但是在init进程后Android附加了很多其他操作,所以其启动流程还是有比较大的差别 的,关于Linux系统的启动流程可以参考我的另一篇博文:
深入理解linux启动过程。
因为我现在工作中用到的是高通的源码,并且高通也是目前Android手机的主流芯片,所以我就按照高通的msm8916来分析了,不过其他的也应该大同小异。
首先来看一下官方给出的Android系统架构:
当按下电源开关后,主要执行了如下步骤:
另,在内核启动了第一个进程后,init->home lanucher的详细流程如下:
注:此图取自网络,觉得描述得很详细,故附上
接下来就按照引导程序、内核启动、init进程、系统服务、Home Lanucher这样的顺序来分析Android启动的code。
引导程序
引导程序在Android操作系统开始运行前的一个小程序,其主要为内核启动服务。引导程序执行的 第一段代码,因此它是针对特定的主板与芯片的。设备制造商要么使用很受欢迎的引导程序比如redboot、uboot或者开发自己的引导程序,它不是 Android操作系统的一部分。引导程序是OEM厂商或者运营商加锁和限制的地方。
引导程序分两个阶段执行。第一个阶段,检测外部的RAM以及加载对第二阶段有用的程序;第二阶段,引导程序设置网络、内存等等。这些对于运行内核是必要的,为了达到特殊的目标,引导程序可以根据配置参数或者输入数据设置内核。
power-on及系统启动
当按下电源键或者系统重启之后,引导芯片代码 PBL(Primary Boot Loader,类似于x86的BIOS)从预定义的地方(固化在ROM)开始执行,PBL由高通做好了的烧写在芯片中,PBL将启动设备、支持紧急下载 等,然后加载引导程序sbl1,然后跳转到sbl1执行。
处理器启动地址
MSM8916芯片内部有很多不同的处理器,如下:
子系统 |
处理器 |
启动地址 |
APPS
Cortex-a53
0xfc010000
RPM
Cortex-m3
0x00200000/0x0
Modem
MSS_QDSP6
可配置的
Pronto
ARM9TM
0x0/0xffff0000/硬件重映射
启动栈
组件 |
处理器 |
加载源地址 |
执行地址 |
功能 |
APPS PBL
Cortex-A53
NA
APPS ROM
启动设备,检测接口,支持紧急下载,通过L2TCM加载和校验SBL1 ELF段,加载校验RPM code RAM
SBL1
Cortex-A53
eMMC
L2 TCM(segment1)/OCIMEM/RPM code RAM(segment2)
初始化内存子系统(总线,DDR,时钟,CDT),加载校验TZ、Hyperviser、 RPM_FW、APPSBL镜像,通过USB2.0和Sahara协议memory dump,看门狗调试retention(如:L2 flush),RAM dump到eMMC/SD卡等的支持,大容量存储支持,USB驱动支持,USB充电,温度检测,PMIC驱动的支持,配置DDR以及crash调试的 flush L1/L2/ETB支持等相关配置
QSEE/TZ
Cortex-A53
eMMC
LPDDR2/3
等同于TZBSP,设置运行时安全环境,配置xPU,支持fuse驱动,校验子系统镜像,丢弃RESET调试功能
QHEE(Hypervisior)
Cortex-A53
eMMC
LPDDR2/3
Hypervisor镜像负责设置VMM,配置SMMU以及控制xPU存取
RPM_FW
Cortex-M3
eMMC
RPM code RAM
电源资源管理
APPSBL/启动管理器和系统加载器
Cortex-A53
eMMC
LPDDR2/3
启动画面,加载校验内核
HLOS
Cortex-A53
eMMC
LPDDR2/3
引导HLOS镜像,例如a53 HLOS内核镜像,Pronto镜像等
Modem PBL
MSS_QDSP6
NA
Modem ROM HexagonTM TCM
设置Hexagon TCM,从LPDDR2/3拷贝MBA到Hexagon TCM并校验
MBA
MSS_QDSP6
eMMC
Hexagon TCM
校验modem镜像,xPU为modem和memory dump保护DDR
eMMC :Embeded Multi Media Card,内嵌式记忆体,内部存储
APPS PBL:Application Processor Primary Boot Loader,应用处理器初级引导程序
SBL1:Secondary Boot Loader Stage1,第二引导程序阶段一(此处写阶段一是因为早期高通芯片分为几个阶段,但现在都由sbl1实现)
TZ:TrustZone
PRM_FW:Resource Power Manager Firmware,电源资源管理固件
HLOS:High-Level Operating System,高级操作系统
Modem PBL:Modem Primary Boot Loader,调制解调器侧初级引导程序
MBA:Modem Boot Authenticator,调制解调器侧引导校验程序
引导代码流程
-
系统上电或者MSM8916 AP侧CPU重启。
-
Cortex-A53中APPS PBL执行,从启动设备中加载校验是sbl1镜像,然后跳转到sbl1中执行。
-
sbl1初始化ddr,从启动设备中加载校验QSEE/TZ、QHEE、RPM_FW、APPSBL镜像到DDR。
-
sbl1将控制权给QSEE/TZ,QSEE/TZ将设置一个安全环境,配置xPU,并支持fuse驱动。
-
QSEE传递控制权给QHEE,QHEE负责设置VMM,配置SMMU和xPU存取控制。
-
QHEE通知RPM开始执行RPM固件。
-
QHEE将控制器传递给HLOS APPSBL,APPSBL将初始化系统。
-
HLOS APPSBL加载和校验HLOS内核。
-
HLOS内核通过PIL.Modem加载MBA和modem镜像到DDR,然后继续启动进程。
-
HLOS通过PIL加载外围设备镜像Pronto到DDR,在通过TZ校验。
第一阶段引导程序和第二阶段引导程序
由PBL加载的sbl1是第一阶段引导程序,APP SBL为第二阶段引导程序。这两部分代码的作用在上面
启动栈和
引导代码流程中已有一个简单的描述,如果想了解更多请参考我另外两篇博文:
Android源码bootable解析之bootloader LK(little kernel)高通平台Android源码bootloader分析之sbl1(一)
内核
Android的内核就是用的Linux的内核,只是针对移动设备做了一些优化,所有Android内核与 linux内核启动的方式差不多。内核主要设置缓存、被保护存储器、计划列表,加载驱动等。当内核完成这些系统设置后,它首先在系统文件中寻 找”init”文件,然后启动root进程或者系统的第一个进程。这部分可以参考我的另一篇博文:
深入理解linux启动过程
init进程
init进程时Android的第一个用户空间进程,是所有进程的父进程。init进程主要有两个任务,一是挂载目录,比如/sys、/dev、/proc,二是读取解析init.rc脚本,将其中的元素整理成自己的数据结构(链表)。
init进程实现路径: systemcoreinit
init.c
首先来看一下init进程的实现代码init.c, 其关键代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# systemcoreinitinit.c
int main(int argc, char **argv)
{
...
/* Get the basic filesystem setup we need put
* together in the initramdisk on / and then we'll
* let the rc file figure out the rest.
*/
mkdir("/dev", 0755);
mkdir("/proc", 0755);
mkdir("/sys", 0755);
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
mount("proc", "/proc", "proc", 0, NULL);
mount("sysfs", "/sys", "sysfs", 0, NULL);
...
property_init(); // 初始化属性服务,主要为属性文件分配存储空间
get_hardware_name(hardware, &revision); // 从虚拟文件/proc/cpuinfo中获取hardware及revision,后面init.rc中的hardware变量值从此获取
process_kernel_cmdline(); // 导入命令行参数并用属性值设置内核变量, /proc/cmdline
...
selinux_initialize();// 初始化selinux安全机制
init_parse_config_file("/init.rc"); // 解析init.rc文件,主要生成action和service链表
/* 解析完init.rc配置文件后,会得到一系列的Action,action_for_each_trigger函数用来将Action加入action_queue,有关init.rc、action等内容下面再分析*/
action_for_each_trigger("early-init", action_add_queue_tail); // 添加“early-init”action
queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
queue_builtin_action(keychord_init_action, "keychord_init");
queue_builtin_action(console_init_action, "console_init");
/* execute all the boot actions to get us started */
action_for_each_trigger("init", action_add_queue_tail); // 添加“init”action
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
queue_builtin_action(property_service_init_action, "property_service_init"); // 启动属性服务
queue_builtin_action(signal_init_action, "signal_init");
/* Don't mount filesystems or start core system services if in charger mode. */
if (is_charger) {
action_for_each_trigger("charger", action_add_queue_tail);
} else {
if (is_ffbm) {
action_for_each_trigger("ffbm", action_add_queue_tail);
} else {
action_for_each_trigger("late-init", action_add_queue_tail); // 添加“late-init”action
}
}
/* run all property triggers based on current state of the properties */
queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
for(;;) { // 无限循环,建立init子进程
...
execute_one_command(); // 执行节点command,zygote service也在此启动,稍后再详细分析
restart_processes(); // 重启进程
# 监听属性服务事件
ufds[fd_count].fd = get_property_set_fd();
ufds[fd_count].events = POLLIN; // 属性事件
ufds[fd_count].fd = get_signal_fd();
ufds[fd_count].events = POLLIN; // 子进程事件
ufds[fd_count].fd = get_keychord_fd();
ufds[fd_count].events = POLLIN; // keychord热键事件
...
#if BOOTCHART
// bootchart是一个性能统计工具,用于搜集硬件和系统的信息,并将其写入磁盘,以便其他程序使用
#endif
nr = poll(ufds, fd_count, timeout); // 等待下一个命令提交
# 处理具体消息
handle_property_set_fd(); // 处理属性命令
handle_keychord(); // adb使能时处理keychord
handle_signal(); // 处理子进程挂掉发来的信号,service重启
...
}
init.rc
.rc文件的语法
init.rc文件是Android的有特定格式和规则的脚本文件,位于:systemcore
ootdirinit.rc,称为Android的初始化语言。当进入adb shell后,我们能在根目录看到一个只读的虚拟内存文件init.rc,源文件init.rc被打包在boot.img中ramdisk.img中。其有四类声明:
- Action - 动作
- Command - 命令
- Service - 服务
- Option - 选项
该语言规定,Action和Service是以一种“小节”(Section)的形式出现的,其中每个Action小节可以含有若干Command,而每个Service小节可以含有若干Option。小节只有起始标记,却没有明确的结束标记,也就是说,是用“后一个小节”的起始来结束“前一个小节”的。
脚本中的Action大体上表示一个“动作”,它用一系列Command共同完成该“动作”。Action需要有一个触发器(trigger)来触发它,一旦满足了触发条件,这个Action就会被加到执行队列的末尾。Action的形式如下:
1
2
3
4
on
......
Service表示一个服务程序,会在初始化时启动,当服务退出时init进程会视情况重启服务。因为init.rc脚本中描述的服务往往都是核心服务,所以(基本上所有的)服务会在退出时自动重启。Service的形式如下:
1
2
3
4
service []*