跳转至

翻译:Ross.Zeng 校对:

系统启动

通过分析安卓启动过程能够很好地把概念融会贯通。如图2-6所示,首先是CPU开始动作。CPU会从某个硬编码地址获取第一条指令,这个地址一般指向芯片的bootloader编程区域。接着bootloader初始化RAM,将基础硬件置为静止状态,加载内核与RAM区域并进入内核。近来的CPU及单芯片外设类的带片上系统设备,已经可以由格式化SD卡等设备上启动了。如pandaBoard和BeagleBorad的新版本就可以直接由SD卡启动,无需依赖板上的存储芯片。

内核初始化与硬件密切相关,但它的目的就是尽早地让CPU开始执行C代码。一旦满足条件,内核就能够执行与架构独立的start_kernel()方法,初始化一些子系统,并为内核驱动启动"init"方法。内核启动时一大片日志信息就是在这几步中打印的。随后内核开始挂载根文件系统并启动初始化进程。

Android's boot sequence 图2-6 安卓启动流程

初始化成功后,安卓开始执行/init.rc中的指令并构建起如系统路径的一系列环境变量,创建挂载点,挂载文件系统,设置OOM,启动原生守护进程。我们已经介绍过一些安卓中的原生守护进程,但也值得再花些精力讨论Zygote。Zygote的特殊之处在于它是负责启动应用的守护进程,它会整合应用程序共用的组件以缩短应用打开的时间。初始化并没有直接启动Zygote,而是通过app_process命令在ART时启动,ART启动系统第一个Dalvik虚拟机并告诉它调用Zygote的main()方法。

Zygote只会在启动新应用时激活。为了实现应用的更快速启动,Zygote会在运行时预加载应用程序所需的Java类和资源。这使得系统RAM载入更加高效。接着,Zygote会在自己的套接字(/dev/socket/zygote)上监听启动新应用的连接请求。当它收到启动应用程序的请求时,就会fork一份自己并启动新程序。这个做法的美妙之处在于,应用从Zygote中fork而来就立即拥有了可能需要的类与资源。换言之,一个新应用程序启动不需要等待所需资源的加载与执行。

这些努力能够实现,是因为Linux内核为fork提供了~~大奶牛~~写时拷贝策略。在Unix中fork创建的新进程与父进程完全相同。而通过写时拷贝,Linux实际上不需要拷贝任何东西,相反,它将新进程页映射到父进程页,只有当新进程写入这页时才会执行拷贝。其中类与资源加载是不会被写入的,因为它们都是默认的,并且在系统的生命周期中几乎不发生变化。所有直接由Zygote fork进程的本质都是使用自身映射的拷贝。因此,无论系统中跑了多少应用程序,RAM中只会加载一份系统类与资源的拷贝。

尽管Zygote被设计用于监听fork新应用请求的连接,有一个“应用”却是被Zygote显式启动:系统服务器。系统服务器是Zygote启动的第一个应用, 并独立于父进程一直存在。然后系统服务器开始初始化每个系统服务,并注册到之前启动的服务管理器中。如活动管理器启动,就会通过发送Intent.CATEGORY_HOME类型意图结束初始化,这将打开启动器程序,继而将用户们熟悉的主界面展示出来。

当用户点击主界面上的图标,启动器告诉活动管理器开启进程,请求被转发给Zygote,由Zygote fork并启动一个新程序,最终能够展示出来。

一旦系统完成启动,进程列表看起来如下:

省略了很多,可以自己在终端ps一下

# ps USER PID PPID VSIZE RSS WCHAN PC NAME (for example) root 1 0 268 180 c009b74c 0000875c S /init root 2 0 0 0 0 c004e72c 00000000 S kthreadd ... ...

这个输出来自于安卓模拟器,所以包含了如qemud这样模拟器特有的进程。注意尽管是由Zygote fork出来,它们是以完整包名展示。有一个小技巧,通过系统的prctl()方法带上PR_SET_NAME参数可以让内核改变调用进程的名称。如果有兴趣可以看一下prctl()的命令手册。另一个需要注意的地方是init启动的第一个进程是ueventd。在此之前,所有进程都是由子系统或驱动程序从内核中启动的。