前言
作为一个 Android 应用开发者,是否想过当你按下 Android 电源键打开设备,从硬件到软件会发生什么?是否会有以下疑问:
- 当设备开机动画出现时,屏幕后面到底发生了什么?
- 手机或者汽车等设备究竟如何加载、引导并启动 Android 操作系统?
接下来的文章将会通过图文结合的方式来探索 Android 操作系统的启动流程。
Android 启动流程
每次 Android 设备开机时都会经过一次完整的启动流程。
大致可分为 6 大步骤:
- Boot ROM 代码执行
- Bootloader 程序执行
- Linux 内核启动
- Init 进程启动
- Zygote 进程 和 Dalvik VM 虚拟机启动
- System Servers 和 Service Manager 启动
Boot ROM 代码执行
Android 设备上电之后,首先会从 SoC 上 Boot ROM 的启动引导代码开始执行。在此阶段会发生以下两件事:
- 初始化设备硬件并尝试扫描和检测引导介质。这块代码是固写在 Boot ROM 中无法改变的,类似于计算机启动过程中的 BIOS 功能。
- 将初始的引导程序 Bootloader 复制到内部 RAM。
Bootloader 程序执行
Bootloader 是在 Android 系统启动前运行的一个微型程序,但它并不属于 Android 操作系统。OEM 通常会在该程序中设置锁定和限制。
Bootloader 程序存在于台式计算机、笔记本电脑和移动设备中,并且它们的功能相似。在 Android 设备上,引导加载程序分为两个阶段:
- Initial Program Load (IPL)
- Second Program Load (SPL)
Initial Program Load (IPL)
IPL 负责检测能够进行 SPL 的外部 RAM。一旦检测到有外部 RAM 可用,SPL 就会被复制到该 RAM 中执行。
Second Program Load (SPL)
SPL 负责加载主 Android 操作系统并提供对其他启动模式的访问,例如fastboot
和recovery
。SPL 还会初始化多个硬件组件,例如显示器、键盘、文件系统、虚拟内存、控制台和其他功能。此后,SPL 会尝试查找 Linux 内核。内核会在启动介质中被找到的,然后将其复制到 RAM。此后,SPL 会将执行转移到内核。
Linux 内核
Android 内核的启动方式与 Linux 内核类似。当内核启动时,它首先会进行一系列系统设置:缓存、受保护的内存、调度和加载驱动程序。当内核完成系统设置后,接下来就会在系统文件中查找init
。
然而, Android 内核与 Linux 内核也有一些区别。以下是 Android 内核中新增定制的部分:
- Binder: Android 特有的进程间通信机制和远程方法调用系统。
- ASHMEM: Android 共享内存,类似于 POSIX SHM,但具有不同的行为并具有更简单的基于文件的 API。
- PMEM: 进程内存分配器,用于管理用户空间和内核驱动程序之间共享的大型(1-16+ MB)物理连续内存区域。
- Logger: 内核对 logcat 命令的支持。
- Paranoid Networking: 根据呼叫进程组限制对某些网络功能的访问。
- Wake locks: 应用程序在手机空闲时保持 CPU/屏幕/其他设备处于唤醒状态以执行特定后台任务的方法。通常用于电源管理文件。
- OOM Handling: 内存管理模块,用以检查系统是否有足够的内存可用于执行任务,验证系统何时内存不足,并终止进程以释放内存。
- Alarm Manager: 允许用户空间在想要唤醒时与内核进行通信。
- Timed output / Timed GPIO: GPIO 是一种允许程序从用户空间访问和操作 GPIO 寄存器的机制。
- RAM Console: RAM 中在启动时保留的区域。该区域是拥有持久属性,用于在内核重新启动或崩溃时存储最后的内核日志消息。此处存储的日志对于内核调试非常有用,可以在崩溃或意外重启之前立即深入了解内核进程。在
/proc/last_kmsg
中查看。 - USB Gadget Driver for ADB
- yaffs2 flash filesystem
- 等...
Init Process
Init
是 Android 系统的第一个进程,又称为根进程,是所有其他进程的父进程。Init 进程主要有两个职责。
- 挂载目录,如
/sys
,/dev
或/proc
- 解析运行
init.rc
脚本
init
进程在:/system/core/init
init.rc
文件在:/system/core/rootdir/
Android 对于init.rc
文件的解释和执行有一套特定规则,会在后续文章中详细介绍。
启动流程到了这个阶段,终于可以在设备屏幕上看到 Android Logo 了。
Zygote 和 Dalvik
在 Java 中,会为每一个应用程序在内存中分配出单独的虚拟机实例。而 Android 的 Dalvik(虚拟机)有所不同,因为它需要尽可能快。这就带来了一个问题。
如果有多个应用程序启动了多个 Dalvik 实例,导致可用内存耗尽,会发生什么?
Android 通过一个名为 Zygote 的系统解决了这个问题。
Zygote 是设备启动后 init 进程最先创建的进程之一。Zygote 支持跨 Dalvik VM 共享代码,从而实现更低的内存占用和最短的启动时间。它是一个虚拟机进程,在系统启动时启动,并尝试创建多个实例来支持每个 Android 进程。Zygote 会预加载并初始化核心库类。
Zygote 加载过程
-
Load
ZygoteInitclass
:加载 ZygoteInit 类。源码:
/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
-
registerZygoteSocket()
:为 zygote 注册一个服务器套接字,供其他客户端建立跨进程连接。 -
preloadClasses()
:这是一个简单的文本文件,其中包含需要预加载的类列表。文件位置:
/frameworks/base
-
preloadResources()
资源文件,包括 android.R 文件的所有内容都将使用此方法加载:
启动流程到了这个阶段,设备屏幕将会显示启动动画。
System service
Zygote 进程会先 fork 出 SystemServer 进程,然后 SystemServer 进程负责将所有 Android 核心服务启动起来。
注:这些 Android 服务并没有各自运行在独立的进程中,它们由 SystemServer 以线程的方式创建,所以都运行在同一个进程中,即 SystemServer 进程,并由它管理。
至此系统启动流程已完结。
此时系统将发送一个名为ACTION_BOOT_COMPLETED
的广播,通知所有相关进程 boot process 已完成。
设备会显示 Home 主屏幕并准备好与用户交互。