Android---系统启动流程

目录

Android 系统启动流程

init 进程分析

init.rc 解析

Zygote 概叙

Zygote 触发过程

Zygote 启动过程

什么时Runtime?

System Server 启动流程

Fork 函数

总结

面试题

Android 是 google 公司开发的一款基于 Linux 的开源操作系统。

Android 系统启动流程

android 系统启动的大概流程如下图所示:

\bullet 第一步:启动电源以及系统启动

当电源按下,引导芯片代码开始从预定义的地方(固化在ROM)开始执行。加载引导程序到 RAM,然后执行。

\bullet 第二步:引导程序

 引导程序是在 Android 操作系统开始运行前的一个小程序。引导程序是运行的第一个程序,因此它是针对特定的主板与芯片的。设备制造商要么使用很受欢迎的引导程序比如 reboot, uboot, qibootloader 或者开发自己的引导程序,它不是 Android 操作系统的一部分。引导程序是 OEM 厂商或者运营商加锁和限制的地方。

引导程序分两个阶段执行:

第一阶段:检测外部的RAM以及加载第二阶段有用的程序。

第二阶段:引导程序设置网络、内存等等。这些对于运行内核是必须的,为了达到特殊的目标,引导程序可以根据配置参数或者输入数据设置内核。

Android 引导程序可以在 \bootable\bootloader\regacy\usbloader 找到。传统的加载器包含两个文件,需要在这里说明:

init.s 初始化堆栈,清零 BBS 段,调用 main.c 的 main()  函数;

main.c 初始化硬件(闹钟、主板、键盘、控制台) ,创建 linux 标签

 \bullet 第三步:内核(Kernel)

Android 内核与桌面 linux 内核启动方式差不多。内核启动时,设置缓存、保护存储器、计划列表、加载驱动。当内核完成系统设置,它首先在系统文件种寻找 "init" 文件,然后启动 root 进程或者系统的第一个进程。

 \bullet 第四步:init 进程

init 进程是 Linux 系统中用户空间的第一个进程,进程号固定为1。Kernel 启动后,在用户空间启动 init 进程,并调用 init 中的 main() 方法执行 init 进程的职责。

 \bullet 第五步:启动 Lancher App

注意:init 进程解析 init.rc 后 fork 出 zygote 进程(init 进程是用户空间的第一个进程),zygote 进程又会 fork 出其它 App 进程,以及它的大儿子 SystemServer 进程,SystemServer 进程中又会创建出许多 SystemService(AMS, PMS, WMS, PKMS)。

init 进程分析

其中 init 进程是 Android 系统中及其重要的第一个进程,init 进程做了如下三件事:

  1. 创建和挂载启动所需要的文件目录

  2. 初始化和启动属性服务

  3. 解析 init.rc 配置文件并启动 zygote 进程

init.rc 解析

init.rc 是一个非常重要的配置文件,它是由 Android 初始化语言(Android Init Language) 编写的脚本,它主要包含五种类型语句:Action(Action 中包含了一系列的 Command)、Commands(init 语言中的命令)、Services(由 init 进程启动的服务)、Options(对服务进行配置的选项) 和 Import (导入其它配置文件)。init.rc 的配置代码如下所示:

# \system\core\rootdir\init.rc
on init # L41
sysclktz 0
# Mix device-specific information into the entropy pool
copy /proc/cmdline /dev/urandom
copy /default.prop /dev/urandom
...
on <trigger> [&& <trigger>]* //设置触发器
<command>
<command> //动作触发之后要执行的命令
service <name> <pathname> [ <argument> ]* //<service的名字><执行程序路径><传递参
数>
<option> //Options是Services的参数配置. 它们影响Service如何运行及运行时机
group <groupname> [ <groupname>\* ] //在启动Service前将group改为第一个
groupname,第一个groupname是必须有的,
//默认值为root(或许默认值是无),第二个groupname可以不设置,用于追加组(通过
setgroups)
priority <priority> //设置进程优先级. 在-20~19之间,默认值是0,能过
setpriority实现
socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ]//创建
一个unix域的socket,名字叫/dev/socket/name , 并将fd返回给Service. type 只能是
"dgram", "stream" or "seqpacket".
...

 Action

Action: 通过触发器 trigger,即以 on 开头的语句来决定执行相应的 service 的时机:

  \bullet on early-init: 在初始化早期阶段触发;

  \bullet on init: 在初始化阶段触发;

  \bullet on late-init: 在初始化晚期阶段触发;

  \bullet on boot/charger: 当系统启动/充电时触发;

  \bullet on property: 当属性值满足条件时触发。

 Service

 服务 Service,以 service 开头,由 init 进程启动,一般运行在 init 的一个子进程,所以启动 service 前需要判断对应的可执行文件是否存在。init 生成的子进程,定义在 rc 文件,其中每一个 service 在启动时会通过 fork 方式生成子进程

