一、层级
1. Linux 内核Android 平台的基础是 Linux 内核。例如,Android Runtime (ART) 依靠 Linux 内核来执行底层功能,例如线程和低层内存管理。 使用 Linux 内核可让 Android 利用主要安全功能,并且允许设备制造商为著名的内核开发硬件驱动程 序。2. 硬件抽象层 (HAL)硬件抽象层 (HAL) 提供标准界面,向更高级别的 Java API 框架显示设备硬件功能。HAL 包含多个 库模块,其中每个模块都为特定类型的硬件组件实现一个界面,例如相机或蓝牙模块。当框架 API 要求访问设备硬件时,Android 系统将为该硬件组件加载库模块。3. Android Runtime对于运行 Android 5.0(API 级别 21)或更高版本的设备,每个应用都在其自己的进程中运行,并 且有其自己的 Android Runtime (ART) 实例。ART 编写为通过执行 DEX 文件在低内存设备上运行 多个虚拟机,DEX 文件是一种专为 Android 设计的字节码格式,经过优化,使用的内存很少。编 译工具链(例如 Jack)将 Java 源代码编译为 DEX 字节码,使其可在 Android 平台上运行。ART 的部分主要功能包括:预先 (AOT) 和即时 (JIT) 编译优化的垃圾回收 (GC)在 Android 9(API 级别 28)及更高版本的系统中,支持将应用软件包中的 Dalvik Executable 格 式 (DEX) 文件转换为更紧凑的机器代码。更好的调试支持,包括专用采样分析器、详细的诊断异常和崩溃报告,并且能够设置观察点以监控 特定字段在 Android 版本 5.0(API 级别 21)之前,Dalvik 是 Android Runtime。如果您的应用在 ART 上 运行效果很好,那么它应该也可在 Dalvik 上运行,但反过来不一定。Android 还包含一套核心运行时库,可提供 Java API 框架所使用的 Java 编程语言中的大部分功能,包括 一些 Java 8 语言功能。4. 原生 C/C++ 库许多核心 Android 系统组件和服务(例如 ART 和 HAL)构建自原生代码,需要以 C 和 C++ 编写的 原生库。Android 平台提供 Java 框架 API 以向应用显示其中部分原生库的功能。例如,您可以通 过 Android 框架的 Java OpenGL API 访问 OpenGL ES,以支持在应用中绘制和操作 2D 和 3D 图 形。如果开发的是需要 C 或 C++ 代码的应用,可以使用 Android NDK 直接从原生代码访问某些原生平台 库。5. Java API 框架您可通过以 Java 语言编写的 API 使用 Android OS 的整个功能集。这些 API 形成创建 Android 应 用所需的构建块,它们可简化核心模块化系统组件和服务的重复使用,包括以下组件和服务:丰富、可扩展的视图系统,可用以构建应用的 UI,包括列表、网格、文本框、按钮甚至可嵌入的网 络浏览器资源管理器,用于访问非代码资源,例如本地化的字符串、图形和布局文件通知管理器,可让所有应用在状态栏中显示自定义提醒Activity 管理器,用于管理应用的生命周期,提供常见的导航返回栈内容提供程序,可让应用访问其他应用(例如“联系人”应用)中的数据或者共享其自己的数据开发者可以完全访问 Android 系统应用使用的框架 API。6. 系统应用Android 随附一套用于电子邮件、短信、日历、互联网浏览和联系人等的核心应用。平台随附的应 用与用户可以选择安装的应用一样,没有特殊状态。因此第三方应用可成为用户的默认网络浏览 器、短信 Messenger 甚至默认键盘(有一些例外,例如系统的“设置”应用)。系统应用可用作用户的应用,以及提供开发者可从其自己的应用访问的主要功能。例如,如果您的应用 要发短信,您无需自己构建该功能,可以改为调用已安装的短信应用向您指定的接收者发送消息
二、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进程分析
1. 创建和挂载启动所需要的文件目录2. 初始化和启动属性服务3. 解析init.rc配置文件并启动Zygote进程
init.rc文件解析
init.rc是一个非常重要的配置文件,它是由Android初始化语言(Android Init Language)编写的脚 本,它主要包含五种类型语句:Action(Action中包含了一系列的Command)、Commands(init语言 中的命令)、Services(由init进程启动的服务)、Options(对服务进行配置的选项)和Import(引入 其他配置文件)。
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 <service_class_name>: 启动属于同一个class的所有服务;
start <service_name>: 启动指定的服务,若已启动则跳过;
stop <service_name>: 停止正在运行的服务
setprop :设置属性值
mkdir :创建指定目录
symlink <sym_link>: 创建连接到的<sym_link>符号链接;
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/<name> 的socket
critical: 在规定时间内该service不断重启,则系统会重启并进入恢复模式
四、zegote进程
Zygote中文翻译为“受精卵”,正如其名,它主要用于孵化子进程。在Android系统中有以下两种程序: java应用程序,主要基于ART虚拟机,所有的应用程序apk都属于这类native程序,也就是利用C或C++语 言开发的程序,如bootanimation。所有的Java应用程序进程及系统服务SystemServer进程都由Zygote 进程通过Linux的fork()函数孵化出来的,这也就是为什么把它称为Zygote的原因,因为他就像一个受精 卵,孵化出无数子进程,而native程序则由Init程序创建启动。Zygote进程最初的名字不是“zygote”而是 “app_process”,这个名字是在Android.mk文件中定义的Zgyote是Android中的第一个art虚拟机,他通过socket的方式与其他进程进行通信。这里的“其他进程” 其实主要是系统进程——SystemSer
Zygote是一个C/S模型,Zygote进程作为服务端,它主要负责创建Java虚拟机,加载系统资源,启
动SystemServer进程,以及在后续运行过程中启动普通的应用程序,其他进程作为客户端向它发
出“孵化”请求,而Zygote接收到这个请求后就“孵化”出一个新的进程。比如,当点击Launcher里的
应用程序图标去启动一个新的应用程序进程时,这个请求会到达框架层的核心服务
ActivityManagerService中,当AMS收到这个请求后,它通过调用Process类发出一个“孵化”子进
程的Socket请求,而Zygote监听到这个请求后就立刻fork一个新的进程出来
1.zygote启动过程
Zygote启动过程
位置\frameworks\base\cmds\app_process\app_main.cpp
在app_main.cpp的main函数中,主要做的事情就是参数解析. 这个函数有两种启动模式:
1. 一种是zygote模式,也就是初始化zygote进程,传递的参数有--start-system-server --socketname=zygote,前者表示启动SystemServer,后者指定socket的名称
2. 一种是application模式,也就是启动普通应用程序,传递的参数有class名字以及class带的参数
两者最终都是调用AppRuntime对象的start函数,加载ZygoteInit或RuntimeInit两个Java类,并将之前整理的参数传入进去
App_main.mian() - AndroidRuntime.start() - startVM() - startReg() - ZygoteInit.main() - registerZygoteSocket() - preload() - startSystemServer() - runSelectLoop()
app_process 里面定义了三种应用程序类型:
1. Zygote: com.android.internal.os.ZygoteInit
2. System Server, 不单独启动,而是由Zygote启动
3. 其他指定类名的Java 程序
2. Runtime
归纳起来的意思就是,Runtime 是支撑程序运行的基础库,它是与语言绑定在一起的。比如:
C Runtime: 就是C standard lib, 也就是我们常说的libc。(有意思的是, Wiki会自动将“C
runtime” 重定向到 “C Standard Library”).
Java Runtime: 同样,Wiki将其重定向到” Java Virtual Machine”, 这里当然包括Java 的支撑类库.
AndroidRuntime: 显而易见,就是为Android应用运行所需的运行时环境。这个环境包括以下东西:
Dalvik VM: Android的Java VM, 解释运行Dex格式Java程序。每个进程运行一个虚拟机(什么
叫运行虚拟机?说白了,就是一些C代码,不停的去解释Dex格式的二进制码(Bytecode),把
它们转成机器码(Machine code),然后执行,当然,现在大多数的Java 虚拟机都支持JIT,也
就是说,bytecode可能在运行前就已经被转换成机器码,从而大大提高了性能。过去一个普
遍的认识是Java 程序比C,C++等静态编译的语言慢,但随着JIT的介入和发展,这个已经完全
是过去时了,JIT的动态性运行允许虚拟机根据运行时环境,优化机器码的生成,在某些情况
下,Java甚至可以比C/C++跑得更快,同时又兼具平台无关的特性,这也是为什么Java如今如
此流行的原因之一吧)。
Android的Java 类库, 大部分来自于 Apache Hamony, 开源的Java API 实现,如 java.lang,java.util, java.net. 但去除了AWT, Swing 等部件。
JNI: C和Java互调的接口。
Libc: Android也有很多C代码,自然少不了libc,注意的是,Android的libc叫 bionic C
3. 工作内容
Java虚拟机的启动大致做了以下一些事情:
1. 从property读取一系列启动参数。
2. 创建和初始化结构体全局对象(每个进程)gDVM,及对应与JavaVM和JNIEnv的内部结构体
JavaVMExt, JNIEnvExt.
3. 初始化java虚拟机,并创建虚拟机线程
4. 注册系统的JNI,Java程序通过这些JNI接口来访问底层的资源。
loadJniLibrary("javacore");
loadJniLibrary("nativehelper");
5. 为Zygote的启动做最后的准备,包括设置SID/UID, 以及mount 文件系统
6. 返回JavaVM 给Native代码,这样它就可以向上访问Java的接口
首先通过Runtime的create方法创建单例的Runtime对象,runtime负责提供art虚拟机的运行时环境,
然后调用其init方法来初始化虚拟机
preloadClassess 将framework.jar里的preloaded-classes 定义的所有class load到内存里,
preloaded-classes 编译Android后可以在framework/base下找到。而preloadResources 将系统的
Resource(不是在用户apk里定义的resource)load到内存。资源preload到Zygoted的进程地址空间,
所有fork的子进程将共享这份空间而无需重新load, 这大大减少了应用程序的启动时间,但反过来增加了
系统的启动时间。通过对preload 类和资源数目进行调整可以加快系统启动。Preload也是Android启动最耗时的部分之一
gc()调用只是通知VM进行垃圾回收,是否回收,什么时候回收全由VM内部算法决定。GC的回收有一个
复杂的状态机控制,通过多次调用,可以使得尽可能多的资源得到回收。gc()必须在fork之前完成(接下
来的StartSystemServer就会有fork操作),这样将来被复制出来的子进程才能有尽可能少的垃圾内存没有释放
ZygoteInit.forkSystemServer() 方法fork 出一个新的进程,这个进程就是SystemServer进程。fork出来
的子进程在handleSystemServerProcess 里开始初始化工作,主要工作分为:
1. prepareSystemServerProfile()方法中将SYSTEMSERVERCLASSPATH中的AppInfo加载到VM
中。
2. 判断fork args中是否有invokWith参数,如果有则进行WrapperInit.execApplication(不进行深入
讲解了)。如果没有则调用
五、SystemServer进程
System Server 是Zygote fork 的第一个Java 进程, 这个进程非常重要,因为他们有很多的系统线程,
提供所有核心的系统服务
看到大名鼎鼎的WindowManager, ActivityManager了吗?对了,它们都是运行在system_server的进程
里。还有很多“Binder-x”的线程,它们是各个Service为了响应应用程序远程调用请求而创建的。除此之
外,还有很多内部的线程,比如 ”UI thread”, “InputReader”, “InputDispatch” 等等,我,现在我们只关
心System Server是如何创建起来的。
SystemServer的main() 函数。
记下来我分成4部分详细分析SystemServer run方法的初始化流程:
初始化必要的SystemServer环境参数,比如系统时间、默认时区、语言、load一些Library等等,
初始化Looper,我们在主线程中使用到的looper就是在SystemServer中进行初始化的
初始化Context,只有初始化一个Context才能进行启动Service等操作,这里看一下源码:
private void createSystemContext() {
ActivityThread activityThread = ActivityThread.systemMain();
mSystemContext = activityThread.getSystemContext();
mSystemContext.setTheme(DEFAULT_SYSTEM_THEME);
final Context systemUiContext = activityThread.getSystemUiContext();
systemUiContext.setTheme(DEFAULT_SYSTEM_THEME);
}
ActivityThread就是这个时候生成的 ActivityThread中如何生成Context:
public ContextImpl getSystemContext() {
synchronized (this) {
if (mSystemContext == null) {
mSystemContext = ContextImpl.createSystemContext(this);
}
return mSystemContext; }}
ContextImpl是Context类的具体实现,里面封装完成了生成几种常用的createContext的方法
初始化SystemServiceManager,用来管理启动service,SystemServiceManager中封装了启动Service的startService方法启动系统必要的Service,启动service的流程又分成三步走:
// Start services.
try {
traceBeginAndSlog("StartServices");
startBootstrapServices();
startCoreServices();
startOtherServices();
SystemServerInitThreadPool.shutdown();
} catch (Throwable ex) {
//
} finally {
traceEnd();}
启动BootstrapServices,就是系统必须需要的服务,这些服务直接耦合性很高,所以干脆就放在一个方
法里面一起启动,比如PowerManagerService、RecoverySystemService、DisplayManagerService、
ActivityManagerService等等启动以基本的核心Service,很简单,只有三个BatteryService、
UsageStatsService、WebViewUpdateService启动其它需要用到的Service,比如
NetworkScoreService、AlarmManagerService
[善后工作是不是到此之后,Zygote的工作变得很轻松了,可以宜养天年了?可惜现代社会,哪个父
母把孩子养大就可以撒手不管了?尤其是像Sytem Server 这样肩负社会重任的大儿子,出问题了
父母还是要帮一把的。这里,Zygote会默默的在后台凝视这自己的大儿子,一旦发现System
Server 挂掉了,将其回收,然后将自己杀掉,重新开始新的一生, 可怜天下父母心啊。这段实现
在代码 :dalvik/vm/native/dalvik_system_zygote.cpp 中,
在Unix-like系统,父进程必须用 waitpid 等待子进程的退出,否则子进程将变成”Zombie” (僵尸)进
程,不仅系统资源泄漏,而且系统将崩溃(没有system server,所有Android应用程序都无法运行)。
但是waitpid() 是一个阻塞函数(WNOHANG参数除外),所以通常做法是在signal 处理函数里进行无阻
塞的处理,因为每个子进程退出的时候,系统会发出 SIGCHID 信号。Zygote会把自己杀掉, 那父亲死
了,所有的应用程序不就成为孤儿了? 不会,因为父进程被杀掉后系统会自动给所有的子进程发生
SIGHUP信号,该信号的默认处理就是将杀掉自己退出当前进程。但是一些后台进程(Daemon)可以通
过设置SIG_IGN参数来忽略这个信号,从而得以在后台继续运行。]
六、总结
1. init 根据init.rc 运行 app_process, 并携带‘–zygote’ 和 ’–startSystemServer’ 参数。2. AndroidRuntime.cpp::start() 里将启动JavaVM,并且注册所有framework相关的系统JNI接口。3. 第一次进入Java世界,运行ZygoteInit.java::main() 函数初始化Zygote. Zygote 并创建Socket的 server 端。4. 然后fork一个新的进程并在新进程里初始化SystemServer. Fork之前,Zygote是preload常用的Java类库以及系统的resources,同时GC()清理内存空间,为子进程省去重复的作。5. SystemServer 里将所有的系统Service初始化,包括ActivityManager 和 WindowManager, 他们 是应用程序运行起来的前提。6. 依次同时,Zygote监听服务端Socket,等待新的应用启动请求。7. ActivityManager ready 之后寻找系统的“Startup” Application, 将请求发给Zygote。8. Zygote收到请求后,fork出一个新的进程。9. Zygote监听并处理SystemServer 的 SIGCHID 信号,一旦System Server崩溃,立即将自己杀死。 init会重启Zygote.什么情况下Zygote进程会重启呢servicemanager进程被杀;(onresart)surfaceflinger进程被杀;(onresart)Zygote进程自己被杀;(oneshot=false)system_server进程被杀; (waitpid)
1. fork

