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閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容