例如:service servicemanager /system/bin/servicemanager 代表的是服务名为 servicemanager,服务执行的路径为 /system/bin/servicemanager。

Command

 下面列举常用的命令:

  \bullet class_start<service_class_name>: 启动属于同一个 class 的所有服务;

  \bullet start<service_name>: 启动指定的服务,若已启动则跳过;

  \bullet stop<service_name>: 停止正在运行的服务;

  \bullet setprop: 设置属性值;

  \bullet mkdir: 创建指定目录;

  \bullet symlink<sym_link>: 创建连接到的<sym_link>符号链接;

  \bullet write: 向文件 path 中写入字符串;

  \bullet exec: fork并执行,会阻塞 init 进程直到程序完毕;

  \bullet exprot: 设置环境变量;

  \bullet loglevel: 设置 log 级别。

Options

Options 是 Service 的可选项,与 Service 配合使用

  \bullet disabled: 不随 class 自动启动,只根据 service 名才启动;

  \bullet oneshot: service 退出后不再重启;

  \bullet user/group: 设置执行服务的用户/用户组,默认都是 root;

  \bullet class: 设置所属的类名,当所属类启动/退出时,服务也启动/停止,默认为 default;

  \bullet onrestart: 当服务重启时执行相应命令;

  \bullet socket: 创建名为 /dev/socket/<name> 的socket

  \bullet critical: 在规定时间内该 service 不断重启,则系统会重启并进入恢复模式。

default: 意味着 disabled = false, oneshot = false, critical = false。

Zygote 概叙

Zygote 中文翻译为“受精卵”,它主要用于孵化子进程。在 Android 系统中有以下两种程序:Java 应用程序,主要基于 ART(Android Runtime) 虚拟机,所有的应用程序 apk 都属于这类 native 程序,也就是利用 C/C++ 语言开发的程序,如 bootanimation。所有的 Java 应用程序进程及系统服务(SystemServer)进程都由 Zygote 进程通过 Linux 的 fork() 函数卵化出来的,也就是为什么把它称为 Zygote 的原因,因为他就像一个受精卵,卵化出无数子进程,而 native 程序则由 Init 程序创建启动。Zygote 进程最初的名字不是“zygote”而是“app_process”,这个名字是在 Android.mk 文件中定义的。

Zygote 是 Android 中的第一个 ART 虚拟机,他通过 socket 的方式与其它进程进行通信。这里的“其他进程”其实主要是系统进程--SystemServer。

Zygote 是一个 C/S 模型,Zygote 进程作为服务端,它主要负责创建 Java 虚拟机,加载系统资源,启动 SystemServer 进程,以及在后续运行过程中启动普通的应用程序,其他进程作为客户端向它发出“孵化”请求,而 Zygote 接收到这个请求后就“孵化”出一个新的进程。比如,当点击 Launcher 里的应用程序图标去启动一个新的应用程序时,这个请求会到达框架层的核心服务 ActivityManagerService 中,当 AMS 收到这个请求后,它通过调用 Process 类发出一个“孵化”子进程的 Socket 请求,而 Zygote 监听到这个请求后就立刻 fork 一个新的进程出来。

Zygote 触发过程

1. init.zygoteXX.rc

import /init.${ro.zygote}.rc

${ro.zygote} 会被替换成 ro.zygote 的属性值,这个是由不同的硬件厂商自己制定的,由四个值:

    \bullet zygote32: zygote进程对应的执行程序是 app_process(纯32bit 模式)

    \bullet zygote64: zygote进程对应的执行程序是 app_process64(纯32bit 模式)

    \bullet zygote32_64: 启动两个 zygote 进程(名为 zygote 和 zygote_secondary),对应的执行程序分别是 app_process32(主模式)、approcess63

     \bullet zygote64_32: 启动两个 zygote 进程(名为 zygote 和 zygote_secondary),对应的执行程序分别是 app_process64(主模式)、app_precess32

2. start zygote

位置:system\core\rootdir\init.rc,zygote-start 是在 on late-init 中触发的。

3. app_processXX

位置 \frameworks\base\cmds\app_process\

Zygote 启动过程

位置 \frameworks\base\cmds\app_process\app_main.cpp
在 app_main.cpp 的 main 函数中,主要做的事情就是参数解析。这个函数有两种启动模式:
        1. zygote 模式,也就是初始化 zygote 进程,传递的参数有 --start-system-server --socket - name = zygote,前者表示启动 SystemServer,后者指定 socket 的名称。
        2. application模式,也就是启动普通应用程序,传递的参数有 class 名字以及 class 带的参数
