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系统启动过程

评论

Ajax Android AndroidStudio Animation Anroid Studio AppBarLayout Babel Banner Buffer Bulma ByteBuffer C++ C11 C89 C99 CDN CMYK COM1 COM2 CSS Camera Raw, 直方图 Chrome Class ContentProvider CoordinatorLayout C语言 DML DOM Dagger Dagger2 Darktable Demo Document DownloadManage ES2015 ESLint Element Error Exception Extensions File FileProvider Flow Fresco GCC Git GitHub GitLab Gradle Groovy HTML5 Handler HandlerThread Hexo Hybrid I/O IDEA IO ImageMagick IntelliJ Intellij Interpolator JCenter JNI JS Java JavaScript JsBridge Kotlin Lab Lambda Lifecycle Lint Linux Looper MQTT MVC MVP Maven MessageQueue Modbus Momentum MySQL NDK NIO NexT Next Nodejs ObjectAnimator Oracle VM Permission PhotoShop Physics Python RGB RS-232 RTU Remote-SSH Retrofit Runnable RxAndroid RxJava SE0 SSH Spring SpringBoot Statubar Style Task Theme Thread Tkinter UI UIKit UML VM virtualBox VS Code VUE ValueAnimator ViewPropertyAnimator Vue Vue.js Web Web前端 Workbench api apk bookmark by关键字 cli compileOnly computed css c语言 databases demo hexo hotfix html iOS icarus implementation init jQuery javascript launchModel logo merge methods mvp offset photos pug query rxjava2 scss servlet shell svg tkinter tomcat transition unicode utf-8 vector virtual box vscode watch webpack 七牛 下载 中介者模式 串口 临潼石榴 主题 书签 事件 享元模式 仓库 代理模式 位运算 依赖注入 修改,tables 光和色 内存 内核 内部分享 函数 函数式编程 分支 分析 创建 删除 动画 单例模式 压缩图片 发布 可空性 合并 同向性 后期 启动模式 命令 命令模式 响应式 响应式编程 图层 图床 图片压缩 图片处理 图片轮播 地球 域名 基础 增加 备忘录模式 外观模式 多线程 大爆炸 天气APP 太白山 头文件 奇点 字符串 字符集 存储引擎 宇宙 宏定义 实践 属性 属性动画 岐山擀面皮 岐山肉臊子 岐山香醋 工具 工厂模式 年终总结 开发技巧 异常 弱引用 恒星 打包 技巧 指令 指针 插件 插值 摄影 操作系统 攻略 故事 数据库 数据类型 数组 文件 新功能 旅行 旋转木马 时序图 时空 时间简史 曲线 杂谈 权限 枚举 架构 查询 标准库 标签选择器 样式 核心 框架 案例 桥接模式 检测工具 模块化 模板 模板引擎 模板方法模式 油泼辣子 泛型 洛川苹果 浅色状态栏 渲染 源码 源码分析 瀑布流 热修复 版本 版本控制 状态栏 状态模式 生活 留言板 相册 相对论 眉县猕猴桃 知识点 码云 磁盘 科学 笔记 策略模式 类图 系统,发行版, GNU 索引 组件 组合模式 绑定 结构 结构体 编码 网易云信 网格布局 网站广播 网站通知 网络 美化 联合 脚手架 膨胀的宇宙 自定义 自定义View 自定义插件 蒙版 虚拟 虚拟机 补码 补齐 表单 表达式 装饰模式 西安 观察者模式 规范 视图 视频 解耦器模式 设计 设计原则 设计模式 访问者模式 语法 责任链模式 贪吃蛇 转换 软件工程 软引用 运算符 迭代子模式 适配器模式 选择器 通信 通道 配置 链表 锐化 错误 键盘 闭包 降噪 陕西地方特产 面向对象 项目优化 项目构建 黑洞
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×