2. 子进程与父进程的区别
1. 除了文件锁以外,其他的锁都会被继承
2. 各自的进程ID和父进程ID不同
3. 子进程的未决告警被清除;
4. 子进程的未决信号集设置为空集。
3.写时拷贝 (copy- on-write)
Linux 的 fork() 使用是通过写时拷贝 (copy- on-write) 实现。写时拷贝是一种可以推迟甚至避免拷贝
数据的技术。内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。只用在需 要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间。也就是说,资源的复制是在需 要写入的时候才会进行,在此之前,只有以只读方式共享
4.孤儿进程、僵尸进程
fork系统调用之后,父子进程将交替执行,执行顺序不定。如果父进程先退出,子进程还没退出那么子 进程的父进程将变为init进程(托孤给了init进程)。(注:任何一个进程都必须有父进程)如果子进程 先退出,父进程还没退出,那么子进程必须等到父进程捕获到了子进程的退出状态才真正结束,否则这 个时候子进程就成为僵进程(僵尸进程:只保留一些退出信息供父进程查询)
5. 多线程进程的Fork调用
在 POSIX 标准中,fork 的行为是这样的:复制整个用户空间的数据(通常使用 copy-on-write 的策略, 所以可以实现的速度很快)以及所有系统对象,然后仅复制当前线程到子进程。这里:所有父进程中别 的线程,到了子进程中都是突然蒸发掉的
假设这么一个环境,在 fork 之前,有一个子线程 lock 了某个锁,获得了对锁的所有权。fork 以
后,在子进程中,所有的额外线程都人间蒸发了。而锁却被正常复制了,在子进程看来,这个锁没
有主人,所以没有任何人可以对它解锁。当子进程想 lock 这个锁时,不再有任何手段可以解开了。程序发生死锁
七、面试题
面试官:你了解 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,最终就看到了手机的 "桌面"。面试官:system_server 为什么要在 Zygote 中启动,而不是由 init 直接启动呢?A:Zygote 作为一个孵化器,可以提前加载一些资源,这样 fork() 时基于 Copy-On-Write 机制创建的其 他进程就能直接使用这些资源,而不用重新加载。比如 system_server 就可以直接使用 Zygote 中的 JNI 函数、共享库、常用的类、以及主题资源。面试官:为什么要专门使用 Zygote 进程去孵化应用进程,而不是让 system_server 去孵化呢?A:首先 system_server 相比 Zygote 多运行了 AMS、WMS 等服务,这些对一个应用程序来说是不需要 的。另外进程的 fork() 对多线程不友好,仅会将发起调用的线程拷贝到子进程,这可能会导致死锁,而 system_server 中肯定是有很多线程的。面试官:能说说具体是怎么导致死锁的吗?在 POSIX 标准中,fork 的行为是这样的:复制整个用户空间的数据(通常使用 copy-on-write 的策略, 所以可以实现的速度很快)以及所有系统对象,然后仅复制当前线程到子进程。这里:所有父进程中别 的线程,到了子进程中都是突然蒸发掉的对于锁来说,从 OS 看,每个锁有一个所有者,即最后一次 lock 它的线程。假设这么一个环境,在 fork 之前,有一个子线程 lock 了某个锁,获得了对锁的所有权。fork 以后,在子进程中,所有的额外线程都 人间蒸发了。而锁却被正常复制了,在子进程看来,这个锁没有主人,所以没有任何人可以对它解锁。 当子进程想 lock 这个锁时,不再有任何手段可以解开了。程序发生死锁面试官:Zygote 为什么不采用 Binder 机制进行 IPC 通信? 有可能死锁?A:Binder 机制中存在 Binder 线程池,是多线程的,如果 Zygote 采用 Binder 的话就存在上面说的 fork() 与 多线程的问题了。其实严格来说,Binder 机制不一定要多线程,所谓的 Binder 线程只不过是 在循环读取 Binder 驱动的消息而已,只注册一个 Binder 线程也是可以工作的,比如 service manager 就是这样的。实际上 Zygote 尽管没有采取 Binder 机制,它也不是单线程的,但它在 fork() 前主动停止了其他线程,fork() 后重新启动了。

499

被折叠的 条评论
为什么被折叠?