两者最终都是调用 AppRuntime 对象的 start 函数,加载 ZygoteInit 或 RuntimeInit 两个 Java 类,并将之前整理的参数传入进去。

 app_process 里面定义了三种应用程序类型:

   1. Zygote: com.android.internal.os.ZygoteInit

   2. SystemServer 不单独启动,而是由 Zygote 启动

   3. 其他指定类名的 Java 程序

什么时Runtime?

Runtime 是支撑程序运行的基础库,它是与语言绑定在一起的。比如:

  \bullet C Runtim: 就是 C standard lib,也就是我们常说的 libc。(有意思的是,Wiki 会自动将" C Runtime" 重定向到"C Standard Library")。

  \bullet Java Runtime: 同样,Wiki 将其重定向到" Java Virtual Machine",这里当然包括 Java 的支撑类库(.jar)。

  \bullet AndroidRuntime: 显而易见,就是为 Android 应用运行所需要的运行时环境。这个环境包括以下内容:

        1. Dalvik VM: Android 的 Java VM, 解释运行 Dex 格式 Java 程序。每个进程运行一个虚拟机(什么叫运行虚拟机?说白了,就是一些 C 代码,不停的去解释 Dex 格式的二进制码(Bytecode),把它们转成机器码(Machine code),然后执行,当然,限制大多数的 Java 虚拟机都支持 JIT,也就是说,bytecode 可能在运行前就以及被转换成机器码,从而大大提高了性能。过去一个普遍的认识是 Java 程序比 C/C++ 等静态编译的语言慢,但随着 JIT 的介入和发展,这个有已经完全是过去式了,JIT 的动态性运行允许虚拟机根据运行时环境,优化机器码的生成,在某些情况下,Java 甚至可以比 C/C++ 跑得更快,同时又兼具平台无关性的特性。

        2. Android 的 Java 类库,大部分来自于 Apache Hamony,开源的 Java API 实现,如 java.lang, java.util, java.net。但去除了 AWT,Swing 等部件。

        3. JNI: C 和 Java 互掉的接口。

        4. Libc: Android 也有很多 C 代码,自然少不了 libc,注意的是,Android 的 libc 叫 bionic C

// \frameworks\base\core\jni\androidRuntime.cpp start() L1091
void AndroidRuntime::start(const char* className, const Vector<String8>&
options, bool zygote)
{
...
JNIEnv* env;
//JNI_CreateJavaVM L1015
if (startVm(&mJavaVM, &env, zygote) != 0) {
return;
}
onVmCreated(env);
/*
* Register android functions.
*/
if (startReg(env) < 0) {
ALOGE("Unable to register all android natives\n");
return;
}
...
}

  \bullet 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 的接口。

System Server 启动流程

System Server 是 Zygote fork 的第一个 java 进程,这个进程非常重要,因为他们有很多的系统线程,提供所有核心的系统服务。

看到大名鼎鼎的 WindowManager,ActivityManager 了吗?对了,它们都是运行在 system_server 的进程里。还有很多 "Binder-x" 的线程,它们是各个 Service 为了响应应用程序远程调用请求而创建的。除此之外,还有很多内部线程,比如”UI thread", "InputReader", "InputDispatch" 等等,现在我们只关心 System Server 是如何创建起来的。

SystemServer 的 main() 函数。

public static void main(String[] args) {
new SystemServer().run();
}

接下来我们分成4部分详细分析 SystemServer.run 方法的初始化流程:

\bullet 初始化必要的 SystemServer 环境参数,比如系统时间、默认时区,语言、load 一些 Library 等等。

\bullet 初始化 Looper,我们在主线程中使用到的 looper 就是在 SystemServer 中进行初始化的

\bullet 初始化 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 的方法:

static ContextImpl createSystemContext(ActivityThread mainThread) {
LoadedApk packageInfo = new LoadedApk(mainThread);
//省略代码
return context;
}
static ContextImpl createSystemUiContext(ContextImpl systemContext) {
final LoadedApk packageInfo = systemContext.mPackageInfo;
//省略代码
return context;
}
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk
packageInfo) {
if (packageInfo == null) throw new
IllegalArgumentException("packageInfo");
//省略代码
return context;
}
static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, ActivityInfo activityInfo, IBinder
activityToken, int displayId,
Configuration overrideConfiguration) {
//省略代码
return context;
}

初始化 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、DisplayMangerService、ActivityManagerService 等等启动以基本的核心 Service,很简单,只有三个 BatteryService、UsageStatsService、WebViewUpdateService 启动其它需要用到的 Service 比如 NetworkScoreService、AlarmManagerService

