Android
是基于Linux
系统的,所以Android
启动将由Linux Kernel
启动并创建init
进程。该进程是所有用户空间的鼻祖。
在init
进程启动的过程中,会相继启动servicemanager
(binder服务管理者)、Zygote
进程(java进程)。而Zygote
又会创建system_server
进程以及app
进程。
所以你一定听到过这句话:app进程是由Zygote
进程通过fork
创建出来的。
下面我尝试来分析Android
启动过程中关于init
进程的创建过程。
此次分析过程基于
Android 10.0
init
init
进程是Android
启动过程中在Linux
系统中用户空间的第一个进程。init
启动入口是在它的SecondStageMain
方法中。但调用init
的SecondStageMain
方法是通过main.cpp
中的main
方法进行的。
所以我们就从main.cpp
的main
方法开始。
main
system/core/init/main.cpp
|
|
在main.cpp
的main
方法中,主要分为三步:
- FirstStageMain
- SetupSelinux
- SecondStageMain
FirstStageMain
system/core/init/first_stage_init.cpp
它是init
进程启动的第一步,主要任务是挂载相关的文件系统
|
|
主要通过mount
挂载对应的文件系统,mkdir
创建对应的文件目录,并配置相应的访问权限。
需要注意的是,这些文件只是在应用运行的时候存在,一旦应用运行结束就会随着应用一起消失。
挂载的文件系统主要有四类:
tmpfs
: 一种虚拟内存文件系统,它会将所有的文件存储在虚拟内存中。由于tmpfs
是驻留在RAM
的,因此它的内容是不持久的。断电后,tmpfs
的内容就消失了,这也是被称作tmpfs
的根本原因。devpts
: 为伪终端提供了一个标准接口,它的标准挂接点是/dev/pts
。只要pty
的主复合设备/dev/ptmx
被打开,就会在/dev/pts
下动态的创建一个新的pty
设备文件。proc
: 也是一个虚拟文件系统,它可以看作是内核内部数据结构的接口,通过它我们可以获得系统的信息,同时也能够在运行时修改特定的内核参数。sysfs
: 与proc
文件系统类似,也是一个不占有任何磁盘空间的虚拟文件系统。它通常被挂接在/sys
目录下。
在FirstStageMain
还会通过InitKernelLogging(argv)
来初始化log
日志系统。此时Android
还没有自己的系统日志,采用kernel
的log
系统,打开的设备节点/dev/kmsg
, 那么可通过cat /dev/kmsg
来获取内核log
。
最后会通过execv
方法传递对应的path
与下一阶段的参数selinux_setup
。
SetupSelinux
system/core/init/selinux.cpp
|
|
主要是用来提高linux
的安全,进一步约束访问的权限。
最后也是通过execv
来进程init
启动的核心阶段SecondStageMain
。
SecondStageMain
system/core/init/init.cpp
|
|
在SecondStageMain
中主要分为4步
- 初始化属性服务
- 初始化single句柄
- 开启属性服务
- 解析.rc文件
初始化属性服务
system/core/init/property_service.cpp
|
|
主要方法是__system_property_area_init()
,用来创建跨进程内存,主要操作为
- 执行
open
,打开dev/properities
共享内存文件,大小为128KB
- 执行
mmap
,将内存映射到init
进程 - 将该内存的首地址保存在全局变量
__system_property_area__
,后续的增加或者修改属性都基于该变量来计算位置。
初始化single句柄
system/core/init/init.cpp
|
|
每个进程在处理其他进程发送的signal
信号时都需要先注册,当进程的运行状态改变或终止时会产生某种signal
信号,init
进程是所有用户空间进程的父进程,当其子进程终止时产生signal
信号。以便父进程进行处理。
主要目的是为了防止子进程成为僵尸进程。
何为僵尸进程?
父进程使用fork
创建子进程,子进程终止后,如果父进程不知道子进程已经终止的话,这时子进程虽然已经退出,但是在系统进程表中还为它保留了一些信息(如进程号、运行时间、退出状态等),这个子进程就是所谓的僵尸进程。其中系统进程表是一项有限的资源,如果它被僵尸进程耗尽的话,系统可能会无法创建新的进程。
通过epoll
进行注册句柄,最后会由HandleSignalFd
进行回调操作。
epoll
又是什么?
在Linux
的新内核中,epoll
是用来取代select/poll
的,它是Linux
内核为处理大批量文件描述符的改进版poll
,是Linux
下多路复用I/O
接口select/poll
的增强版,它能显著提升程序在大量并发连接中只有少量活跃的情况下的系统CPU
利用率。
epoll
内部使用了红黑树,所以查找效率比使用数组的select
更快。
开启属性服务
system/core/init/property_service.cpp
|
|
主要做的任务是:
- 创建非阻塞式的
Socket
,并返回property_set_fd
文件描述符。 - 使用
listen()
函数去监听property_set_fd
,此时Socket
即成为属性服务端,并且它最多同时可为8
个试图设置属性的用户提供服务。 - 使用
epoll
注册,当property_set_fd
中有数据到来时,init
进程将调用handle_property_set_fd()
函数进行处理。
解析.rc文件
system/core/init/init.cpp
|
|
通过ParseConfig
来解析init.rc
配置文件。
.rc
文件语法是以行尾单位,以空格间隔的语法,以#
开始代表注释行。.rc
文件主要包含Action
、Service
、Command
、Options
、Import
,其中对于Action
和Service
的名称都是唯一的,对于重复的命名视为无效。
init.rc
中的Action
、Service
语句都有相应的类来解析,即ActionParser
、ServiceParser
。
在解析init.rc
中的配置,进行启动Zygote
。
关于
Zygote
的启动后续再分析。
今天主要尝试分析了一下Android
在Linux
系统下的init
启动涉及的主要流程。
可见init
启动主要涉及的工作是:
- 创建与挂载启动所需要的文件系统
- 初始化属性服务
- 创建
single
句柄,来监听子进程,防止僵尸进程的产生 - 开启属性服务
- 解析
.rc
文件并启动Zygote
进程
项目
android_startup: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup
的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。
AwesomeGithub: 基于Github
客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin
语言进行开发,项目架构是基于Jetpack&DataBinding
的MVVM
;项目中使用了Arouter
、Retrofit
、Coroutine
、Glide
、Dagger
与Hilt
等流行开源技术。
flutter_github: 基于Flutter
的跨平台版本Github
客户端,与AwesomeGithub
相对应。
android-api-analysis: 结合详细的Demo
来全面解析Android
相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。
daily_algorithm: 每日一算法,由浅入深,欢迎加入一起共勉。