Android系统启动过程

参考链接:

《深入理解Android 卷I》
《Linux 的启动流程》
《Android系统架构开篇》

引言

Google官方提供的经典分层架构图,从下往上依次分为Linux内核、HAL、系统Native库和Android运行时环境、Java框架层以及应用层这5层架构,其中每一层都包含大量的子模块或子系统。

Android系统架构

这幅图很清晰的展现了Android系统从下往上的层次关系,这种关系恰好也是整个系统启动大过程的缩影。

init是Linux系统中用户空间的第一个进程,要搞清楚Android的启动过程,我们先来看一下Linux系统的启动过程。

Linux启动过程

Linux启动过程

第一步:加载内核

操作系统接管硬件以后,首先读入 /boot 目录下的内核文件

第二步:启动初始化进程

内核文件加载以后,就开始运行第一个程序 /sbin/init,它的作用是初始化系统环境。

由于init是第一个运行的程序,它的进程编号(pid)就是 1。其他所有进程都从它衍生,都是它的子进程。

第三步:确定运行级别

许多程序需要开机启动。它们在Windows叫做”服务”(service),在Linux就叫做”守护进程”(daemon)。

init进程的一大任务,就是去运行这些开机启动的程序。但是,不同的场合需要启动不同的程序,比如用作服务器时,需要启动Apache,用作桌面就不需要。Linux允许为不同的场合,分配不同的开机启动程序,这就叫做”运行级别”(runlevel)。也就是说,启动时根据”运行级别”,确定要运行哪些程序。

第四步:加载开机启动程序

init进程逐一加载开机启动程序,其实就是运行/etc/init.d目录里的启动脚本。

第五步、用户登录

开机启动程序加载完毕以后,就要让用户登录了。一般来说,用户的登录方式有三种:

  1. 命令行登录
  2. ssh登录
  3. 图形界面登录

第六步、进入 login shell

所谓shell,简单说就是命令行界面,让用户可以直接与操作系统对话。用户登录时打开的shell,就叫做login shell。

第七步,打开 non-login shell

上一步完成以后,Linux的启动过程就算结束了,用户已经可以看到命令行提示符或者图形界面了。用户进入操作系统以后,常常会再手动开启一个shell。这个shell就叫做 non-login shell,意思是它不同于登录时出现的那个shell,不读取/etc/profile和.profile等配置文件。它会读入用户自己的bash配置文件 ~/.bashrc。大多数时候,我们对于bash的定制,都是写在这个文件里面的。

init过程分析

上面我们看到了linux系统的启动过程,Android系统是基于Linux内核的,所以init也是Android系统中用户空间的第一个进程。

init的入口函数是main,源码中main方法代码很长,下面简要来分析一下做了什么操作。

/system/core/init/init.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
int main(int argc, char **argv)
{
int device_fd = -1;
int property_set_fd = -1;
int signal_recv_fd = -1;
int keychord_fd = -1;
int fd_count;
int s[2];
int fd;
struct sigaction act;
char tmp[PROP_VALUE_MAX];
struct pollfd ufds[4];
char *tmpdev;
char* debuggable;

//设置子进程退出的信号处理函数,该函数为sigchld_handler
act.sa_handler = sigchld_handler;
act.sa_flags = SA_NOCLDSTOP;
act.sa_mask = 0;
act.sa_restorer = NULL;
sigaction(SIGCHLD, &act, 0);

//umask设置用户创建文件的默认权限
umask(0);

//创建一些文件夹并挂载设备
mkdir("/dev", 0755);
mkdir("/proc", 0755);
mkdir("/sys", 0755);
mount("tmpfs", "/dev", "tmpfs", 0, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
mount("proc", "/proc", "proc", 0, NULL);
mount("sysfs", "/sys", "sysfs", 0, NULL);

//重定向标注输入输出到/dev/_null_
open_devnull_stdio();

//设置日志的输出设备为/dev/__ksmg__
log_init();

//解析init.rc配置文件
INFO("reading config file\n");
parse_config_file("/init.rc");

//qemu可以虚拟主机实现对系统的引导
qemu_init();

//读取机器的Hardware名
get_hardware_name();
snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);

//解析和这个机器相关的配置文件,手机对应的配置文件为init.bravo.rc
parse_config_file(tmp);

//执行配置文件中的action动作
action_for_each_trigger("early-init", action_add_queue_tail);
drain_action_queue();

//创建利用Uevent与Linux内核交互的socket
INFO("device init\n");
device_fd = device_init();

//初始化和属性相关的资源
property_init();

//.....more.....

//从这里init进入一个死循环...
for(;;) {
int nr, i, timeout = -1;

for (i = 0; i < fd_count; i++)
ufds[i].revents = 0;

drain_action_queue();
restart_processes();

if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}

}

