前言
做过接近两年的android,特整理文档。第一篇,先了解android系统的启动流程。主要讲的是从init进程开始。主要讲的是基于Android M的开机启动流程介绍,当然也会分析下Android N版本的启动流程。
Android 系统的平台架构
Android 系统的底层是建立在Linux系统之上,该平台是由应用层(System apps),应用框架层(framework),系统运行库层(C/C++程序库和Android运行时库ART),硬件抽象层(HAL),Linux 内核层构成。
做应用开发的同学,手上肯定有一本书叫做 《android疯狂讲义》,在这本书的一开始就贴出了这张android系统的体系架构图。每一歌部分的功能不再做介绍,有兴趣的同学可以自行查询。
而android的启动流程大体分为三个阶段,1 bootloader引导 2 启动Kernel 3 启动Android 细分如下图所示
第一步:启动电源以及系统启动
当电源按下,引导芯片代码开始从预定义的地方(固化在ROM)开始执行。加载引导程序到RAM,然后执行。
第二步:系统引导
相关代码在(bootable\bootloader)
加电后,CPU先执行bootloader程序,正常启动系统,加载boot.img,boot.img中包含内核。
第三步 :内核kernel
由bootloader加载kernel,kernel经自解压、初始化、载入built-in 驱动程序完成启动。Kernel启动后会创建若干内核线程(kernel thread),之后装入并执行程序/sbin/init/,载入init process,切换至user-space。
内核启动时,设置缓存、被保护存储器、计划列表,加载驱动。当内核完成系统设置,它首先在系统文件中寻找”init”文件,然后启动root进程或者系统的第一个进程。
第四步 :Init 进程启动
Android 从linux 系统启动有4个步骤: 1.Init 进程启动 2. Zygote服务启动 3. System server.android服务启动 4. HOME启动
Init进程,是第一个由内核启动的用户级进程。用户自行启动(已经被载入内存,开始运行,并已初始化所有设备驱动程序和数据结构等)之后,就通过启动一个用户级程序init的方式,完成引导进程。Init始终是第一个进程。我们可以说它是root进程或者说有进程的父进程。init进程有两个责任,一是挂载目录,比如/sys、/dev、/proc,二是运行init.rc脚本。
Init 进程启动后,根据Init.rc和init.xxx.rc的脚本文件建立基本服务。
init 进程是在kernel/init/main.c文件中启动的,启动过程如下start_kernel()->rest_init()->kernel_init()->run_init_process(“/init”)
run_init_process函数如下,通过传入文件名字,调用do_execve函数,来启动新的程序的,
static int run_init_process(const char *init_filename)
{
argv_init[0] = init_filename;
return do_execve(init_filename,
(const char __user *const __user *)argv_init,
(const char __user *const __user *)envp_init);
}
在 init.cpp main 函数中,首先做的事情的创建一些文件夹并且挂在设备,代码如下
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "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);
接下来是三个函数
open_devnull_stdio();
klog_init()
klog_set_level(KLOG_NOTICE_LEVEL);
open_devnull_stdio()是将标准输入,标准输出以及错误输出都重定向到了/dev/_null_的设备节点,而_null_ 是一个无底洞。
klog_init() 查看源代码的话是 打开了一个/dev/__kmsg__的节点,设定init的输出设备为/dev/__kmsg__
property_init() 主要是为属性分配一些存储空间.
下面到了我们最重要的init_parse_config_file("/init.rc")函数了。init解析。
这个函数是init.rc文件解析的开始函数,我们看它做了什么事。
int init_parse_config_file(const char* path) {
read_file(path, &data) // 将init.rc文件里面的数据读取到data里面
parse_config(path, data); // 对data数据进行解析
dump_parser_state();
}
下面是parse_config 函数,代码较多
static void parse_config(const char *fn, const std::string& data)
{
struct listnode import_list;
struct listnode *node;
char *args[INIT_PARSER_MAXARGS];
int nargs = 0;
parse_state state;
state.filename = fn;
state.line = 0;
state.ptr = strdup(data.c_str()); // c_str():生成一个const char*指针,指向以空字符终止的数组。数组的数据是临时的,当有一个改变这些数据的成员函数被调用后,其中的数据就会失效。因此要么现用先转换,要么把它的数据复制到用户自己可以管理的内存中。
state.nexttoken = 0;
state.parse_line = parse_line_no_op;
list_init(&import_list);
state.priv = &import_list;/* 开始获取每一个token,然后分析这些token,每一个token就是有空格、字表符和回车符分隔的字符串 */
for (;;) {
switch (next_token(&state)) { //查看next_token函数源代码的话,next_token函数相当于词法分析器
case T_EOF: // init.rc 文件分析完毕
state.parse_line(&state, 0, 0);
goto parser_done;
case T_NEWLINE: // 分析每一行命令
state.line++;
if (nargs) {
int kw = lookup_keyword(args[0]); // 这个函数就有意思了,通过对每一行第一个单词进行匹配
if (kw_is(kw, SECTION)) {
state.parse_line(&state, 0, 0);
parse_new_section(&state, kw, nargs, args);
} else {
state.parse_line(&state, nargs, args);
}
nargs = 0;
}
break;
case T_TEXT:
if (nargs < INIT_PARSER_MAXARGS) {
args[nargs++] = state.text;
}
break;
}
}
parser_done:
list_for_each(node, &import_list) {
struct import *import = node_to_item(node, struct import, list);
int ret;
ret = init_parse_config_file(import->filename);
if (ret)
ERROR("could not import file '%s' from '%s'\n",
import->filename, fn);
}
}
int init_parse_config_file(const char* path) {
INFO("Parsing %s...\n", path);
Timer t;
std::string data;
if (!read_file(path, &data)) {
return -1;
}
data.push_back('\n'); // TODO: fix parse_config.
parse_config(path, data);
dump_parser_state();
NOTICE("(Parsing %s took %.2fs.)\n", path, t.duration());
return 0;
}
以下是对与每一行语句进行分析,分类存储
static void parse_new_section(struct parse_state *state, int kw,
int nargs, char **args)
{
switch(kw) {
case K_service:
state->context = parse_service(state, nargs, args);
//如果标志是K-service 调用parse_service()中的 list_add_tail(&service_list, &svc->slist);将数据存储在service_list。
//注意此时parse_service中的svc对象基本上是一个空壳,因为相关的options还没有解析
if (state->context) {
state->parse_line = parse_line_service;//解析service对应的options行,主要是填充parse_service()中创建的service对象。
return;
}
break;
case K_on:
state->context = parse_action(state, nargs, args); //同理,如果标志位是K_on, 加到action_list上
if (state->context) {
state->parse_line = parse_line_action;
return;
}
break;
case K_import:
parse_import(state, nargs, args);同理,如果标志位是K_import, 加到import_list上
break;
}
state->parse_line = parse_line_no_op;
}
看下init.rc 文件就明白了
import /init.trace.rc
on early-init
# Set init and its forked children's oom_adj.
write /proc/1/oom_score_adj -1000
# Set the security context of /adb_keys if present.
restorecon /adb_keys
start ueventd
service ueventd /sbin/ueventd
class core
critical
seclabel u:r:ueventd:s0
以上过程就是对init.rc文件存储的数据进行解析的。
action_list存放了所有探知的action
service_list存放了所有在init.rc文件中定义的 服务。
queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
这个函数 维护action_queue,存放了 将要执行的 action。