安卓启动流程浅析
安卓启动流程
先总的从下面这个图中来学习一下安卓的启动流程吧
Bootloader 引导阶段
当按下电源键后,芯片从固化在 ROM(Read-Only Memory)中预设的(BOOT ROM)开始执行,这个阶段是纯硬件操作,代码负责加载引导程序 BootLoader 到 RAM(Random Access Memory)中
BootLoader 是操作系统运行之前的一小段程序,类似于 PC 上的 BIOS,主要功能大致有:
- 第一,检测并初始化硬件设备:如外部 RAM,时钟,主板等,为内核运行准备一个基本的硬件环境
- 第二,拉起 Linux 内核:将内核映像加载到指定的内存地址,然后将系统控制权交给内核
- 第三,安全验证:检查内核的数字签名(厂商通常会锁定 Bootloader)
Linux 内核启动阶段
安卓系统主要是运行再 Linux 内核之上的,在这里主要可以将在内核阶段做的事情分成 3 个阶段:
0 号进程:swapper(创世神)
这是整个 Android 系统的起点
- 诞生:它是整个 Android 系统中唯一不被 fork 出来的进程,由内核代码直接创建的
- 核心任务(初始化):
- 执行
start_kernel():这个是内核执行的第一个 C 语言函数,他会初始化中断处理,内存页表映射,进程调度策略等 - 点亮关键硬件:在这一阶段,内核会根据设备树(Device Tree)加载最核心的驱动:显示(Display),相机(Camera)以及 Android 的通信命脉–Binder 驱动
- 执行
- 结果:当 1 号进程和 2 号进程启动后,0 号进程就会变成 idle 进程(0 号进程的任务已经完成了,接下来就是 1 号和 2 号进程的事情了,但是又不能杀死 0 号进程,这个时候就把 0 号进程变成 idle 进程,变成低功耗状态)
2 号进程:kthreadd(内核态鼻祖)
在 0 号进程执行的过程中,它会调用 rest_init() 来孵化出 2 号进程
- 身份:它是所有内核线程的鼻祖
- 核心任务:
- 运行在内核态:它负责管理那些不需要用户参与,直接在后台与硬件打交道的任务
- 孵化线程:
kworker:内核的"勤杂工"(奴隶),处理各种异步 IO 任务ksoftirqd:专门负责处理软中断,保证系统在高负载下的相应速度thermal:温度守护进程,负责实时监控 CPU 温度并执行强制降频,防止手机被烧坏
注:什么是软中断呢?那硬中断是什么意思呢?
硬中断:当硬件(如网卡,触摸屏)产生信号时,CPU 会立即停下手里的活取处理,这就叫做硬中断
- 特点:优先级最高,执行时会屏蔽其他中断
- 问题:如果硬中断处理时间太长,系统就会卡死,因为它优先级高,没法干其他事情,为了解决这个问题,Linux 就把中断处理分成了两半
- 上半部分:硬中断:
- 只做最紧急的事情,干完赶紧走
- 下半部分:软中断
- 处理耗时其不那么紧急的事情,可以被抢占,不影响系统接收新的硬中断
因为硬中断是立即做的事情,软中断延后,如果在处理硬中断任务的时候,接收到了很多的软中断任务,软中断任务越堆越多,这个时候ksoftirqd线程就是专门用来管理这些软中断任务的
- 处理耗时其不那么紧急的事情,可以被抢占,不影响系统接收新的硬中断
- 上半部分:硬中断:
1 号进程:init(用户态鼻祖)
这个是 0 号进程孵化出来的另一个进程
- 身份:所有用户空间进程的鼻祖
- 核心任务:
- 权限切换:内核通过
run_init_process试图在根分区寻找 init 文件,一旦找到,代码执行权立即从用户态(Kernel Space)切换到用户态(User Space) - 解析
init.rc:进入用户态后,init进程会化身为一个"配置解析器",它会根据脚本里的指令,一个接一个地启动我们在桌面上能看到的那些服务
- 权限切换:内核通过
init 进程初始化阶段
init 进程的三个阶段
第一阶段:FirstStageMain 基础挂载
内核刚刚把控制权交给 init 进程,此时系统还很简陋,init 需要做一些工作
- 挂载文件系统:挂载
/dev,/proc,/sys等虚拟文件系统,让进程能够访问硬件设备和内核信息。 - 加载 First Stage Console:初始化早期的日志输出,这样如果出错了,开发者才能够在串口看到 log
下面来详细解释一下
/dev,/proc,/sys这三个虚拟文件系统: /dev: 设备驱动的入口- 作用:在 Linux 中,“万物皆文件”,我们常常访问手机的屏幕,摄像头或是 Binder,都得通过这个目录
- 在启动中的角色:init 进程会在这里创建设备的节点。比如,没有
/dev/binder这个文件,Android 的所有进程通信都得瘫痪;没有/dev/graphics/fb0,屏幕就没法显示图像
/proc:内核与进程信息的实时快照- 作用:它让我们能"窥探"内核当前的运行状态
- 常见操作:
/proc/cpuinfo:查看 CPU 有几个核/proc/meminfo:查看当前内存还剩多少/proc/[pid]/stack:查看某个进程现在的调用栈
/sys:硬件设备树的结构化体现- 作用:如果说
/dev是为了设备的读写数据,那/sys就是为了修改配置 - 在启动中的角色:init 会通过往
/sys里的文件写"1"或"0"来控制硬件- 往
/sys/class/leds/red/brightness写一个数字,手机的呼吸灯就亮了 - 控制 CPU 的频率,调节屏幕亮度,都是通过这个目录来完成的
第二阶段:SetupSelinux 安全加固
Android 是极其安全的系统,这一步是给系统"上锁"
- 往
- 作用:如果说
- 加载
SELinux策略:定义那些进程可以访问哪些文件。 - 权限转交:一旦策略加载完成,init 就会重新执行自己,进入受安全保护的第二阶段
第三阶段:SecondStageMain 配置解析
这是我们最常讨论的 init 核心逻辑: - 初始化属性服务:启动
Property Service,可以把它理解为"全局变量数据库"- Q1:它是做什么的?
- A1:在安卓系统中,很多配置信息 (比如手机型号,系统版本,当前网络状态)需要在不同的进程之间共享
- 存储键值对:它以
key=value的形式存储信息(例如:ro.product.model=pixel 6) - 跨进程访问:无论是在 C++层还是 Java 层,所有的进程都可以读取这些属性,从而了解系统当前的状况
- 存储键值对:它以
- Q2:它是如何工作的?
- A2:属性服务在 init 进程的第二阶段(SecondStageMain)启动。
- 内存共享:init 进程会开辟一段特殊的共享内存(称为
__system_property_area__)来存放这些属性 - 权限控制:
- 读取:任何进程都可以读取属性。
- 写入:普通进程不能直接修改共享内存。如果一个进程想修改属性,必须向 init 进程发送请求,由 init 检查
SELinux权限后代为修改。
- 内存共享:init 进程会开辟一段特殊的共享内存(称为
- Q3:这些属性是如何命名的呢?
- A3:在 Android 中,属性的名字是有特殊含义的
ro.(Read-Only):只读属性,一旦设定,除非重启系统,否则无法修改。例如:ro.build.version.release(系统版本)persist.:持久化属性。修改后会存入硬盘,下次开机依然有效。例如:persist.sys.timezone(用户设置的时区)ctl(Control):控制属性。用于启动或停止服务,例如:setprop ctl.start zygote(让 init 启动 zygote)- 其他:临时属性,关机即消失,例如:
gsm.operator.alpha(运营商名称)
- 解析
init.rc:这是 init 的灵魂,它会根据脚本指令去拉起所有系统服务
核心任务:解析 init.rc 脚本
1.什么是.rc 文件rc 代表 Run Commands(运行命令) 。这是一种从 Unix 时代传承下来的习惯,专门用于存放系统启动时需要自动执行的脚本。在 Android 中,它采用了一种特殊的文本格式,易于阅读但也非常严谨
2.init.rc 的四大核心组成部分
Actions- 语法:以
on开头,后跟一个 Trigger(触发器) - 例子:
on boot或on early-init - 理解:它定义了"什么时候干活"。当系统状态达到触发器要求时,它下面的命令就会被依次执行
- 语法:以
CommandsAction下面缩进的一行行指令- 例子:
mount挂载,chmod改权限,mkdir建文件夹,write往文件写值 - 理解:它定义了"具体干什么活"
Services- 语法:以
service开头,后跟服务名和执行程序路径 - 例子:
service zygote /system/bin/cpp/app_process... - 理解:它定义了"谁来干活",这些服务通常是常驻后台的守护进程
- 语法:以
Options- 语法:
Service下面所进的修饰符 - 例子:
class main:把服务归类到"主服务"user system:以系统用户身份运行critical:如果这个服务挂了,系统必须立刻重启进入Recoveryonrestart:当服务重启时,顺便把别的服务也重启了 3.解析的流程
在源码中,init并不是简单地从头读到尾,它有一个解析=>排序=>执行的过程
- 语法:
- 解析文件:
init进程会递归读取/system/etc/init/,/vendor/etc/init/等目录下所有的.rc文件,并将他们加载到内存 - 分装
Action:将所有的 Action 按照触发器的时间顺序排好队 - 启动
Service:当某个 Action 执行了class_start main命令时,属于main类的所有服务(如 zygote)才会排队启动
Zygote 进程启动阶段
Q1:为什么叫做 Zygote(受精卵)?
在 Android 中,几乎所有的应用程序进程以及 SystemServer 进程,都是由 Zygote 进程 fork 出来的
为什么要 fork 呢?
如果每个 APP 启动都要重新加载一遍基础类库和资源,手机会卡死,而如果 Zygote 预先加载好了这些东西,新进程只需要"复制"一份,效率贼高
Q2:Zygote 是如何被拉起来的?
在之前分析的过程中,init 进程解析到 init.rc 文件的时候,会有一个语句 service zygote /system/bin/app_process ...
- App_main.cpp 启动:首先运行的是 C++编写的
app_process程序 - 创建
AndroidRuntime:它会启动Android运行时环境 - 启动虚拟机(
ART/Dalvik):这是关键的一步,Zygote亲手打开了 Java 虚拟机
Q3:Zygote 启动后干了啥?
当启动后,它主要会干一下的几件事情
1.预加载资源
Zygote 会把上千个常用的 Java 类(如 Activity, view)和系统资源(如图标,主题)加载到内存里
2.启动 SystemServer
这是 Zygote 孵化的第一个孩子
地位: SystemServer 进程是整个 Android 框架的重心,管理着 ActivityManagerService(AMS),WindowManagerService(WMS) 等核心服务
关系:没有这个孩子,Android 只是一个空的 Java 环境,没有任何手机功能
下面总结了一些常见的服务:
| 服务名称 | 简称 | 核心职责 | 例子 |
|---|---|---|---|
| ActivityManagerService | AMS | 管理组件生命周期(启动,停止)、进程调度、权限审批 | 当你从"微信"跳转到"朋友圈"时,是它在切换界面并保持后台运行 |
| WindowManagerService | WMS | 管理窗口位置,大小,层级(谁在上面),转场动画 | 当你开启"小窗模式"或者看到 App 打开时的缩放动画时,是它在计算位置 |
| PackageManagerService | PKMS | 管理 App 安装,卸载,解析 AndroidManifet.xml,权限检查 | 当你安装一个 APK 时,是它在检查报名是否冲突,签名是否合法 |
| PowerManagerService | PMS | 管理屏幕亮度,待机状态,电源策略(省电模式) | 当你半分钟不操作,屏幕慢慢变暗,是它在下达指令 |
| InputManagerService | IMS | 接收触摸屏,物理按键的原始信号,并配合 WMS 进行分发 | 当你点击屏幕上的"确认"按钮,是它最先捕捉到信号并判断你点击的是哪个地方 |
3.建立 Socket
Zygote 会创建一个 LocalServerSocket
它会从此进入死循环,开始待命。当 AMS 想要启动一个新的 App 时,会通过这个 Socket 给 Zygote 发消息,Zygote 收到消息之后,会立即 fork 出一个新的进程给 App 使用
SystemServer 阶段
SystemServer 启动后,它的 run() 方法会像一个精密的流水线,依次点名启动 80 多个 系统服务。这些服务被归类为三大步:
引导服务
首先会启动系统运行必不可少的底层服务
- ActivityManagerService(AMS):管理四大组件和进程
- PackageManagerService(PKMS):解析所有已安装的 App 信息
- PowerManagerService(PMS):电源管理
核心服务
这些服务虽然不是第一优先级的,但是后续的所有交互都会依赖他们
- BatteryService:监控电量
- UsageStatsService:统计应用使用情况
其他服务
启动各种与交互相关的复杂服务
- WMS (WindowManagerService):管理屏幕上的窗口。
- IMS (InputManagerService):处理触摸和按键。
- 蓝牙、Wifi、振动服务等。
所有服务启动完毕后,调用 ActivityManagerService.systemReady(),标志着系统核心准备就绪。此后,AMS 会发送 BOOT_COMPLETED 广播,并启动桌面 Launcher,完成开机。
Launcher 启动阶段
当所有服务都已经初始化完成了,最后就会启动 launcher 了,也就是平常的桌面 app,当在启动这个桌面 app 的过程中,依然会走一遍 App 启动的标准流程:
- AMS 决策:AMS 发现桌面进程还没启动,于是再次求助 Zygote
- Zygote 孵化:Zygote 收到指令,
fork出一个新的进程给 Launcher - 加载页面:Launcher 进程启动后,开始加载手机上所有安装好的 app 图标
- WMS 渲染:Launcher 告诉 WMS,“我需要一个空间”,WMS 分配好空间,画面呈现
至此,安卓启动完成