结合上面分析,可以将init的过程重点总结为以下四点:

  1. 解析两个配置文件(init.rc和init.bravo.rc)
  2. 执行各个阶段的动作,创建zygote的工作就是在某个阶段完成的。
  3. 调用property_init初始化属性相关资源。
  4. init进入无限循环。

解析init.rc

在init中会解析init.rc和init.bravo.rc两个配置文件,其中bravo是平台名称,这两个配置文件都是通过调用parse_config_file函数来解析的,解析函数如下:

1
2
3
4
5
6
7
8
9
10
int parse_config_file(const char *fn)
{
char *data;
data = read_file(fn, 0);
if (!data) return -1;

parse_config(fn, data);
DUMP();
return 0;
}

可以看到parse_config_file方法实际上只是一个读取文件的方法,真正的解析是在parse_config函数中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
static void parse_config(const char *fn, char *s)
{
struct parse_state state;
char *args[SVC_MAXARGS];
int nargs;

nargs = 0;
state.filename = fn;
state.line = 1;
state.ptr = s;
state.nexttoken = 0;
//设置解析函数,不同的内容用不同的函数
state.parse_line = parse_line_no_op;
for (;;) {
switch (next_token(&state)) {
case T_EOF:
state.parse_line(&state, 0, 0);
return;
case T_NEWLINE:
if (nargs) {
//得到关键字的类型
int kw = lookup_keyword(args[0]);
//判断关键字是不是SECTION
if (kw_is(kw, SECTION)) {
state.parse_line(&state, 0, 0);
//解析这个SECTION
parse_new_section(&state, kw, nargs, args);
} else {
state.parse_line(&state, nargs, args);
}
nargs = 0;
}
break;
case T_TEXT:
if (nargs < SVC_MAXARGS) {
args[nargs++] = state.text;
}
break;
}
}
}

上面的next_token方法会拆解配置文件,parse_config首先会找到配置文件的一个section然后针对不同的section使用不同的解析函数来解析。

keywords.h中定义了init过程中使用的关键字。

/system/core/init/keywords.h

1
2
3
4
5
6
7
8
9
10
11
12

#define KEYWORD(symbol, flags, nargs, func) K_##symbol,

//....

KEYWORD(on, SECTION, 0, 0)
KEYWORD(oneshot, OPTION, 0, 0)
KEYWORD(onrestart, OPTION, 0, 0)
KEYWORD(restart, COMMAND, 1, do_restart)
KEYWORD(service, SECTION, 0, 0)

//....

可以看到symbol为on或者service的时候标识section。

打开配置文件init.rc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
on init //on关键字标示一个section,对应名字是init,直到下一个section开始下面的代码都属于这个section

//.....

on boot

//.....

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
socket zygote stream 666
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media

//.....

一个section的内容从这个标识section的关键字开始到下一个标识section的地方结束。init.rc中定义了boot和init动作执行,另外发现zygote被放在了一个service section中。

理解zygote

zygote本身是一个Native的应用程序,与驱动、内核等无关,上面我们知道zygote是有init进程通过fork而来。

zygote的main函数中的重要功能是通过AppRuntime的start来完成的。