Fork 函数

pid_t fork(void)
1. 参数:不需要参数
2. 需要的头文件 <sys/types.h> <unistd.h>
3. 返回值分两种情况:
        返回0 表示成功创建子进程,并且接下来进入子进程执行流程
        返回PID >0 ),成功创建子进程,并且继续执行父进程流程代码
        返回非正数(<0 ),创建子进程失败,失败原因主要有:
        进程数超过系统所能创建的上限,errno 会被设置为 EAGAIN 系统内存不足, errno 会被设置为
        ENOMEM

使用 fork() 函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空
间:包括进程上下文(进程执行活动全过程的静态描述)、进程堆栈、打开的文件描述符、信号控
制设定、进程优先级、进程组号等。子进程所独有的只有它的进程号,计时器等(只有小量信
息)。因此,使用 fork() 函数的代价是很大的

         

总结

1. init 根据 init.rc 运行 app_process,并携带“-zygote" 和"-startSystemServer"参数。

2. AndroidRuntime.cpp::start() 里将启动 JavaVM,并且注册所有 framework 相关的系统 JNI 接口

3. 第一次进入 Java 世界,运行 ZygoteInit.java::main() 函数初始化 Zygote,并创建 Socket 的 Server 端。

4. 然后 fork 一个新的进程并在新进程里初始化 SystemServer.Fork 之前,Zygote 是 preload 常用的 Java 类库,以及系统的 resource,同时 GC 清理内存空间,为子进程省去重复的工作。

5. SystemServer 将所有的系统 Service 初始化,包括 ActivityManager 和 WindowManager,他们是应用程序运行起来的前提。

6. 依次同时,Zygote 监听服务端 Socket,等待新的应用启动请求。

7. ActivityManager ready 之后寻找系统的 “Startup" Application,将请求发给 Zygote。

8. Zygote 收到请求后,fork 出一个新的进程。

9. Zygote 监听并处理 SystemServer 的 SIGCHID 信号,一旦 SystemServer 崩溃,立即将自己 kill 掉。init 会重启 Zygote。

面试题

1. 你了解 Android 系统启动流程吗?
答:当按电源键触发开机,首先会从 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 ,最终就看到了手机的 " 桌面 "
2.  system_server 为什么要在 Zygote 中启动,而不是由 init 直接启动呢?
答:Zygote 作为一个孵化器,可以提前加载一些资源,这样 fork() 时基于 Copy-On-Write 机制创建的其 他进程就能直接使用这些资源,而不用重新加载。比如 system_server 就可以直接使用 Zygote 中的 JNI 函数、共享库、常用的类、以及主题资源。
3. 为什么要专门使用 Zygote 进程去孵化应用进程,而不是让 system_server 去孵化呢?
答:首先 system_server 相比 Zygote 多运行了 AMS WMS 等服务,这些对一个应用程序来说是不需要 的。另外进程的 fork() 对多线程不友好,仅会将发起调用的线程拷贝到子进程,这可能会导致死锁,而 system_server 中肯定是有很多线程的。
4. 能说说具体是怎么导致死锁的吗?
答:在 POSIX 标准中, fork 的行为是这样的:复制整个用户空间的数据(通常使用 copy-on-write 的策略, 所以可以实现的速度很快)以及所有系统对象,然后仅复制当前线程到子进程。这里:所有父进程中别 的线程,到了子进程中都是突然蒸发掉的,对于锁来说,从 OS 看,每个锁有一个所有者,即最后一次 lock 它的线程。假设这么一个环境,在 fork 之前,有一个子线程 lock 了某个锁,获得了对锁的所有权。 fork 以后,在子进程中,所有的额外线程都 人间蒸发了。而锁却被正常复制了,在子进程看来,这个锁没有主人,所以没有任何人可以对它解锁。 当子进程想 lock 这个锁时,不再有任何手段可以解开了。程序发生死锁
5.  Zygote 为什么不采用 Binder 机制进行 IPC 通信?
答:Binder 机制中存在 Binder 线程池,是多线程的,如果 Zygote 采用 Binder 的话就存在上面说的 fork() 与 多线程的问题了。其实严格来说, Binder 机制不一定要多线程,所谓的 Binder 线程只不过是 在循环读取 Binder 驱动的消息而已,只注册一个 Binder 线程也是可以工作的,比如 service manager 就是这样的。实际上 Zygote 尽管没有采取 Binder 机制,它也不是单线程的,但它在 fork() 前主动停止 了其他线程,fork() 后重新启动了。

猜你喜欢

转载自blog.csdn.net/qq_44950283/article/details/129355893