最近開始閱讀android系統(tǒng)啟動模塊的源碼了,記錄一下從中學(xué)到的東西。
文章中的源碼基于android8.0.0
init進(jìn)程是android在用戶空間啟動的第一個進(jìn)程,也是用戶空間其他進(jìn)程的父進(jìn)程,它的進(jìn)程號是1,系統(tǒng)通過init進(jìn)程來進(jìn)行一些初始化工作,包括啟動Zygoto、SystemServer等重要進(jìn)程。
系統(tǒng)在加載linux內(nèi)核后便會創(chuàng)建init進(jìn)程,并會執(zhí)行init文件中的main方法,接下來我們通過分析main方法的代碼來看一下init進(jìn)程的啟動流程。
1.執(zhí)行第一階段,掛載系統(tǒng)運行時目錄
源碼路徑:\system\core\init\init.cpp
int main(int argc, char** argv) {
// 1.根據(jù)參數(shù)argv來判斷是否是ueventd或watchdogd
//strcmp函數(shù)用來比較字符串的值是否相等,相等返回0
//basename函數(shù)用來獲取字符串中最后一個‘/’之后的內(nèi)容
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
if (!strcmp(basename(argv[0]), "watchdogd")) {
return watchdogd_main(argc, argv);
}
if (REBOOT_BOOTLOADER_ON_PANIC) {
install_reboot_signal_handlers();//設(shè)置一些信號量
}
//添加環(huán)境變量
add_environment("PATH", _PATH_DEFPATH);
// 2.判斷是否是第一階段
bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
if (is_first_stage) {//if內(nèi)的代碼只在第一階段執(zhí)行
boot_clock::time_point start_time = boot_clock::now();
// 清空文件權(quán)限
umask(0);
// 3.掛載和創(chuàng)建一些系統(tǒng)運行時必要的目錄
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));
// Don't expose the raw commandline to unprivileged processes.
chmod("/proc/cmdline", 0440);
gid_t groups[] = { AID_READPROC };
setgroups(arraysize(groups), groups);
mount("sysfs", "/sys", "sysfs", 0, NULL);
mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);
mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));
mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));
//初始化log
InitKernelLogging(argv);
LOG(INFO) << "init first stage started!";
//準(zhǔn)備開始轉(zhuǎn)入第二階段
...
}
...
}
在注釋1處,首先通過main函數(shù)的參數(shù)argv來判斷是否是ueventd或watchdogd,我們閱讀源碼主要是為了了解init進(jìn)程的啟動流程,就不對ueventd和watchdogd進(jìn)行研究了。
在注釋2處,通過環(huán)境變量INIT_SECOND_STAGE來判斷當(dāng)前是第一階段還是第二階段,在init進(jìn)程的啟動過程中,main函數(shù)一共會執(zhí)行兩次,分別代表第一階段和第二階段,如果是第一階段,則執(zhí)行if內(nèi)的代碼。
在注釋3處,通過mount和mkdir來掛載和創(chuàng)建了一些系統(tǒng)運行時所需要的目錄,這些目錄只有在系統(tǒng)運行時才會創(chuàng)建,當(dāng)系統(tǒng)關(guān)閉后這些目錄都會消失。
之后便開始準(zhǔn)備進(jìn)入第二階段。
2.轉(zhuǎn)入第二階段
//初始化Selinux安全模塊,SELinux是美國國家安全局(NSA)對于強(qiáng)制訪問控制的實現(xiàn),是 Linux的一個安全子系統(tǒng)
selinux_initialize(true);
...
// 1.設(shè)置環(huán)境變量,將INIT_SECOND_STAGE的值設(shè)置為true
setenv("INIT_SECOND_STAGE", "true", 1);
static constexpr uint32_t kNanosecondsPerMillisecond = 1e6;
uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;
setenv("INIT_STARTED_AT", StringPrintf("%" PRIu64, start_ms).c_str(), 1);
char* path = argv[0];
char* args[] = { path, nullptr };
//2.重新執(zhí)行main方法
execv(path, args);
在注釋1處將環(huán)境變量INIT_SECOND_STAGE設(shè)置為true,然后再注釋2處通過execv函數(shù)重新執(zhí)行main方法,其中path的值即當(dāng)前init文件的路徑,execv會終止當(dāng)前進(jìn)程并根據(jù)path執(zhí)行新的進(jìn)程,但是進(jìn)程id不會改變。
3.開啟屬性服務(wù)并初始化信號處理函數(shù)
//第二階段重新執(zhí)行main方法
int main(int argc, char** argv) {
...
//由于INIT_SECOND_STAGE的值已經(jīng)為true了,當(dāng)?shù)诙芜\行main函數(shù)時會跳過if (is_first_stage) 內(nèi)的代碼.
bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
if (is_first_stage) {
...
}
InitKernelLogging(argv);
LOG(INFO) << "init second stage started!";//表示現(xiàn)在已經(jīng)處于第二階段了
...
property_init();// 1.初始化屬性服務(wù)
process_kernel_dt();//處理DT屬性
process_kernel_cmdline();//處理命令行屬性
export_kernel_boot_props();//處理系統(tǒng)屬性
...
// 清除不需要的環(huán)境變量
unsetenv("INIT_SECOND_STAGE");
unsetenv("INIT_STARTED_AT");
unsetenv("INIT_SELINUX_TOOK");
unsetenv("INIT_AVB_VERSION");
// 加載Selinux
selinux_initialize(false);
selinux_restore_context();
// 2.初始化信號處理函數(shù)
signal_handler_init();
property_load_boot_defaults();
export_oem_lock_status();
//開始屬性服務(wù)
start_property_service();
set_usb_controller();
...
現(xiàn)在進(jìn)入第二階段 。上面的這段代碼的邏輯比較簡單,主要開啟了屬性服務(wù)并初始化了信號處理函數(shù)。
在注釋1出對屬性服務(wù)進(jìn)行了初始化,屬性服務(wù)的作用類似于windows系統(tǒng)的注冊表。
在注釋2出初始化信號處理函數(shù),它的主要作用是防止init進(jìn)程的子進(jìn)程成為僵尸進(jìn)程。
在注釋3出開啟了屬性服務(wù)。
4.解析init.rc文件
...
Parser& parser = Parser::GetInstance(); // 1.獲取解析器對象
parser.AddSectionParser("service",std::make_unique<ServiceParser>());//加載解析Service語句的解析器
parser.AddSectionParser("on", std::make_unique<ActionParser>());//加載解析on語句的解析器
parser.AddSectionParser("import", std::make_unique<ImportParser>());//加載解析import語句的解析器
std::string bootscript = GetProperty("ro.boot.init_rc", "");
if (bootscript.empty()) {
parser.ParseConfig("/init.rc");// 2.解析init.rc文件
parser.set_is_system_etc_init_loaded(
parser.ParseConfig("/system/etc/init"));
parser.set_is_vendor_etc_init_loaded(
parser.ParseConfig("/vendor/etc/init"));
parser.set_is_odm_etc_init_loaded(parser.ParseConfig("/odm/etc/init"));
} else {
parser.ParseConfig(bootscript);
parser.set_is_system_etc_init_loaded(true);
parser.set_is_vendor_etc_init_loaded(true);
parser.set_is_odm_etc_init_loaded(true);
}
...
這段代碼主要用來解析init.rc文件,在注釋1處,通過Parser::GetInstance()獲取一個Parser解析器對象,然后使用AddSectionParser方法為該對象添加了service、on和import語句的解析器。
在注釋2處調(diào)用了parser的ParseConfig方法,對init.rc文件進(jìn)行解析
我們先來了解一下什么是.rc文件。
4.1 .rc文件簡介
.rc文件是使用android初始化語言編寫的一種腳本,這種語言由許多section塊組成,這些section可以分為兩類:動作(Action)和服務(wù)(Service),它們的基本形式如下:
//動作(Action)
on <trigger> [&&<trigger>]* //設(shè)置觸發(fā)器,Action類型的語句為on
<command> //觸發(fā)之后要執(zhí)行的命令
<command>
...
//服務(wù)(Service)
service <name> <pathname> [<argument>]* //<service的名稱> <執(zhí)行程序的路徑> <參數(shù)>
<option> //一些選項
<option>
以init.rc中的一段代碼為例:
on early-init //設(shè)置early-init觸發(fā)器
//下面的語句都是一些command,當(dāng)early-init被觸發(fā)后便執(zhí)行下面的語句
write /proc/1/oom_score_adj -1000
# Disable sysrq from keyboard
write /proc/sys/kernel/sysrq 0
# Set the security context of /adb_keys if present.
restorecon /adb_keys
# Shouldn't be necessary, but sdcard won't start without it. http://b/22568628.
mkdir /mnt 0775 root system
# Set the security context of /postinstall if present.
restorecon /postinstall
start ueventd
在.rc文件中還有一類import語句,用來引入其他.rc文件。
4.2init.rc源碼分析
我們來看一下init.rc文件的源碼:
源碼路徑:\system\core\rootdir\init.rc
import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /vendor/etc/init/hw/init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc // 1.加載init.zygote.rc文件
//下面是一系列的觸發(fā)器
on early-init
write /proc/1/oom_score_adj -1000
write /proc/sys/kernel/sysrq 0
restorecon /adb_keys
mkdir /mnt 0775 root system
restorecon /postinstall
start ueventd
on init
sysclktz 0
copy /proc/cmdline /dev/urandom
copy /default.prop /dev/urandom
symlink /system/etc /etc
symlink /sys/kernel/debug /d
symlink /system/vendor /vendor
mount cgroup none /acct cpuacct
mkdir /acct/uid
...
on late-init
trigger early-fs
trigger fs
trigger post-fs
trigger late-fs
trigger post-fs-data
trigger zygote-start // 2.觸發(fā)zygote-start
trigger load_persist_props_action
trigger firmware_mounts_complete
trigger early-boot
trigger boot
...
on zygote-start && property:ro.crypto.state=unencrypted
exec_start update_verifier_nonencrypted
start netd
start zygote //3.啟動zygote
start zygote_secondary
...
...
在注釋1處使用import語句引入了init.zygote.rc文件,這是后面啟動zygote進(jìn)程的關(guān)鍵一步,可以看到init.zygote.rc文件的名字是不固定的,那是因為根據(jù)手機(jī)處理器位數(shù)的不通,將init.zygote.rc拆分成了多個不同的文件,如init.zygote32.rc、init.zygote64.rc等。
然后通過on語句定義了一系列的觸發(fā)器,包括early-init、init等等。
可以看到在late-init被觸發(fā)時會去觸發(fā)zygote-start這個觸發(fā)器(注釋2處),而在zygote-start中則會調(diào)用start zygote(注釋3處),我們來看一下init.zygote32.rc文件的源碼:
源碼位置:\system\core\rootdir\init.zygote32.rc
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
priority -20
user root
group root readproc
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
onrestart restart wificond
writepid /dev/cpuset/foreground/tasks
可以看到,該文件通過service語句來創(chuàng)建zygote進(jìn)程,該進(jìn)程的代碼位于/system/bin/app_process目錄下。
這樣,當(dāng)相關(guān)的觸發(fā)器被觸發(fā)后,便會啟動zygote進(jìn)程。
4.3 init.rc文件的解析流程
回到init.cpp的main方法中來,前面我們說過parser對象調(diào)用AddSectionParser方法為該對象添加了service、on和import語句的解析器,并使用ParseConfig方法對init.rc文件進(jìn)行解析,我們來看一下這兩個方法的源碼:
文件路徑:\system\core\init\init_parser.cpp
void Parser::AddSectionParser(const std::string& name, std::unique_ptr<SectionParser> parser) {
section_parsers_[name] = std::move(parser);
}
section_parsers_有點類似于java中的map對象,AddSectionParser方法就是將一個觸發(fā)器對象放到了一個map集合中。
bool Parser::ParseConfig(const std::string& path) {
if (is_dir(path.c_str())) { // 1.判斷是否目錄
return ParseConfigDir(path);
}
return ParseConfigFile(path);
}
bool Parser::ParseConfigFile(const std::string& path) {
...
std::string data;
if (!read_file(path, &data)) { // 2.將要解析的文件的數(shù)據(jù)讀入到data中
return false;
}
...
ParseData(path, data); //3.調(diào)用ParseData方法進(jìn)行解析
...
}
在ParseConfig方法中,首先判斷傳入的文件是否是一個目錄,如果是目錄則調(diào)用ParseConfigDir方法,如果是文件則調(diào)用ParseConfigFile方法,由于我們需要解析的init.rc是一個文件,我們直接來看ParseConfigFile方法。
在注釋2出,將要解析的文件的數(shù)據(jù)讀取到了一個string類型的變量data中,然后再注釋3處調(diào)用ParseData方法進(jìn)行數(shù)據(jù)解析。
我們來看ParseData方法的源碼:
void Parser::ParseData(const std::string& filename, const std::string& data) {
std::vector<char> data_copy(data.begin(), data.end());// 1.將數(shù)據(jù)存入一個char類型的鏈表中
data_copy.push_back('\0');//在結(jié)尾添加‘\0’作為結(jié)束標(biāo)示
parse_state state; // 創(chuàng)建一個state對象
state.filename = filename.c_str();
state.line = 0;
state.ptr = &data_copy[0]; //state對象內(nèi)的ptr指針指向data_copy鏈表
state.nexttoken = 0;
SectionParser* section_parser = nullptr;//section解析器
std::vector<std::string> args;
for (;;) {
switch (next_token(&state)) {
case T_EOF: //當(dāng)讀取到結(jié)束標(biāo)示符時
if (section_parser) {
section_parser->EndSection(); //1.調(diào)用EndSection
}
return;
case T_NEWLINE: //讀取到回車符時
...
//section_parsers_是一個map,之前我們創(chuàng)建的on service import語句解析器便放入了這個map中
if (section_parsers_.count(args[0])) {//2.如果args[0]是on、service、import語句
...
if (!section_parser->ParseSection(args, &ret_err)) {//調(diào)用ParseSection進(jìn)行解析
...
}
} else if (section_parser) {
...
if (!section_parser->ParseLineSection(args, state.filename, state.line, &ret_err)) {//調(diào)用ParseLineSection
...
}
}
args.clear(); //清空
break;
case T_TEXT:
args.emplace_back(state.text); //3.將讀入的數(shù)據(jù)放入args中
break;
}
}
}
在ParseData函數(shù)中開啟了一個無限循環(huán)來進(jìn)行數(shù)據(jù)的讀取,當(dāng)讀取到一個單詞時,便將這個單詞放到一個字符串鏈表args中(注釋3處),這樣便將一行語句拆分成了多個單詞。
在注釋2處,通過args[0]來從section_parsers_這個map中獲取解析器,在第4節(jié)我們創(chuàng)建了on、service、import類型的解析器并放入到了這個map中,因此如果args[0]是這三種語句中的一種便可以獲取到相應(yīng)的解析器,否則的話這條語句可能是一條command或option。然后分別調(diào)用ParseSection或ParseLineSection方法來進(jìn)行解析。
當(dāng)讀取到結(jié)束符號時,則調(diào)用EndSection方法(注釋1處)。
ParseSection方法是一個抽象方法,在各個解析器中的實現(xiàn)不同,我們主要來看一下ServiceParser中的實現(xiàn):
源碼位置:\system\core\init\service.cpp
bool ServiceParser::ParseSection(const std::vector<std::string>& args,
std::string* err) {
...
service_ = std::make_unique<Service>(name, str_args);//1.生成service_對象
return true;
}
從源碼中我們可以看到,ParseSection主要是根據(jù)傳入的service語句來構(gòu)建了一個service_對象。
我們再來看一下EndSection方法在ServiceParser中的實現(xiàn):
void ServiceParser::EndSection() {
if (service_) {
ServiceManager::GetInstance().AddService(std::move(service_));
}
}
該方法即使是service_對象存入到了ServiceManager中。
ActionParser中的ParseSection方法與ServiceParser類似,只是ActionParser是根據(jù)action語句創(chuàng)建了一個action_對象,并把這個action_放入了ActionManager中。
至此init.rc中的各類Action和Service便都加載完成了。
5.將需要被觸發(fā)的觸發(fā)器加入隊列
我們回到init.cpp的main方法中繼續(xù)往下看:
ActionManager& am = ActionManager::GetInstance();
am.QueueEventTrigger("early-init"); //early-init觸發(fā)器
am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
am.QueueBuiltinAction(set_mmap_rnd_bits_action, "set_mmap_rnd_bits");
am.QueueBuiltinAction(set_kptr_restrict_action, "set_kptr_restrict");
am.QueueBuiltinAction(keychord_init_action, "keychord_init");
am.QueueBuiltinAction(console_init_action, "console_init");
am.QueueEventTrigger("init"); //init觸發(fā)器
am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
std::string bootmode = GetProperty("ro.bootmode", "");
if (bootmode == "charger") {
am.QueueEventTrigger("charger");
} else {
am.QueueEventTrigger("late-init"); //late-init觸發(fā)器
}
am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
在解析完init.rc文件之后,我們還要進(jìn)行一些準(zhǔn)備工作才能對各類事件進(jìn)行觸發(fā)。在上面的代碼中,我們先獲取了ActionManager的實例,然后通過QueueEventTrigger方法將需要被觸發(fā)的觸發(fā)器加入到了觸發(fā)隊列中,并使用QueueBuiltinAction方法用于動態(tài)創(chuàng)建了一些Action??梢钥吹絜arly-init、init、late-init觸發(fā)器都先后被加入到了隊列中。
QueueEventTrigger的源碼如下:
源碼路徑:\system\core\init\action.cpp
//std::queue<std::unique_ptr<Trigger>> trigger_queue_;定義于.h中
void ActionManager::QueueEventTrigger(const std::string& trigger) {
trigger_queue_.push(std::make_unique<EventTrigger>(trigger));//將觸發(fā)器加入到隊列中
}
6.觸發(fā)事件,并不斷監(jiān)聽新事件
while (true) {
...
if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
am.ExecuteOneCommand(); //執(zhí)行一條command
}
if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
restart_processes(); //重啟需要重啟的進(jìn)程
...
}
...
}
通過while(true)開啟了一個事件循環(huán)模型,在這個無限循環(huán)中,通過ActionManager的ExecuteOneCommand來逐條執(zhí)行Action的command。
至此init進(jìn)程的啟動便完成了。
7.總結(jié)
總結(jié)一下init進(jìn)程啟動所做的事情:
1.掛載和創(chuàng)建系統(tǒng)目錄
2.初始化系統(tǒng)log、開啟屬性服務(wù)、加載Selinux模塊等工作
3.解析init.rc文件,加載各種Action和Service
4.觸發(fā)Action,并不斷監(jiān)聽新的Action
參考資料:
http://www.itdecent.cn/p/befff3d70309
http://www.itdecent.cn/p/464c3d1203b1
《Android》進(jìn)階解密——劉望舒 著