/frameworks/base/core/jni/AndroidRuntime.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
void AndroidRuntime::start(const char* className, const bool startSystemServer)
{
LOGD("\n>>>>>>>>>>>>>> AndroidRuntime START <<<<<<<<<<<<<<\n");

char* slashClassName = NULL;
char* cp;
JNIEnv* env;

blockSigpipe();

/*
* 'startSystemServer == true' means runtime is obslete and not run from
* init.rc anymore, so we print out the boot start event here.
*/
if (startSystemServer) {
/* track our progress through the boot sequence */
const int LOG_BOOT_PROGRESS_START = 3000;
LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START,
ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
}

const char* rootDir = getenv("ANDROID_ROOT");
if (rootDir == NULL) {
rootDir = "/system";
if (!hasDir("/system")) {
LOG_FATAL("No root directory specified, and /android does not exist.");
goto bail;
}
setenv("ANDROID_ROOT", rootDir, 1);
}

//const char* kernelHack = getenv("LD_ASSUME_KERNEL");
//LOGD("Found LD_ASSUME_KERNEL='%s'\n", kernelHack);

/* [1]创建虚拟机 */
if (startVm(&mJavaVM, &env) != 0)
goto bail;

/*
* [2]注册JNI函数
*/
if (startReg(env) < 0) {
LOGE("Unable to register all android natives\n");
goto bail;
}

/*
* We want to call main() with a String array with arguments in it.
* At present we only have one argument, the class name. Create an
* array to hold it.
*/
jclass stringClass;
jobjectArray strArray;
jstring classNameStr;
jstring startSystemServerStr;

stringClass = env->FindClass("java/lang/String");
assert(stringClass != NULL);
strArray = env->NewObjectArray(2, stringClass, NULL);
assert(strArray != NULL);
classNameStr = env->NewStringUTF(className);
assert(classNameStr != NULL);
env->SetObjectArrayElement(strArray, 0, classNameStr);
startSystemServerStr = env->NewStringUTF(startSystemServer ?
"true" : "false");
env->SetObjectArrayElement(strArray, 1, startSystemServerStr);

/*
* Start VM. This thread becomes the main thread of the VM, and will
* not return until the VM exits.
*/
jclass startClass;
jmethodID startMeth;

slashClassName = strdup(className);
for (cp = slashClassName; *cp != '\0'; cp++)
if (*cp == '.')
*cp = '/';

startClass = env->FindClass(slashClassName);
if (startClass == NULL) {
LOGE("JavaVM unable to locate class '%s'\n", slashClassName);
/* keep going */
} else {
//[3]通过JNI调用Java函数,这里调用的main函数
startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");
if (startMeth == NULL) {
LOGE("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
}
}

LOGD("Shutting down VM\n");
if (mJavaVM->DetachCurrentThread() != JNI_OK)
LOGW("Warning: unable to detach main thread\n");
if (mJavaVM->DestroyJavaVM() != 0)
LOGW("Warning: VM did not shut down cleanly\n");

bail:
free(slashClassName);
}

通过上面代码,我们找到了三个关键点([1][2][3])

  1. 创建虚拟机
  2. 注册JNI函数
  3. 通过JNI调用Java函数,这里调用的main函数

它们共同组成了开创 Android 系统中Java世界的三部曲。zygote 是Android系统中Java世界的盘古,它创建了第一个Java虚拟机并创建了framework的核心system_service 进程。

在Java世界的入口里面,最终CallStaticVoidMethod调用了com.android.internal.os.ZygoteInit.java的main函数。至此进入了Java的世界。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public static void main(String argv[]) {
try {
// Start profiling the zygote initialization.
SamplingProfilerIntegration.start();

//注册zygote使用的socket
registerZygoteSocket();
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
SystemClock.uptimeMillis());

//预加载java类和资源
preloadClasses();
//cacheRegisterMaps();
preloadResources();
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
SystemClock.uptimeMillis());

if (SamplingProfilerIntegration.isEnabled()) {
SamplingProfiler sp = SamplingProfiler.getInstance();
sp.pause();
SamplingProfilerIntegration.writeZygoteSnapshot();
sp.shutDown();
}

// 强制执行一次垃圾回收
gc();

// If requested, start system server directly from Zygote
if (argv.length != 2) {
throw new RuntimeException(argv[0] + USAGE_STRING);
}

if (argv[1].equals("true")) {

//最终进入这里了。。。启动system_service进程
startSystemServer();
} else if (!argv[1].equals("false")) {
throw new RuntimeException(argv[0] + USAGE_STRING);
}

Log.i(TAG, "Accepting command socket connections");

if (ZYGOTE_FORK_MODE) {
runForkMode();
} else {
runSelectLoopMode();
}

closeServerSocket();
} catch (MethodAndArgsCaller caller) {
caller.run();
} catch (RuntimeException ex) {
Log.e(TAG, "Zygote died with exception", ex);
closeServerSocket();
throw ex;
}
}

上面大致完成了下面几件事情:

  1. 在ZygoteInit的main函数中,建立了IPC通信服务端(registerZygoteSocket),zygote及其系统中其他程序的通信没有使用Binder而是采用了基于AF_UNIX类型的socket.
  2. 预加载类和资源
  3. 启动了system_service
  4. zygote进入runSelectLoopMode

SystemService的进程名叫system_service,它是Zygote创建的第一个进程,SystemService调用zygoteInitNative后与Binder通讯系统建立联系。

整个过程总结
[1] ZygoteInit调用startSystemService创建system_service进程
[2] system_service调用handleSystemServiceProcess完成自己的使命
[3] handleSystemServiceProcess抛出异常,最终调用com.android.service.SystemService的main函数
[4] main函数加载libandroid_service.so并调用native的init1函数
[5] init1函数通过JNI调用com.android.service.SystemService类的init2函数,init2函数创建一个线程,用于加载各种service
[6] init1函数最终加入Binder通信系统

关于各个进程之间通信方式关系图如下,在后面继续看相关通信过程:

Android系统启动过程