Android启动流程源码分析(基于Android S)
从上图我们可以清楚的看到Android系统的启动分为以下几个步骤
-
启动电源以及启动系统
当我们按下电源键时, 引导芯片代码开始从预定义的地方(固化在ROM)开始执行, 加载引导程序到RAM, 然后执行
-
引导程序
引导程序是在Android操作系统开始运行前的一个小程序. 引导程序是运行的第一个程序, 因此它是针对特性的主板和芯片的. 设备制造商要么使用很受欢迎的引导程序比如redboot, uboot, qi bootloader或者开发自己的引导程序, 在此注意一下, 引导程序并不是Android操作系统的一部分. 引导程序是OEM厂商或运营商加锁和限制的地方.
引导程序分为两个阶段执行.
第一: 检查外部的RAM以及加载对第二阶段有用的程序
第二: 引导程序设置网络, 内存等等. 这些对于运行内核是有必要的. 为了达到特殊的目标, 引导程序可以根据配置参数或者输入数据设置内核.
传统得加载器主要是2个文件:
/external/arm-trusted-firmware/lib/romlib/init.s
这个主要是初始化堆栈, 清零BSS段, 调用main.c的_main()函数
/external/rootdev/main.c
初始化硬件(闹钟,主板,键盘, 控制台等), 创建Linux标签
-
内核
Android内核与桌面Linux内核的启动方式差不多, 内核启动时, 设置缓存 被保护的存储器 计划列表 加载驱动. 当内核完成系统设置时, 它首先在系统文件中寻找"init"文件, 然后启动root进程或者系统的第一个进程(据我之前的了解这个进程叫idle进程, pid=0, 如有记错的话请大家指出)
-
init进程
init进程是Linux系统中用户空间的第一个进程, 进程号固定为1. Kernel启动后, 在用户空间启动init进程, 并调用init中的main()方法执行init进程的职责
-
启动Launcher App
init进程分析
init进程是Android系统中及其重要的用户空间的第一个进程. 接下来我们看看init进程做了些什么事情.
-
创建和挂载启动所需要的目录文件
-
初始化和启动属性服务
-
解析init.rc配置文件并启动Zygote进程
比较重要的两个文件
/system/core/init/init.cpp
/system/core/rootdir/init.rc
在这里init.cpp里面干了啥我们就不去详细解析了, 有兴趣得同学自己去研究
init.rc解析
init.rc是什么? 它是一个非常重要的配置文件, 由android初始化语言(Android Init Language)编写的脚本, 它里面包含了Action, Service, Command, Options. 这里就不过多讲解, 有兴趣了解的同学自己google
Zygote
-
Zygote概述
Zygote的翻译为"受精卵", 如其名, 它的主要作用是用来孵化进程的, 在Android系统中主要有以下两种程序
Java应用程序---主要基于ART虚拟机, 所有的应用程序APK都是属于这一类程序
Native程序---也就是利用c/cpp开发的程序, 例如bootanimation.
所有的Java程序都以及系统服务进程SystemServer 都是由Zygote孵化而来的, 而native程序是由init程序创建启动的.
Binder 机制中存在 Binder 线程池, 是多线程的, 如果 Zygote 采用 Binder 的话就存在上面说的 fork() 与 多线程的问题了。 其实严格来说, Binder 机制不一定要多线程, 所谓的 Binder 线程只不过是在循环读 取 Binder 驱动的消息而已, 只注册一个 Binder 线程也是可以工作的, 比如 service manager 就是这样的。 实际 上 Zygote 尽管没有采取 Binder 机制, 它也不是单线程的, 但它在 fork() 前主动停止了其他线程, fork() 后重 新启动了。
-
Zygote触发过程.
之前介绍了Zygote,那么Zygote是怎么被唤起来的?
在init.rc中, 上面有这一行代码:
import /system/etc/init/hw/init.${ro.zygote}.rc
${ro.zygote}这个会被替换成ro.zygote对应的属性值, 这个是由不同厂商自己定制的, 有4个值:
-
zygote32: zygote进程对应的执行程序是app_process(纯32bit模式)
-
zygote64:zygote进程对应的执行程序是app_process64(纯64bit模式)
-
zygote32_64: 启动两个zygote进程(名为zygote和zygote_secondary), 对应的执行程序是app_process32(主模式), app_process64
-
zygote64_32: 启动两个zygote进程(名为zygote和zygote_secondary), 对应的执行程序是app_process64(主模式), app_process32
Start zygote
system/core/rootdir/init.rc 960L
# It is recommended to put unnecessary data/ initialization from post-fs-data
# to start-zygote in device's init.rc to unblock zygote start.
on zygote-start && property:ro.crypto.state=unencrypted
wait_for_prop odsign.verification.done 1
# A/B update verifier that marks a successful boot.
exec_start update_verifier_nonencrypted
start statsd
start netd
start zygote
start zygote_secondary
on zygote-start && property:ro.crypto.state=unsupported
wait_for_prop odsign.verification.done 1
# A/B update verifier that marks a successful boot.
exec_start update_verifier_nonencrypted
start statsd
start netd
start zygote
start zygote_secondary
on zygote-start && property:ro.crypto.state=encrypted && property:ro.crypto.type=file
wait_for_prop odsign.verification.done 1
# A/B update verifier that marks a successful boot.
exec_start update_verifier_nonencrypted
start statsd
start netd
start zygote
start zygote_secondary
app_processXXX对应的位置为:out/target/product/xxx/system/bin
源位置app_process 中有一个比较重要的文件:::::/frameworks/base/cmds/app_process/app_main.cpp
L336 runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
这里, 就是由native的世界进入java世界的地方, 在此之前都是native和kernel的世界.
这里有个很重要的东西AppRunTime 从代码上看, 它是AnroidRunTime的子类, 也就是Android中比较核心的一个东西叫作ART /frameworks/base/core/jni/AndroidRunTime.cpp
既然是是进入了java世界, 那我们不是应该有虚拟机吗? 虚拟机是在哪里搞出来的?
AndroidRunTime.cpp start()函数中的L1246
if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) {
return;
}
onVmCreated(env);
/*
* Register android functions.
*/
if (startReg(env) < 0) {
ALOGE("Unable to register all android natives\n");
return;
}
我们可以看到, 这里ART先给我们干了两件事 启动java虚拟机, 然后注册JNI
然后呢? 不是说好进入java世界的吗? 只干这个可不行, start函数L1287
jclass startClass = env->FindClass(slashClassName);
if (startClass == NULL) {
ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
/* keep going */
} else {
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");
if (startMeth == NULL) {
ALOGE("JavaVM unable to find main() in '%s'\n", className);
/* keep going */
} else {
env->CallStaticVoidMethod(startClass, startMeth, strArray);
#if 0
if (env->ExceptionCheck())
threadExitUncaughtException(env);
#endif
}
}
OK, 我们应该都能看到了, 这里是通过反射去调用某个类的main方法, 哪个类? OK, 之前在app_main里面那一行 runtime.start 传进来的参数是ZygoteInit, 就是调的它.. 从这里.我们真正的进入了Java的世界
/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
zygoteServer = new ZygoteServer(isPrimaryZygote);
if (startSystemServer) {
Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);
// {@code r == null} in the parent (zygote) process, and {@code r != null} in the
// child (system_server) process.
if (r != null) {
r.run();
return;
}
}
Log.i(TAG, "Accepting command socket connections");
// The select loop returns early in the child process after a fork and
// loops forever in the zygote.
caller = zygoteServer.runSelectLoop(abiList);
pid = Zygote.forkSystemServer(
parsedArgs.mUid, parsedArgs.mGid,
parsedArgs.mGids,
parsedArgs.mRuntimeFlags,
null,
parsedArgs.mPermittedCapabilities,
parsedArgs.mEffectiveCapabilities);
/* For child process */
if (pid == 0) {
if (hasSecondZygote(abiList)) {
waitForSecondaryZygote(socketName);
}
zygoteServer.closeServerSocket();
return handleSystemServerProcess(parsedArgs);
}
这里对怎么启动SystemServer就不去过多的赘述了, 大概的套路是一样的, Zygote调用的是nativeFork之类的方法, 然后通过反射调用到SystemServer的main, 大概就是这个套路
当SystemServer启动完成之后, 当SystemReady, 就会启动Launcher, 到此 Android的大体启动流程到此结束
下面给出Zygote的流程图
我们都知道, 当我们点到Launcher中的某个icon要启动某个app的时候, Launcher通过Binder和AMS通信, AMS如果发现当前没有这个进程,则通知Zygote fork一个进程,然后AMS再通过Binder与ActivityThread进行交互, 这里不深究交互细节, 抛出一个问题: 既然Binder是Android中很重要的IPC, 那为什么Zygote和SystemServer之间用Socket通信? 用Binder是不是会更好?
其实并不然, Binder固然是好, 基于mmap能达到在内存中只做一次拷贝.. 但是Binder是多线程的.. 多线程环境下fork会不会出问题? 答案是肯定的.
别说什么在fork之前暂停所有线程, fork完之后再启动之类的话.. 这只是其中一个规避方案而已.
大家可以了解下 unix3 c++程序设计守则,
https://blog.csdn.net/wallwind/article/details/7036733
这个守则中有明确说明, 多线程程序里不准使用fork, 多线程环境下fork可能会导致死锁
Binder 机制中存在 Binder 线程池, 是多线程的, 如果 Zygote 采用 Binder 的话就存在上面说的 fork() 与 多线程的问题了。 其实严格来说, Binder 机制不一定要多线程, 所谓的 Binder 线程只不过是在循环读 取 Binder 驱动的消息而已, 只注册一个 Binder 线程也是可以工作的, 比如 service manager 就是这样的。 实际 上 Zygote 尽管没有采取 Binder 机制, 它也不是单线程的, 但它在 fork() 前主动停止了其他线程, fork() 后重 新启动了。
浅谈fork()导致死锁的原因
复制整个用户空间的数据(通常使用 copy-on-write 的策略, 所以可以 实现的速度很快) 以及所有系统对象, 然后仅复制当前线程到子进程。这里: 所有父进程中别 的线程, 到了子进程 中都是突然蒸发掉的 对于锁来说, 从 OS 看, 每个锁有一个所有者, 即最后一次 lock 它的线程。 假设这么一个环境, 在 fork 之前, 有 一个子线程 lock 了某个锁, 获得了对锁的所有权。 fork 以后, 在子进程中, 所有的额外线程都人间蒸发了。 而锁却 被正常复制了, 在子进程看来, 这个锁没有主人, 所以没有任何人可以对它解锁。 当子进程想 lock 这个锁时, 不再有任何手段可以解开了。 程序发生死锁
init脚本语言相关
https://blog.csdn.net/chaihuasong/article/details/50456113
汇编指ARM指令集
https://blog.csdn.net/TuxedoLinux/article/details/111462307
Unix c++程序设计守则
https://blog.csdn.net/xiaohangyu/article/details/3341135
Linux 进程托孤
沙盒机制
init脚本语言属性
Copy-on-write
mmap