Android系统开机流程分析

Android系统启动分两大阶段:Linux阶段,Android阶段。

BootLoader启动,引导进入Linux内核阶段;Android启动阶段,kernel_init函数完成设备驱动程序的初始化,并调用init_post函数启动用户空间的init进程,Linux内核启动完成之后跳转到Android层init进程启动,从此开启了Android世界的大门,整个开机过程大致如下:

image.png

图A-1

从图A-1中可以看出,对于从事Android系统上层(非驱动)开发人员,理解了init进程启动之后阶段的流程便知道系统进程Systemserver, 诸如ActivityManagerService、PowerManagerService等核心服务如何启动的、怎么样进入我们常见的系统桌面(Launcher)应用的。图A-2 是init进程启动后阶段大致过程。

image.png

图A-2

Init进程启动在这里不做详细的陈述,本文主要目的是打通整个开机流程任督六脉,后续若有机会或时间,补写关于init进程启动的详细流程以及init.rc脚本的语法规则以及编写方法技巧,在此立帖为证,拜请监督。

由图A-2可知,内核初始化阶段会调用Kernel_init函数,在这个函数中调用init_post函数启动祖先进程,即init进程。

01

Init启动【1】

image.png

图A-3

文件路径:/system/core/init/init.cpp。入口函数为main,代码如下所示:

int main(int argc, char** argv) {

if (!strcmp(basename(argv[0]), "ueventd")) {

return ueventd_main(argc, argv);

}

if (!strcmp(basename(argv[0]), "watchdogd")) {

return watchdogd_main(argc, argv);

}

// Clear the umask.

umask(0);

add_environment("PATH", _PATH_DEFPATH);

bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);

// Get the basic filesystem setup we need put together in the initramdisk

// on / and then we'll let the rc file figure out the rest.

//1 创建文件并挂载

if (is_first_stage) {

mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");

mkdir("/dev/pts", 0755);

mkdir("/dev/socket", 0755);

mount("devpts", "/dev/pts", "devpts", 0, NULL);

define MAKE_STR(x) __STRING(x)

mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));

mount("sysfs", "/sys", "sysfs", 0, NULL);

}

// We must have some place other than / to create the device nodes for

// kmsg and null, otherwise we won't be able to remount / read-only

// later on. Now that tmpfs is mounted on /dev, we can actually talk

// to the outside world.

open_devnull_stdio();

klog_init();

klog_set_level(KLOG_NOTICE_LEVEL);

NOTICE("init %s started!\n", is_first_stage ? "first stage" : "second stage");

if (!is_first_stage) {

// Indicate that booting is in progress to background fw loaders, etc.

close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));

//2 初始化属性资源

property_init();

// If arguments are passed both on the command line and in DT,

// properties set in DT always have priority over the command-line ones.

process_kernel_dt();

process_kernel_cmdline();

// Propagate the kernel variables to internal variables

// used by init as well as the current required properties.

export_kernel_boot_props();

}

...

const BuiltinFunctionMap function_map;

Action::set_function_map(&function_map);

Parser& parser = Parser::GetInstance();

parser.AddSectionParser("service",std::make_unique<ServiceParser>());

parser.AddSectionParser("on", std::make_unique<ActionParser>());

parser.AddSectionParser("import", std::make_unique<ImportParser>());

//3 解析init.rc脚本文件

parser.ParseConfig("/init.rc");

...

while (true) {

if (!waiting_for_exec) {

am.ExecuteOneCommand();

restart_processes();

}

int timeout = -1;

if (process_needs_restart) {

timeout = (process_needs_restart - gettime()) * 1000;

if (timeout < 0)

timeout = 0;

}

if (am.HasMoreCommands()) {

timeout = 0;

}

bootchart_sample(&timeout);

epoll_event ev;

int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));

if (nr == -1) {

ERROR("epoll_wait failed: %s\n", strerror(errno));

} else if (nr == 1) {

((void (*)()) ev.data.ptr)();

}

}

return 0;

}

从上述代码片段可以分析可知,init进程main函数做了许多事情,但是我们只需要关注几点便可,1 is_first_stage为真时创建文件并挂载,2 调用property_init函数进行初始化属性相关资源,3 调用parser.ParseConfig("/init.rc")解析init.rc文件,至于怎么解析脚本文件在此不作阐述,如需深入理解可以跳转至system/core/init/init_parse.cpp文件进行自行阅读,这里主要介绍zygote进程是如何启动。

02

Zygote启动

讲解Zygote启动之前先准备一点预备知识,Android7.0版本的系统,Google对init.rc做了拆分,每个服务一个启动脚本,因本文是居于64Bit的Android7.0系统进行分析的,故这里拿init.zygote64.rc文件进行剖析,内容如下:

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server

class main

socket zygote stream 660 root system

onrestart write /sys/android_power/request_state wake

onrestart write /sys/power/state on

onrestart restart audioserver

onrestart restart cameraserver

onrestart restart media

onrestart restart netd

writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks

从以上脚本代码分析得知Zygote服务的class name是main。从init启动部分流程图看到main会去调用parser.ParseConfig("/init.rc")(system/core/init/init_parse.cpp),同学们可能会有一个疑问,为什么看不到init进程去直接解析Zygote并启动?不急,且听我慢慢道来。从init.rc脚本开头处可以看到语句import /init.${ro.zygote}.rc,相当于上面的init.zygote64.rc文件中的代码内容。init.rc代码片段:

...

import /init.${ro.zygote}.rc

...

on nonencrypted

A/B update verifier that marks a successful boot.

exec - root -- /system/bin/update_verifier nonencrypted

class_start main

class_start late_start

...

