Android系统启动的大概流程
第一步: 启动电源以及系统启动
当电源按下,引导芯片代码开始从预定义的地方(固化在ROM)开始执行。加载引导程序到RAM,然后执行
第二步:引导程序
引导程序是在Android操作系统开始运行前的一个小程序。引导程序是运行的第一个程序,因此它是针对特定的主板与芯片的。设备制造商要么使用很受欢迎的引导程序比如redboot、uboot、qi bootloader或者开发自己的引导程序,它不是Android操作系统的一部分。引导程序是OEM厂商或者运营商加锁和限制的地方。
引导程序分两个阶段执行。
第一个阶段,检测外部的RAM以及加载对第二阶段有用的程序;
第二阶段,引导程序设置网络、内存等等。这些对于运行内核是必要的,为了达到特殊的目标,引导程序可以根据配置参数或者输入数据设置内核。
Android引导程序可以在\bootable\bootloader\legacy\usbloader找到。传统的加载器包含两个文件, 需要在这里说明:
init.s初始化堆栈,清零BBS段,调用main.c的_main()函数;
main.c初始化硬件(闹钟、主板、键盘、控制台),创建linux标签
第三步:内核
Android内核与桌面linux内核启动的方式差不多。内核启动时,设置缓存、被保护存储器、计划列表, 加载驱动。当内核完成系统设置,它首先在系统文件中寻找”init”文件,然后启动root进程或者系统的第 一个进程
第四步:init进程
init进程是Linux系统中用户空间的第一个进程,进程号固定为1。Kernel启动后,在用户空间启动init进 程,并调用init中的main()方法执行init进程的职责。
第五步:启动系统服务,Lancher App
init进程分析
其中init进程是Android系统中及其重要的第一个进程,接下来看下init进程主要做了些什么
- 创建和挂载启动所需要的文件目录
- 初始化和启动属性服务
- 解析init.rc配置文件并启动Zygote进程
1 | // \system\core\init\init.cpp main() |
init.rc解析
init.rc是一个非常重要的配置文件,它是由Android初始化语言(Android Init Language)编写的脚 本,它主要包含五种类型语句:Action(Action中包含了一系列的Command)、Commands(init语言 中的命令)、Services(由init进程启动的服务)、Options(对服务进行配置的选项)和Import(引入 其他配置文件)。init.rc的配置代码如下所示。
1 | # \system\core\rootdir\init.rc |
Action
Action: 通过触发器trigger,即以on开头的语句来决定执行相应的service的时机,具体有如下时机:
- on early-init; 在初始化早期阶段触发;
- on init; 在初始化阶段触发;
- on late-init; 在初始化晚期阶段触发;
- on boot/charger: 当系统启动/充电时触发,还包含其他情况;
- on property:=: 当属性值满足条件时触发
Service
服务Service,以 service开头,由init进程启动,一般运行在init的一个子进程,所以启动service前需要判断对应的可执行文件是否存在。init生成的子进程,定义在rc文件,其中每一个service在启动时会通过 fork方式生成子进程。
例如: service servicemanager /system/bin/servicemanager代表的是服务名为 servicemanager,服务执行的路径为/system/bin/servicemanager。
Command
常用的命令
- class_start : 启动属于同一个class的所有服务;
- start : 启动指定的服务,若已启动则跳过;
- stop : 停止正在运行的服务
- setprop :设置属性值
- mkdir :创建指定目录
- symlink : 创建连接到的符号链接;
- write : 向文件path中写入字符串;
- exec: fork并执行,会阻塞init进程直到程序完毕;
- exprot :设定环境变量;
- loglevel :设置log级别
Options
Options是Service的可选项,与service配合使用
- disabled: 不随class自动启动,只有根据service名才启动;
- oneshot: service退出后不再重启;
- user/group: 设置执行服务的用户/用户组,默认都是root;
- class:设置所属的类名,当所属类启动/退出时,服务也启动/停止,默认为default;
- onrestart:当服务重启时执行相应命令;
- socket: 创建名为/dev/socket/的socket
- critical: 在规定时间内该service不断重启,则系统会重启并进入恢复模式
default: 意味着disabled=false,oneshot=false,critical=false。
service解析流程
1 | // \system\core\init\init.cpp LoadBootScripts() |
解析完成后,接下来就是启动Service,以启动Zygote来分析
1 | # \system\core\rootdir\init.rc |
Zygote
主要用于孵化子进程。在Android系统中有以下两种程序:
- java应用程序,主要基于ART虚拟机,所有的应用程序apk都属于这类
- native程序,也就是利用C或C++语 言开发的程序,如bootanimation。
所有的Java应用程序进程及系统服务SystemServer进程都由Zygote 进程通过Linux的fork()函数孵化出来的,这也就是为什么把它称为Zygote的原因。
Zygote进程最初的名字不是“zygote”而是 “app_process”,这个名字是在Android.mk文件中定义的
Zgyote是Android中的第一个art虚拟机,他通过socket的方式与其他进程进行通信。这里的“其他进程” 其实主要是系统进程——SystemServer
1 | Zygote是一个C/S模型,Zygote进程作为服务端,它主要负责创建Java虚拟机,加载系统资源,启 动SystemServer进程,以及在后续运行过程中启动普通的应用程序,其他进程作为客户端向它发 出“孵化”请求,而Zygote接收到这个请求后就“孵化”出一个新的进程。比如,当点击Launcher里的 应用程序图标去启动一个新的应用程序进程时,这个请求会到达框架层的核心服务 ActivityManagerService中,当AMS收到这个请求后,它通过调用Process类发出一个“孵化”子进 程的Socket请求,而Zygote监听到这个请求后就立刻fork一个新的进程出来。 |
Zygote触发过程
1、init zygote
1 | import /init.${ro.zygote}.rc |
init.zygoteXX.rc ${ro.zygote} 会被替换成 ro.zyogte 的属性值,这个是由不同的硬件厂商自己定制的, 有四个值,
- zygote32: zygote 进程对应的执行程序是 app_process (纯 32bit 模式)
- zygote64: zygote 进程对应的执行程序是 app_process64 (纯 64bit 模式)
- zygote32_64: 启动两个 zygote 进程 (名为 zygote 和 zygote_secondary),对应的执行程序分别 是 app_process32 (主模式)
- zygote64_32: 启动两个 zygote 进程 (名为 zygote 和 zygote_secondary),对应的执行程序分别 是 app_process64 (主模式)、app_process32
1 | //\system\core\rootdir\init.zygote64.rc |
2、start zygote 位置:system\core\rootdir\init.rc
1 | on zygote-start && property:ro.crypto.state=encrypted && property:ro.crypto.type=file |
1 | // \system\core\init\init.cpp |
3、app_processXX 位置\frameworks\base\cmds\app_process\
1 | app_process_src_files := \ |
Zygote启动过程
位置\frameworks\base\cmds\app_process\app_main.cpp
在app_main.cpp的main函数中,主要做的事情就是参数解析. 这个函数有两种启动模式:
- 一种是zygote模式,也就是初始化zygote进程,传递的参数有–start-system-server –socket name=zygote,前者表示启动SystemServer,后者指定socket的名称
- 一种是application模式,也就是启动普通应用程序,传递的参数有class名字以及class带的参数 两者最终都是调用AppRuntime对象的start函数,加载ZygoteInit或RuntimeInit两个Java类,并将之前整理的参数传入进去
1 | graph LR |
app_process
1 | // \frameworks\base\cmds\app_process\app_main.cpp main() |
app_process 里面定义了三种应用程序类型:
- Zygote: com.android.internal.os.ZygoteInit
- System Server, 不单独启动,而是由Zygote启动
- 其他指定类名的Java 程序
什么是Runtime
Runtime 是支撑程序运行的基础库,它是与语言绑定在一起的。比如:
C Runtime:就是C standard lib, 也就是常说的libc。
Java Runtime: 包括Java 的支撑类库 (.jar).
AndroidRuntime: 为Android应用运行所需的运行时环境。这个环境包括以下内容:
- Dalvik/ART VM: Android的Java VM, 解释运行Dex格式Java程序。每个进程运行一个虚拟机。
- Android的Java 类库, 开源的Java API 实现,如 java.lang, java.util, java.net. 但去除了AWT, Swing 等部件。
- JNI: C和Java互调的接口。
- Libc: Android也有很多C代码,自然少不了libc,注意的是,Android的libc叫 bionic C
1 | // \frameworks\base\core\jni\androidRuntime.cpp start() |
Java虚拟机的启动大致做了以下一些事情:
从property读取一系列启动参数。
创建和初始化结构体全局对象(每个进程)gDVM,及对应与JavaVM和JNIEnv的内部结构体 JavaVMExt, JNIEnvExt.
初始化java虚拟机,并创建虚拟机线程
注册系统的JNI,Java程序通过这些JNI接口来访问底层的资源。 (”javacore”, “nativehelper”)
为Zygote的启动做最后的准备,包括设置SID/UID, 以及mount 文件系统
返回JavaVM 给Native代码,这样它就可以向上访问Java的接口
除了系统的JNI接口(”javacore”, “nativehelper”), android framework 还有大量的Native实现, Android将所有这些接口一次性的通过start_reg()来完成
ZygotInit
1 | // \frameworks\base\core\java\com\android\internal\os\ZygotInit.java main() |
preload() 的作用就是提前将需要的资源加载到VM中,比如class、resource等
1 | // \frameworks\base\core\java\com\android\internal\os\ZygotInit.java preload() |
preloadClassess 将framework.jar里的preloaded-classes 定义的所有class load到内存里, preloaded-classes 编译Android后可以在framework/base下找到。
preloadResources 将系统的 Resource(不是在用户apk里定义的resource)load到内存。资源preload到Zygoted的进程地址空间, 所有fork的子进程将共享这份空间而无需重新load, 这大大减少了应用程序的启动时间,但反过来增加了 系统的启动时间。通过对preload 类和资源数目进行调整可以加快系统启动。Preload也是Android启动 最耗时的部分之一。
ZygoteInit.forkSystemServer() 方法fork 出一个新的进程,这个进程就是SystemServer进程。fork出来 的子进程在handleSystemServerProcess 里开始初始化工作,主要工作分为:
- prepareSystemServerProfile()方法中将SYSTEMSERVERCLASSPATH中的AppInfo加载到VM 中。
- 判断fork args中是否有invokWith参数,如果有则进行WrapperInit.execApplication,如果没有则调用调用ZygoteInit.zygoteInit
1 | \frameworks\base\core\java\com\android\internal\os\ZygotInit.java |
1 | // \frameworks\base\core\java\com\android\internal\os\RuntimeInit.java applicationInit() |
接着在MethodAndArgsCaller的run方法中执行SystemServer的main方法。
System Server 启动流程
System Server是Zygote fork 的第一个Java 进程, 这个进程非常重要,因为他们有很多的系统线程, 提供所有核心的系统服务。还有很多“Binder-x”的线程,它们是各个Service为了响应应用程序远程调用请求而创建的。除此之外,还有很多内部的线程,比如 ”UI thread”, “InputReader”, “InputDispatch” 等等,现在只关心System Server是如何创建起来的。 SystemServer的main() 函数
1 | public static void main(String[] args) { |
SystemServer run方法的初始化流程:
- 初始化必要的SystemServer环境参数,比如系统时间、默认时区、语言、load一些Library等等,
- 初始化Looper,我们在主线程中使用到的looper就是在SystemServer中进行初始化的
- 初始化Context,只有初始化一个Context才能进行启动Service等操作
- 初始化SystemServiceManager,用来管理启动service,SystemServiceManager中封装了启动Service的 startService方法启动系统必要的Service,启动service的流程
初始化Context
1 | private void createSystemContext() { |
初始化SystemServiceManager
1 | //SystemServer Start services. |
- 启动BootstrapServices,就是系统必须需要的服务,这些服务直接耦合性很高,所以干脆就放在一个方 法里面一起启动,比如PowerManagerService、RecoverySystemService、DisplayManagerService、 ActivityManagerService等
- 启动以基本的核心Service,比如BatteryService、 UsageStatsService、WebViewUpdateService等
- 启动其它需要用到的Service,比如 NetworkScoreService、AlarmManagerService等
最后Zygote会监控System Server是否挂掉了,如果挂掉会将其回收,然后将自己杀掉,重新开始,这段实现在代码 :dalvik/vm/native/dalvik_system_zygote.cpp Dalvik_dalvik_system_Zygote_forkSystemServer中。
Zygote和SystemServer 总结
- init 根据init.rc 运行 app_process, 并携带‘–zygote’ 和 ’–startSystemServer’ 参数。
- AndroidRuntime.cpp::start() 里将启动JavaVM,并且注册所有framework相关的系统JNI接口。
- 第一次进入Java世界,运行ZygoteInit.java::main() 函数初始化Zygote. Zygote 并创建Socket的 server 端。
- 然后fork一个新的进程并在新进程里初始化SystemServer. Fork之前,Zygote是preload常用的 Java类库,以及系统的resources,同时GC()清理内存空间,为子进程省去重复的工作。
- SystemServer 里将所有的系统Service初始化,包括ActivityManager 和 WindowManager, 他们 是应用程序运行起来的前提。
- 于此同时,Zygote监听服务端Socket,等待新的应用启动请求。
- ActivityManager ready 之后寻找系统的“Startup” Application, 将请求发给Zygote。
- Zygote收到请求后,fork出一个新的进程。
- Zygote监听并处理SystemServer 的 SIGCHID 信号,一旦System Server崩溃,立即将自己杀死。 init会重启Zygote。
什么情况下Zygote进程会重启呢?
- servicemanager进程被杀;
- (onresart)surfaceflinger进程被杀;
- (onresart)Zygote进程自己被杀;
- (oneshot=false)system_server进程被杀; (waitpid)
fork函数
使用 f ork() 函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间:包括进程上下文(进程执行活动全过程的静态描述)、进程堆栈、打开的文件描述符、信号控 制设定、进程优先级、进程组号等。子进程所独有的只有它的进程号,计时器等(只有小量信 息)。因此,使用 fork() 函数的代价是很大的。
子进程与父进程的区别
- 除了文件锁以外,其他的锁都会被继承
- 各自的进程ID和父进程ID不同
- 子进程的未决告警被清除;
- 子进程的未决信号集设置为空集。
写时拷贝 (copy- on-write)
Linux 的 fork() 使用是通过写时拷贝 (copy- on-write) 实现。写时拷贝是一种可以推迟甚至避免拷贝数据的技术。内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。只用在需要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间。也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。
孤儿进程、僵尸进程
fork系统调用之后,父子进程将交替执行,执行顺序不定。如果父进程先退出,子进程还没退出那么子进程的父进程将变为init进程(托孤给了init进程)。(注:任何一个进程都必须有父进程)如果子进程先退出,父进程还没退出,那么子进程必须等到父进程捕获到了子进程的退出状态才真正结束,否则这个时候子进程就成为僵进程(僵尸进程:只保留一些退出信息供父进程查询)。
多线程进程的Fork调用
在 POSIX 标准中,fork 的行为是这样的:复制整个用户空间的数据(通常使用 copy-on-write 的策略, 所以可以实现的速度很快)以及所有系统对象,然后仅复制当前线程到子进程。这里:所有父进程中别的线程,到了子进程中都是突然蒸发掉的
1 | 假设这么一个环境,在 fork 之前,有一个子线程 lock 了某个锁,获得了对锁的所有权。fork 以后,在子进程中,所有的额外线程都人间蒸发了。而锁却被正常复制了,在子进程看来,这个锁没有主人,所以没有任何人可以对它解锁。当子进程想lock这个锁时,不再有任何手段可以解开了。程序发生死锁。 |
面试题
Q:你了解 Android 系统启动流程吗?
A:当按电源键触发开机,首先会从 ROM 中预定义的地方加载引导程序 BootLoader 到 RAM 中,并执 行 BootLoader 程序启动 Linux Kernel,然后启动用户级别的第一个进程:init 进程。init 进程会解析 init.rc 脚本做一些初始化工作,包括挂载文件系统、创建工作目录以及启动系统服务进程等,其中系统服务进程包括 Zygote、service manager、media 等。在 Zygote 中会进一步去启动 system_server 进 程,然后在 system_server 进程中会启动 AMS、WMS、PMS 等服务,等这些服务启动之后,AMS 中就 会打开 Launcher 应用的 home Activity,最终就看到了手机的 “桌面”。
Q:system_server为什么要在 Zygote 中启动,而不是由 init 直接启动呢?
A:Zygote作为一个孵化器,可以提前加载一些资源,这样fork() 时基于 Copy-On-Write 机制创建的其他进程就能直接使用这些资源,而不用重新加载。比如 system_server 就可以直接使用 Zygote 中的 JNI 函数、共享库、常用的类、以及主题资源。
Q:为什么要专门使用 Zygote 进程去孵化应用进程,而不是让system_server去孵化呢?
A:首先 system_server相比 Zygote 多运行了 AMS、WMS 等服务,这些对一个应用程序来说是不需要的。另外进程的 fork() 对多线程不友好,仅会将发起调用的线程拷贝到子进程,这可能会导致死锁,而 system_server 中肯定是有很多线程的。
Q:能说说具体是怎么导致死锁的吗?
A:在 POSIX 标准中,fork 的行为是这样的:复制整个用户空间的数据(通常使用 copy-on-write 的策略, 所以可以实现的速度很快)以及所有系统对象,然后仅复制当前线程到子进程。这里:所有父进程中别 的线程,到了子进程中都是突然蒸发掉的 对于锁来说,从 OS 看,每个锁有一个所有者,即最后一次lock它的线程。假设这么一个环境,在fork之前,有一个子线程lock了某个锁,获得了对锁的所有权。fork 以后,在子进程中,所有的额外线程都 人间蒸发了。而锁却被正常复制了,在子进程看来,这个锁没有主人,所以没有任何人可以对它解锁。当子进程想 lock 这个锁时,不再有任何手段可以解开了。程序发生死锁
Q:Zygote为什么不采用 Binder 机制进行 IPC 通信?
A:Binder 机制中存在 Binder 线程池,是多线程的,如果 Zygote 采用 Binder 的话就存在上面说的fork() 与 多线程的问题了。其实严格来说,Binder机制不一定要多线程,所谓的 Binder 线程只不过是在循环读取 Binder 驱动的消息而已,只注册一个 Binder 线程也是可以工作的,比如 service manager就是这样的。实际上 Zygote 尽管没有采取 Binder 机制,它也不是单线程的,但它在 fork() 前主动停止 了其他线程,fork() 后重新启动了。