1.0 前言
当按下终端的电源键到看到Home界面,这个过程需要经过Android系统体系结构中的内核层、运行层、框架层、应用层。本文只介绍到SystemServer启动(代码基于android 8.1),引用gityuan的一张整体框架图。
1.1 内核启动前准备
当电源键按下之后,引导芯片的代码会从预定义开始的地方开始执行,加载引导程序到RAM运行。引导程序可以在/bootable/bootloader中找到,由于引导程序各个设备制造商各不相同,不是Android系统的一部分。引导程序主要分为两个阶段执行:
第一个阶段为检测外部的RAM和加载对第二阶段有用的程序;
第二阶段为设置网络、内存等,为内核启动准备条件。
1.2 内核启动流程
Android系统本质上是Linux内核的操作系统。Linux内核启动主要涉及到三个进程,idle进程(PID=0), init进程(PID=1),kthreadd进程(PID=2)。
idle进程是Linux系统的第一个进程,是init和kthreadd进程的父进程。
init进程是Linux系统的第一个用户进程,是Android系统应用程序的首个进程。
kthreadd进程是Linux系统内核管理进程,所有的内核线程都是间接或直接以它为父进程。
1.2.1 idle进程启动
idle进程最开始的名字为init_task,后期退化为idle,在完成初始化操作后,主要负责进程的调度、交换。
idle进程启动对应的文件在/kernel/msm-3.18/arch/arm64/kernel/head.S
1 | /* |
这里的比较重要的一句是b start_kernel,跳转到start_kernel函数。
start_kernel函数在/kernel/msm-3.18/init/main.c
1 | asmlinkage __visible void __init start_kernel(void) |
可以看出这里主要进行的是各种初始化,最后调用的是rest_init,rest_init创建了Linux系统中最重要的两个进程init和kthreadd,最后把init_task进程变为idle进行,开始无限循环,负责进程调度。
1 | static noinline void __init_refok rest_init(void) |
里面各个函数详细的介绍参考:https://github.com/foxleezh/AOSP/issues/3
1.2.2 kthreadd进程启动
kthreadd进程启动,在文件/kernel/msm-3.18/kernel/kthread.c中
1 | int kthreadd(void *unused) |
1.2.3 init进程启动
init进程分为两个部分,第一部分是在内核启动的,主要完成创建和内核初始化工作;第二部分是在用户空间启动的,主要完成Android系统的初始化工作。
1 | static int __ref kernel_init(void *unused) |
kernel_init主要完成了一些初始化操作,然后再系统目录下找到ramdisk_execute_command 和execute_command设置的应用程序,如果这两个目录找不到,就依次从/sbin/init,/etc/init,/bin/init,/bin/sh这四个应用程序中启动,只要有一个应用程序启动,其他就不启动。ramdisk_execute_command 和execute_command是通过bootloader传递过来的参数设置的。
Android系统一般会在根目录下放一个init的可执行文件。在linux系统的init进程在内核初始化完成后,就直接执行init这个文件,这个文件的源代码在/system/core/init/init.cpp中,而后开始Android系统的init进程。
init第二阶段(/system/core/init/init.cpp)
主要的工作是初始化属性系统,解析SELinux的匹配规则,处理子进程终止信号,启动系统属性服务。由于init进程执行的操作很多,不可能一行行代码去做,所以Android系统引入了init.rc。init.rc是init进程启动的配置脚本,这个脚本是用一种Android Init Language的语言编写,这个语法的定义在/system/core/init/README.md文件中。在Android7.0之前只解析根目录下的init.rc文件,但随着版本的叠加,越来越臃肿,所以以后的版本init.rc一些业务被拆分到/system/etc/init,/vendor/etc/init,/odm/etc/init中。
1 | int main(int argc, char** argv) { |
init进程解析.rc文件(/system/core/rootdir/的目录下),会启动一些service,这些service不是普通的服务,是守护进程(daemon)。守护进程是在系统初始化时启动,一直运行在后台,直到系统关闭时终结。Android系统的母进程zygote进程就是在这个时候启动的,zygote进程主要负责Java虚拟机,加载系统资源,启动SystemServer进程。
1.3 zygote进程启动
在文件/system/core/rootdir/init.rc中看到start zygote来启动zygote进程
1 | # It is recommended to put unnecessary data/ initialization from post-fs-data |
zygote服务的定义在头文件中有声明import /init.${ro.zygote}.rc
ro.zygote的属性值是不同的硬件厂商定制
在/system/core/rootdir/目录下可以看到四种不同的zygote相关的文件
init.zygote32.rc: zygote进程对应的执行程序是app_process(纯32bit模式)
init.zygote64.rc: zygote进程对应的执行程序是app_process64(纯64bit模式)
init.zygote32_64.rc:启动两个zygote进程(名为zygote和zygote_secondary),对应的执行程序分别是app_process32(主模式),app_process64
init.zygote64_32.rc:启动两个zygote进程(名为zygote和zygote_secondary),对应的执行程序分别是app_process64(主模式),app_process32
之所以出现这种定义是因为Android5.0以后开始支持64位程序,为了兼容32位和64位才这样定义,不同的zygote文件相差不大,主要是启动32位还是64位的进程。下面以init.zygote32_64.rc为例
1 | service zygote /system/bin/app_process32 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote |
app_processXX为终端目录下的可执行文件,其对应的源码文件是
/frameworks/base/cmds/app_process/app_main.cpp,其main函数如下,函数中主要是参数解析,通过解析的结果来判断启动的模式,这里有两种启动模式。
zygote模式,在初始化zygote进程,传递的参数有–start-system-server –socket-name=zygote,前者表示启动SystemService,后者指定socket的名称
application模式,启动普通的应用程序,传递的参数有class名字以及class带的参数。
解析完成后最终调用AppRuntime对象的start函数,加载ZygoteInit或RuntimeInit两个Java类,并传递相应的参数。
1 | int main(int argc, char* const argv[]) |
创建虚拟机,调用ZygoteInit函数
runtime.start函数在/frameworks/base/core/jni/AndroidRuntime.cpp中
主要是初始化JNI,然后创建虚拟机,注册JNI函数,而后通过反射调用ZygoteInit类中的main函数。
1 | void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) |
/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
在zygoteInit中首先设置了Java虚拟机的堆内存,然后启动了一个类加载器加载Android启动依赖的类比如Activity等四大组件,dialog等UI的类,然后分出一个子系统启动SystemServer服务
1 | @UnsupportedAppUsage |
1 | static void preload(TimingsTraceLog bootTimingsTraceLog) { |
zygote进程内加载了preload()方法中的所有资源,当需要fork新进程时,采用copy on write技术,如下:
fork()采用copy on write技术,这是linux创建进程的标准方法,调用一次,返回两次,返回值有3种类型。
- 父进程中,fork返回新创建的子进程的pid;
- 子进程中,fork返回0;
- 当出现错误时,fork返回负数。(当进程数超过上限或者系统内存不足时会出错)
fork()的主要工作是寻找空闲的进程号pid,然后从父进程拷贝进程信息,例如数据段和代码段,fork()后子进程要执行的代码等。 Zygote进程是所有Android进程的母体,包括system_server和各个App进程。zygote利用fork()方法生成新进程,对于新进程A复用Zygote进程本身的资源,再加上新进程A相关的资源,构成新的应用进程A。
copy-on-write过程:当父子进程任一方修改内存数据时(这是on-write时机),才发生缺页中断,从而分配新的物理内存(这是copy操作)。
copy-on-write原理:写时拷贝是指子进程与父进程的页表都所指向同一个块物理内存,fork过程只拷贝父进程的页表,并标记这些页表是只读的。父子进程共用同一份物理内存,如果父子进程任一方想要修改这块物理内存,那么会触发缺页异常(page fault),Linux收到该中断便会创建新的物理内存,并将两个物理内存标记设置为可写状态,从而父子进程都有各自独立的物理内存。
1.4 SystemServer启动
forkSystemServer函数将会启动SystemServer进程
1 | /** |
Zygote.forkSystemServer
1 | public static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, |
handleSystemServerProcess方法
此处systemServerClasspath
环境变量主要有/system/framework/目录下的services.jar,ethernet-service.jar, wifi-service.jar这3个文件
1 | /** |
ZygoteInit.zygoteInit最后调用的是这个方法,这样就进入到了SystemServer类的main()方法。
1 | /** |
1.5 总结
上面详细介绍了SystemServer进程的启动过程,在这个启动过程中,主要是一系列进程的启动,从内核启动的三个进程idle进程、kthreadd进程、init进程最后到zygote进程启动,通过zygote进程来启动SystemServer进程,最后通过反射的方法调用了SystemServer的main方法,下节将会介绍详细的SystemServer中的启动流程。
附录
本文设计的源码文件路径
1 | /kernel/msm-3.18/arch/arm64/kernel/head.S |