安卓启动流程浅析

安卓启动流程

先总的从下面这个图中来学习一下安卓的启动流程吧

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 权限后代为修改。
    • 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 booton early-init
    • 理解:它定义了"什么时候干活"。当系统状态达到触发器要求时,它下面的命令就会被依次执行
  • Commands
    • Action 下面缩进的一行行指令
    • 例子: mount 挂载, chmod 改权限, mkdir 建文件夹, write 往文件写值
    • 理解:它定义了"具体干什么活"
  • Services
    • 语法:以 service 开头,后跟服务名和执行程序路径
    • 例子: service zygote /system/bin/cpp/app_process...
    • 理解:它定义了"谁来干活",这些服务通常是常驻后台的守护进程
  • Options
    • 语法: Service 下面所进的修饰符
    • 例子:
      • class main:把服务归类到"主服务"
      • user system:以系统用户身份运行
      • critical:如果这个服务挂了,系统必须立刻重启进入 Recovery
      • onrestart:当服务重启时,顺便把别的服务也重启了 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 环境,没有任何手机功能
下面总结了一些常见的服务:

服务名称简称核心职责例子
ActivityManagerServiceAMS管理组件生命周期(启动,停止)、进程调度、权限审批当你从"微信"跳转到"朋友圈"时,是它在切换界面并保持后台运行
WindowManagerServiceWMS管理窗口位置,大小,层级(谁在上面),转场动画当你开启"小窗模式"或者看到 App 打开时的缩放动画时,是它在计算位置
PackageManagerServicePKMS管理 App 安装,卸载,解析 AndroidManifet.xml,权限检查当你安装一个 APK 时,是它在检查报名是否冲突,签名是否合法
PowerManagerServicePMS管理屏幕亮度,待机状态,电源策略(省电模式)当你半分钟不操作,屏幕慢慢变暗,是它在下达指令
InputManagerServiceIMS接收触摸屏,物理按键的原始信号,并配合 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 分配好空间,画面呈现

至此,安卓启动完成

从源码角度进行分析

参考文章

Android 操作系统架构开篇 - Gityuan博客 | 袁辉辉的技术博客