根据init.rc脚本语法语义规则,class_start是一个COMMAND,它对应的函数是do_class_start,而我们从前面分析可知,main是指Zygote,所以此处是用来启动Zygote服务的。

system/core/init/builtins.cpp

static int do_class_start(const std::vector<std::string>& args) {

/* Starting a class does not start services

  • which are explicitly disabled. They must

  • be started individually.

*/

ServiceManager::GetInstance().

ForEachServiceInClass(args[1], [] (Service* s) { s->StartIfNotDisabled(); });

return 0;

}

StartIfNotDisabled做了什么?

bool Service::StartIfNotDisabled() {

if (!(flags_ & SVC_DISABLED)) {

return Start(); //Service没有运行,则启动

} else {

flags_ |= SVC_DISABLED_START;

}

return true;

}

Start函数我们只需要关心两点,判断启动service的执行文件是否存在,如若不存在则不启动。否则创建子进程调用execve执行程序system/bin/app_process。

image.png

图A-4

bool Service::Start() {

...

if (flags_ & SVC_RUNNING) { //如果service已经运行,则返回

ifdef MTK_INIT

ERROR("service '%s' still running, return directly\n", name_.c_str());

endif

return false;

}

...

//判断启动service的执行文件是否存在,如若不存在则返回

struct stat sb;

if (stat(args_[0].c_str(), &sb) == -1) {

ERROR("cannot find '%s' (%s), disabling '%s'\n",

args_[0].c_str(), strerror(errno), name_.c_str());

flags_ |= SVC_DISABLED;

return false;

}

...

pid_t pid = fork();

if (pid == 0) {

umask(077);

for (const auto& ei : envvars_) {

add_environment(ei.name.c_str(), ei.value.c_str());

}

for (const auto& si : sockets_) {

int socket_type = ((si.type == "stream" ? SOCK_STREAM :

(si.type == "dgram" ? SOCK_DGRAM :

SOCK_SEQPACKET)));

const char* socketcon =

!si.socketcon.empty() ? si.socketcon.c_str() : scon.c_str();

int s = create_socket(si.name.c_str(), socket_type, si.perm,

si.uid, si.gid, socketcon);

if (s >= 0) {

PublishSocket(si.name, s);

}

}

...

if (execve(args_[0].c_str(), (char) &strs[0], (char) ENV) < 0) {

ERROR("cannot execve('%s'): %s\n", args_[0].c_str(), strerror(errno));

}

...

}

为了让读者们一目了然,根据代码流程画图A-4流图,希望能帮助理解代码执行流程。

我们进入frameworks/base/cmds/app_process/app_main.cpp文件,代码片段如下:

int main(int argc, char* const argv[])

{

...

//306行开始

if (zygote) {

runtime.start("com.android.internal.os.ZygoteInit", args, zygote);

} else if (className) {

runtime.start("com.android.internal.os.RuntimeInit", args, zygote);

} else {

fprintf(stderr, "Error: no class name or --zygote supplied.\n");

app_usage();

LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");

return 10;

}

}

从上述代码可以看到,Zygote分析了那么多,辗转反侧,跋山涉水,终于看到启动Zygote的真正位置了。

找到frameworks/base/core/java/com/android/internal/os/ZygoteInit.java文件,看都做了那些事情?

public static void main(String argv[]) {

...

//判断是否需要启动SystemServer

boolean startSystemServer = false;

String abiList = null;

for (int i = 1; i < argv.length; i++) {

if ("start-system-server".equals(argv[i])) {

startSystemServer = true;

} else if (argv[i].startsWith(ABI_LIST_ARG)) {

abiList = argv[i].substring(ABI_LIST_ARG.length());

} else if (argv[i].startsWith(SOCKET_NAME_ARG)) {

socketName = argv[i].substring(SOCKET_NAME_ARG.length());

} else {

throw new RuntimeException("Unknown command line argument: " + argv[i]);

}

}

...

//启动SystemServer进程

if (startSystemServer) {

startSystemServer(abiList, socketName);

}

}

03

SystemServer启动

依照图A-1,main方法里面我们只需要关心SystemServer启动,其他的细枝末节暂不需要关注。SystemServer进程在整个Android世界中起着非常重要的作用。与Zygote进程一样同等重要,同属于Android的两大进程,系统很多核心进程都是在这两个进程中开启的。

既然上文说到调用startSystemServer方法启动SystemServer,那么我们来观察SystemServer类内都做了那些重要的事情?

/frameworks/base/services/java/com/android/server/SystemServer.java

image.png

图A-5

从图A-5中知道,SystemServer启动ActivityManagerService完成,在ActivityManagerService中会调用finishBooting方法完成引导过程,与此同时会发送开机的广播,当系统指定的进程接收到开机广播之后便会启动Home程序,完成Launcher桌面的加载和显示。这样Android的整个开机过程就算完成了。很明显,后面这一个过程远比我这里写的复杂得多,如果展开分析的话会导致整篇文章篇幅过长,稍有不慎就成了裹脚布又长又臭,所以这里就不作阐述了。【2】

总结

整个开机过程大分为如下几方面:

step1 BootLoader引导

step2 Kernel内核加载

step3 Init祖先进程启动

step4 Zygote进程启动

step5 SystemServer进程启动

step6 启动系统核心服务、服务库等

step7 Home桌面加载和显示

PS:后期如有时间,会详细分析init进程的启动过程,以及最后阶段是怎么一步一步启动Home桌面程序的,即文中标注【1】【2】处。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,873评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,483评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,525评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,595评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,018评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,958评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,118评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,873评论 0 208
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,643评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,813评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,293评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,615评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,306评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,170评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,968评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,107评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,894评论 2 278

推荐阅读更多精彩内容