本次系列的內(nèi)容如下:
Android啟動流程——1 序言拂玻、bootloader引導與Linux啟動
Android系統(tǒng)啟動——2 init進程
Android系統(tǒng)啟動——3 init.rc解析
Android系統(tǒng)啟動——4 zyogte進程
Android系統(tǒng)啟動——5 zyogte進程(Java篇)
Android系統(tǒng)啟動——6 SystemServer啟動
Android系統(tǒng)啟動——7 附錄1:Android屬性系統(tǒng)
Android系統(tǒng)啟動——8 附錄2:相關(guān)守護進程簡介
本篇文章的主要內(nèi)容如下:
- 1谋竖、init進程簡介
- 2瑟幕、Init.cpp的main()方法解析
一、init進程簡介
通過上篇文章我們知道充尉,Android設備啟動要經(jīng)過3個階段飘言,BootLoader、Linux Kernel和Android系統(tǒng)服務驼侠,一般情況下姿鸿,他們都會相應的啟動對動畫對應。前面我們已經(jīng)知道Andorid系統(tǒng)是如何啟動的BootLoader和Linux Kernel的倒源。
嚴格上講苛预,Android系統(tǒng)實際上是運行于Linux內(nèi)核之上的一系列"服務進程",并不算一個完成意義上的"操作系統(tǒng)"笋熬;而這一系列進程是維持Android設備正常工作的關(guān)鍵热某,所以它們肯定有一個"根進程",這個"根進程"衍生出了這一系列進程。這個"根進程"就是init進程昔馋。
init進程是Android系統(tǒng)啟動的第一個進程筹吐。它通過解析init.rc腳本來構(gòu)建出系統(tǒng)的初始形態(tài)。其他的"一系列"Android系統(tǒng)進程大部分也是通過"init.rc"來啟動的秘遏。因為要兼容不同的開發(fā)商丘薛,所以init.rc腳本的語法很簡單,并且采用的是純文本編輯的邦危,這樣導致它可讀性就會很高洋侨。
二、Init.cpp
init是Linux系統(tǒng)中用戶空間的第一個進程(pid=1)倦蚪,Linux Kernel啟動后希坚,會調(diào)用/system/core/init/Init.cpp的main()方法
那我們就來看下init.cpp的main()里面的具體實現(xiàn)
代碼在init.cpp989行
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);
}
// ****************** 第二部分 ******************
// 設置文件屬性為0777
// Clear the umask.
umask(0);
// ****************** 第三部分 ******************
// 設置環(huán)境變量
add_environment("PATH", _PATH_DEFPATH);
// ****************** 第四部分 ******************
// 創(chuàng)建一些基本目錄,并掛載
//判斷是否是第一次
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
//如果是第一次.
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);
mount("proc", "/proc", "proc", 0, NULL);
mount("sysfs", "/sys", "sysfs", 0, NULL);
}
// ****************** 第五部分 ******************
// 把標準輸入审丘、標準輸出和標準錯誤重定向到空設備文件"/dev/_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();
// ****************** 第六部分 ******************
// 啟動kernel log
klog_init();
klog_set_level(KLOG_NOTICE_LEVEL);
// 輸出init啟動階段的log
NOTICE("init%s started!\n", is_first_stage ? "" : " second stage");
// ****************** 第七部分 ******************
// 設置系統(tǒng)屬性
if (!is_first_stage) {
// Indicate that booting is in progress to background fw loaders, etc.
// 7.1 創(chuàng)建初始化標志
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
//7.2 初始化Android的屬性系統(tǒng)
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.
//7.3 解析DT和命令行中的kernel啟動參數(shù)
process_kernel_dt();
process_kernel_cmdline();
// Propogate the kernel variables to internal variables
// used by init as well as the current required properties.
//7.4 設置系統(tǒng)屬性
export_kernel_boot_props();
}
// ****************** 第八部分 ******************
// Set up SELinux, including loading the SELinux policy if we're in the kernel domain.
// 調(diào)用selinux_initialize函數(shù)啟動SELinux
selinux_initialize(is_first_stage);
// If we're in the kernel domain, re-exec init to transition to the init domain now
// that the SELinux policy has been loaded.
if (is_first_stage) {
// 按照selinux policy要求吏够,重新設置init文件屬性
if (restorecon("/init") == -1) {
ERROR("restorecon failed: %s\n", strerror(errno));
security_failure();
}
char* path = argv[0];
// 設置參數(shù) --second-stage
char* args[] = { path, const_cast<char*>("--second-stage"), nullptr };
// 執(zhí)行init進程勾给,重新進入main函數(shù)
if (execv(path, args) == -1) {
ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno));
security_failure();
}
}
// These directories were necessarily created before initial policy load
// and therefore need their security context restored to the proper value.
// This must happen before /dev is populated by ueventd.
INFO("Running restorecon...\n");
restorecon("/dev");
restorecon("/dev/socket");
restorecon("/dev/__properties__");
restorecon_recursive("/sys");
// ****************** 第九部分 ******************
epoll_fd = epoll_create1(EPOLL_CLOEXEC);
if (epoll_fd == -1) {
ERROR("epoll_create1 failed: %s\n", strerror(errno));
exit(1);
}
signal_handler_init();
// ****************** 第十部分 ******************
property_load_boot_defaults();
start_property_service();
// ****************** 第十一部分 ******************
// 重點部分滩报,我們后面用專門用一篇文章講解
init_parse_config_file("/init.rc");
// ****************** 第十二部分 ******************
action_for_each_trigger("early-init", action_add_queue_tail);
// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
// ... so that we can start queuing up actions that require stuff from /dev.
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
queue_builtin_action(keychord_init_action, "keychord_init");
queue_builtin_action(console_init_action, "console_init");
// Trigger all the boot actions to get us started.
action_for_each_trigger("init", action_add_queue_tail);
// Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
// wasn't ready immediately after wait_for_coldboot_done
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
// Don't mount filesystems or start core system services in charger mode.
char bootmode[PROP_VALUE_MAX];
if (property_get("ro.bootmode", bootmode) > 0 && strcmp(bootmode, "charger") == 0) {
action_for_each_trigger("charger", action_add_queue_tail);
} else {
action_for_each_trigger("late-init", action_add_queue_tail);
}
// Run all property triggers based on current state of the properties.
queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
// ****************** 第十三部分 ******************
while (true) {
if (!waiting_for_exec) {
execute_one_command();
restart_processes();
}
int timeout = -1;
if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}
if (!action_queue_empty() || cur_action) {
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;
}
由于main()函數(shù)比較長播急,整個init進程的啟動流程都在這個函數(shù)中握截,我們把main()切割成,然后我們就來挨個解釋下
1忘朝、第一部分
主要是檢查啟動程序的文件名偎痛,這里面分成三種情況
- 1 如果文件名是"ueventd",則執(zhí)行守護進程ueventd的主函數(shù)ueventd_main()
- 2 如果文件名是"watchdogd"才顿,則執(zhí)行看門狗守護進程的主函數(shù)watchdogd_main()
- 如果文件名既不是"ueventd"也不是"watchdogd",則往下執(zhí)行
2歹叮、第二部分
umask(0);
缺省情況下慰技,一個進程創(chuàng)建出來的文件和文件夾的屬性是022,使用umask()函數(shù)能設置文件屬性的掩碼今野。參數(shù)為0意味著進程創(chuàng)建的文件屬性是0777
3夹厌、第3部分
設置環(huán)境變量地址
4采够、第4部分
創(chuàng)建一些基本的目錄冰垄,包括/dev蹬癌、/porc、/sysfc等。同時把一些文件系統(tǒng)逝薪,如tmpfs隅要、devpt、proc董济、sysfs等mount到項目的目錄步清。我們把上面的文件目錄簡單說一下
- tmpfs
是一種基于內(nèi)存的文件系統(tǒng),mount后就可以使用。tmpfs文件系統(tǒng)下的文件都存放在內(nèi)存中虏肾,訪問速度快尼啡,但是關(guān)機后所有內(nèi)容偶讀會丟失,因此tmpfs文件系統(tǒng)比較合適存放一些臨時性的文件询微。tmpfs文件系統(tǒng)的大小是動態(tài)變化的崖瞭,剛開始占用空間很小,隨著文件的增多會隨之變大撑毛,很節(jié)省空間书聚。Android將tmpfs文件mount到/dev目錄,dev目錄用來存放系統(tǒng)創(chuàng)建的設備節(jié)點藻雌,正好符合tmpfsw文件系統(tǒng)的特點雌续。
-devpts
是虛擬終端文件系統(tǒng),它通常mount在目錄dev/pts下- proc
是一種基于內(nèi)存的虛擬文件系統(tǒng)胯杭,它可以看作是內(nèi)核內(nèi)部數(shù)據(jù)結(jié)構(gòu)的接口驯杜,通過它可以獲得系統(tǒng)的信息,同時能夠在運行時修改特定的內(nèi)核參數(shù)- sysfs
文件系統(tǒng)和proc文件系統(tǒng)類似做个,它是Linux2.6內(nèi)核引入的鸽心,作用是把系統(tǒng)的設備和總線按層次組織起來,使得它們可以在用戶空間存取居暖,用來向用戶空間導出內(nèi)核的數(shù)據(jù)結(jié)構(gòu)及它們的屬性顽频。
5、第5部分
調(diào)用open_devnull_stdio()函數(shù)把標準輸入太闺、標準輸出和標準錯誤重定向到空設備文件"/dev/null"糯景,這是創(chuàng)建守護進程常用的手段
6、第6部分
調(diào)用klog_init()函數(shù)創(chuàng)建節(jié)點/dev/kmsg省骂,這樣init進程可以使用kernel的log系統(tǒng)來出書log了蟀淮,同時調(diào)用klog_set_level函數(shù)來設置輸出log的級別。
PS:
- 1 這里補充下钞澳,為什么要使用kernel的log系統(tǒng)怠惶,因為此時Android系統(tǒng)的log還沒有啟動,所以需要使用kernel的log系統(tǒng)略贮。
- 2 log的輸出級別是KLOG_NOTICE_LEVEL(5)甚疟,當log級別小于5時仗岖,這回輸出kernel log,默認值是3览妖,關(guān)于log級別如下:
define KLOG_ERROR_LEVEL 3
define KLOG_WARNING_LEVEL 4
define KLOG_NOTICE_LEVEL 5
define KLOG_INFO_LEVEL 6
define KLOG_DEBUG_LEVEL 7
define KLOG_DEFUALT_LEVEL 3
默認為3
7轧拄、第7部分
如果不是第一次,則進行一些設置讽膏,我又將這里具體劃分為4個部分
- 7.1 創(chuàng)建初始化標志
- 7.2 初始化Android的屬性系統(tǒng)
- 7.3 解析DT和命令行中的kernel啟動參數(shù)
- 7.4 設置系統(tǒng)屬性
7.1 創(chuàng)建初始化標志
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
在/dev目錄下創(chuàng)建一個空文件".booting"表示初始化正在進行
is_booting()函數(shù)會依靠空文件".booting"來判斷是否進程處于初始化中檩电,初始化結(jié)束后,這個文件會被刪除
7.2 初始化Android的屬性系統(tǒng)
主要是調(diào)用property_init()函數(shù)來初始化Android的屬性系統(tǒng)府树,property_init()函數(shù)主要作用是創(chuàng)建一個共享區(qū)域來存儲屬性值
7.3 解析DT和命令行中的kernel啟動參數(shù)
這里里面又分兩個俐末,由于DT的優(yōu)先級高于cmdline,所以先調(diào)用process_kernel_dt()函數(shù)解析啟動參數(shù)奄侠,然后調(diào)用process_kernel_cmdline解析啟動參數(shù)卓箫。
為了讓大家更好的理解這個兩個方法,下面我們來看下這兩個方法的具體實現(xiàn)
- process_kernel_dt函數(shù)解析
代碼在init.cpp816行
static void process_kernel_dt(void)
{
static const char android_dir[] = "/proc/device-tree/firmware/android";
std::string file_name = android::base::StringPrintf("%s/compatible", android_dir);
std::string dt_file;
android::base::ReadFileToString(file_name, &dt_file);
// 判斷compatible文件內(nèi)容是否是android,firmware
if (!dt_file.compare("android,firmware")) {
ERROR("firmware/android is not compatible with 'android,firmware'\n");
return;
}
std::unique_ptr<DIR, int(*)(DIR*)>dir(opendir(android_dir), closedir);
if (!dir)
return;
struct dirent *dp;
// 讀取目錄的每個文件
while ((dp = readdir(dir.get())) != NULL) {
if (dp->d_type != DT_REG || !strcmp(dp->d_name, "compatible"))
continue;
file_name = android::base::StringPrintf("%s/%s", android_dir, dp->d_name);
android::base::ReadFileToString(file_name, &dt_file);
std::replace(dt_file.begin(), dt_file.end(), ',', '.');
// 每個文件名作為屬性名垄潮,里面的內(nèi)容作為屬性值
std::string property_name = android::base::StringPrintf("ro.boot.%s", dp->d_name);
property_set(property_name.c_str(), dt_file.c_str());
}
}
上面這個函數(shù)主要是在/proc/device-tree/firmware/android 這個目錄下烹卒,先看compatiable文件內(nèi)容是否是android,firmware。然后這個目錄下每個文件名作為屬性弯洗,文件里面的內(nèi)容作為屬性值旅急。這里的話就是ro.boot.hareware ro.boot.name這兩個屬性值。
我們知道內(nèi)核通常由bootloader(啟動引導程序)加載啟動牡整,前面說過了藐吮,目前使用最廣泛的是bootloader大都基于u-boot定制。內(nèi)核允許bootloader啟動自己時傳遞參數(shù)逃贝。在內(nèi)核內(nèi)核啟動完畢后谣辞,啟動參數(shù)可以通過/proc/cmdline查看。
例如android 4.4模擬器啟動后秋泳,查看其內(nèi)核啟動參數(shù)潦闲,如下:
root@generic:/ # cat /proc/cmdline
qemu.gles=0 qemu=1 console=ttyS0 android.qemud=ttyS1 android.checkjni=1 ndns=1
- process_kernel_cmdline函數(shù)解析
代碼在init.cpp848行
static void process_kernel_cmdline(void)
{
/* don't expose the raw commandline to nonpriv processes */
// 第一步
chmod("/proc/cmdline", 0440);
/* first pass does the common stuff, and finds if we are in qemu.
* second pass is only necessary for qemu to export all kernel params
* as props.
*/
// 第二步
import_kernel_cmdline(false, import_kernel_nv);
if (qemu[0])
import_kernel_cmdline(true, import_kernel_nv);
}
這個函數(shù)內(nèi)部有點小復雜攒菠,我將其分為2個步驟迫皱,解析如下:
- 第一步:修改/proc/cmdline文件權(quán)限,0440即表明只有root用戶或root組用戶可以續(xù)寫該文件辖众,其他用戶無法訪問卓起。
- 第二步:調(diào)用import_kernel_cmdline函數(shù),就是讀取proc/cmdline中的內(nèi)容凹炸。這個函數(shù)有兩個有兩個參數(shù)戏阅,第一個采納數(shù)標識當前Android十倍是否是模擬器。第二個參數(shù)是一個函數(shù)指針啤它;主要指的是通過調(diào)用import_kernel_nv函數(shù)來設置系統(tǒng)屬性奕筐。
import_kernel_cmdline函數(shù)將/proc/cmdline內(nèi)容讀入到內(nèi)存緩沖區(qū)中舱痘,并將cmdline內(nèi)容以空格拆分為小段字符串,依次傳遞給import_kernel_nv函數(shù)處理离赫。以上面的/proc/cmdline的輸出為例子芭逝,該字符串可以拆分為以下幾段:
qemu.gles=0
qemu=1
console=ttyS0
android.qemud=ttyS1
android.checkjni=1
ndns=1
因此,在import_kernel_nv將會連續(xù)調(diào)用6次渊胸,依次傳入上述字符串旬盯。函數(shù)實現(xiàn)如下:
代碼在init.cpp763行
static void import_kernel_nv(char *name, bool for_emulator)
{
char *value = strchr(name, '=');
int name_len = strlen(name);
if (value == 0) return;
*value++ = 0;
if (name_len == 0) return;
if (for_emulator) {
/* in the emulator, export any kernel option with the
* ro.kernel. prefix */
char buff[PROP_NAME_MAX];
int len = snprintf( buff, sizeof(buff), "ro.kernel.%s", name );
if (len < (int)sizeof(buff))
property_set( buff, value );
return;
}
if (!strcmp(name,"qemu")) {
strlcpy(qemu, value, sizeof(qemu));
} else if (!strncmp(name, "androidboot.", 12) && name_len > 12) {
const char *boot_prop_name = name + 12;
char prop[PROP_NAME_MAX];
int cnt;
cnt = snprintf(prop, sizeof(prop), "ro.boot.%s", boot_prop_name);
if (cnt < PROP_NAME_MAX)
property_set(prop, value);
}
}
import_kernel_cmdline第一次執(zhí)行時,傳入import_kernel_nv的形參為for_emulator為0翎猛,因此將匹配name是否為qemu胖翰,如果是,將其值保存在qemu全局靜態(tài)緩沖區(qū)中切厘。對于android模擬器萨咳,存在/proc/cmdline中存在"qemu=1"字段。如果for_emulator為1疫稿,則將生成ro.kernel.{name}={value}屬性寫入Android屬性系統(tǒng)中某弦。
此時回到process_kernel_cmdline函數(shù),繼續(xù)執(zhí)行
if (qemu[0])
import_kernel_cmdline(1, import_kernel_nv);
當系統(tǒng)為模擬器時而克,qemu[0]其值為"1"靶壮,第二次執(zhí)行import_kernel_cmdline函數(shù),將再次調(diào)用6次import_kernel_nv函數(shù)员萍,并且for_emulator為1腾降,因此將生成6個屬性。我們來確定下我們的分析
root@generic:/ # getprop | grep ro.kernel.
[ro.kernel.android.checkjni]: [1]
[ro.kernel.android.qemud]: [ttyS1]
[ro.kernel.console]: [ttyS0]
[ro.kernel.ndns]: [1]
[ro.kernel.qemu.gles]: [0]
[ro.kernel.qemu]: [1]
可驗證我們的分析是正確的碎绎。
7.4 設置系統(tǒng)屬性
代碼在init.cpp796行
static void export_kernel_boot_props() {
struct {
const char *src_prop;
const char *dst_prop;
const char *default_value;
} prop_map[] = {
{ "ro.boot.serialno", "ro.serialno", "", },
{ "ro.boot.mode", "ro.bootmode", "unknown", },
{ "ro.boot.baseband", "ro.baseband", "unknown", },
{ "ro.boot.bootloader", "ro.bootloader", "unknown", },
{ "ro.boot.hardware", "ro.hardware", "unknown", },
{ "ro.boot.revision", "ro.revision", "0", },
};
// 通過內(nèi)核的屬性設置應用層配置文件的屬性
for (size_t i = 0; i < ARRAY_SIZE(prop_map); i++) {
char value[PROP_VALUE_MAX];
int rc = property_get(prop_map[i].src_prop, value);
property_set(prop_map[i].dst_prop, (rc > 0) ? value : prop_map[i].default_value);
}
}
所以export_kernel_boot_props這個函數(shù)螃壤,它就是設置一些屬性,設置ro屬性根據(jù)之前的ro.boot這類的屬性值筋帖,如果沒有設置成unknown奸晴,像之前我們有ro.boot.hardware,那我們就可以設置root.hardware這樣的屬性日麸。這樣描述很有朋友看不懂寄啼,我把上面的語言轉(zhuǎn)化為最直觀的語言如下:
export_kernel_boot_props用戶設置幾個系統(tǒng)屬性,如下:
- 讀取ro.boot.serialno代箭,若存在其值寫入ro.serialno墩划,否則ro.serialno寫入空
- 讀取ro.boot.mode,若存在其值寫入ro.bootmode嗡综,否則ro.bootmode寫入"unkown"
- 讀取ro.boot.baseband乙帮,若存在其值寫入ro.baseband,否則ro.baseband寫入"unkown"
- 讀取ro.boot.bootloader极景,若存在其值寫入ro.bootloader察净,否則ro.bootloader寫入"unkown"
- 讀取ro.boot.revision驾茴,若存在,若存在其值寫入revision中氢卡,否則ro.revision寫入"unkown"沟涨。
- 讀取ro.boot.hardware,若存在其值寫入ro.hardware异吻,否則ro.hardware寫入"unkown"裹赴。
8、第8部分
這部分主要是selinux相關(guān)的诀浪,這部分代碼是Android4.3之后添加的安全內(nèi)核棋返,隨后伴隨著Android系統(tǒng)更新不斷迭代,這段代碼主要是設計SELinux初始化雷猪。后面有時間咱們開個專題講解SELinux睛竣。
SELinux帶給Linux的主要價值是:提供了一個靈活的,可配置的MAC機制求摇。同時SELinux是一個安全體系結(jié)構(gòu)射沟,它通過LSM(Linux Security Modules)框架被集成到Linux Kernel 2.6.x。它是NSA(United States National Security Agency)和SELinux社區(qū)的聯(lián)合項目。
這里重點分析下selinux_initialize函數(shù)
代碼在init.cpp955行
static void selinux_initialize(bool in_kernel_domain) {
// 使用Timer計時,計算selinux初始化耗時
Timer t;
selinux_callback cb;
// 用于打印Log的回調(diào)函數(shù)
cb.func_log = selinux_klog_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
// 用于檢查權(quán)限的回調(diào)函數(shù)
cb.func_audit = audit_callback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
// 如果不支持锅论,則直接返回
if (selinux_is_disabled()) {
return;
}
// 內(nèi)核態(tài)處理流程齿尽,第一階段in_kernel_domain為true
if (in_kernel_domain) {
// 這個log打印不出读第,因為是INFO級別
INFO("Loading SELinux policy...\n");
// 用于加載sepolicy文件,該函數(shù)最終將sepolicy文件傳遞給kernel,這樣kernel就有了安全策略的配置文件
if (selinux_android_load_policy() < 0) {
ERROR("failed to load policy: %s\n", strerror(errno));
security_failure();
}
// 命令行中得到的信息
bool is_enforcing = selinux_is_enforcing();
// 這里補充下 用于設置selinux工作模式。selinux有兩種工作模式:
// 1绑谣、"permissive",所有的操作都被允許(即沒有MAC)拗引,但是如果違反權(quán)限的話借宵,會記錄日志。
// 2矾削、"enforcing"壤玫,所有操作都會進行權(quán)限檢查。在一般的中斷中怔软,應有工作于enforcing模式
security_setenforce(is_enforcing);
if (write_file("/sys/fs/selinux/checkreqprot", "0") == -1) {
security_failure();
}
//輸出selinux的模式垦细,與初始化耗時
NOTICE("(Initializing SELinux %s took %.2fs.)\n",
is_enforcing ? "enforcing" : "non-enforcing", t.duration());
} else {
// 如果啟動第二階段,調(diào)用該函數(shù)
selinux_init_all_handles();
}
}
9挡逼、第9部分
這部分分為上下按兩個階段
第一階段主要是調(diào)用epoll_create1創(chuàng)建epoll句柄,如果創(chuàng)建失敗腻豌,則退出家坎。
第二階段是調(diào)用signal_handler_init()函數(shù)嘱能,主要是裝載進程信號處理器。
signal_handler_init()函數(shù)主要是當子進程被kill之后虱疏,會在父進程接受一個信號惹骂。處理這個信號的時候往sockpair一段寫數(shù)據(jù),而另一端的fd是加入epoll中
init是一個守護進程做瞪,為了防止init的子進程稱為僵尸進程(zombie process)对粪,需要init在子進程結(jié)束時獲取子進程的結(jié)束碼,通過結(jié)束碼將程序表中的子進程移除装蓬,防止稱為僵尸進程的子進程占用程序表的空間(程序表的空間達到上線時著拭,系統(tǒng)就不能再啟動新的進城了,會引起嚴重的系統(tǒng)問題)牍帚。
現(xiàn)在我們來看下signal_handler_init()函數(shù)的內(nèi)容
signal_handler.cpp955行
void signal_handler_init() {
// 在Linux中儡遮,父進程是通過捕捉SIGCHILD信號來得知子進程運行結(jié)束的情況
// Create a signalling mechanism for SIGCHLD.
int s[2];
// 利用socketpair創(chuàng)建出已經(jīng)連接的兩個socket,分別作為信號的讀暗赶、寫端
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) {
ERROR("socketpair failed: %s\n", strerror(errno));
exit(1);
}
signal_write_fd = s[0];
signal_read_fd = s[1];
// Write to signal_write_fd if we catch SIGCHLD.
struct sigaction act;
memset(&act, 0, sizeof(act));
// 信號處理器為SIGCHLD_handler鄙币,其被存在sigaction結(jié)構(gòu)體重,負責處理SIGCHLD消息
// 信號處理器
act.sa_handler = SIGCHLD_handler;
// 僅當進程終止時才接受
act.sa_flags = SA_NOCLDSTOP;
// 調(diào)用信號安裝函數(shù)sigaction蹂随,將監(jiān)聽的信號及對應的信號處理器注冊到內(nèi)核中
sigaction(SIGCHLD, &act, 0);
reap_any_outstanding_children();
// 定義在system/core/init/init.cpp中十嘿,注冊epoll handler,當signal_read_fd 有數(shù)據(jù)可讀時岳锁,調(diào)用handle_signal
register_epoll_handler(signal_read_fd, handle_signal);
}
9.1信號
這里我們簡單介紹下信號:
Linux進程通過相互發(fā)送接收消息來實現(xiàn)進程間通信详幽,這些消息被稱為"信號"。每個進程在處理它進程發(fā)送的信號時浸锨,都要注冊處理者唇聘,處理者被稱為信號處理器。
每個進程在處理其他進程發(fā)送的signal信號時都需要先注冊柱搜,當進程的運行狀態(tài)改變或終止時會產(chǎn)生某種signal信號迟郎,init進程是所有用戶空間進程的父進程,當其子進程終止時產(chǎn)生SIGCHLD信號聪蘸,init進程調(diào)用信號安裝函數(shù)sigaction()宪肖,傳遞參數(shù)給sigaction結(jié)構(gòu)體,便完成信號處理的過程健爬。
這里有兩個重要的函數(shù):SIGCHLD_handler和handle_signal控乾,他倆是對應的
代碼在signal_handler.cpp 158行
static void SIGCHLD_handler(int) {
//向signal_write_fd寫入1,知道成功為止
if (TEMP_FAILURE_RETRY(write(signal_write_fd, "1", 1)) == -1) {
ERROR("write(signal_write_fd) failed: %s\n", strerror(errno));
}
}
SIGCHLD_handler()函數(shù)負責寫入
代碼在signal_handler.cpp 150行
static void handle_signal() {
// Clear outstanding requests.
char buf[32];
// 讀取signal_read_fd數(shù)據(jù)娜遵,放入buf
read(signal_read_fd, buf, sizeof(buf));
reap_any_outstanding_children();
}
handle_signal()函數(shù)負責讀取數(shù)據(jù)蜕衡,上面調(diào)用了reap_any_outstanding_children()函數(shù),我們來看下
代碼在signal_handler.cpp 145行
static void reap_any_outstanding_children() {
while (wait_for_one_process()) {
}
}
我們看到reap_any_outstanding_children函數(shù)就是調(diào)用while循環(huán)來調(diào)用wait_for_one_process()函數(shù)设拟,下面我們來看下wait_for_one_process()函數(shù)的具體執(zhí)行
代碼在signal_handler.cpp 53行
static bool wait_for_one_process() {
int status;
// 等待任意子進程慨仿,如果子進程沒有退出則返回0久脯,否則則返回該子進程的pid
pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, WNOHANG));
if (pid == 0) {
return false;
} else if (pid == -1) {
ERROR("waitpid failed: %s\n", strerror(errno));
return false;
}
// 根據(jù)pid 查找相應的service
service* svc = service_find_by_pid(pid);
std::string name;
if (svc) {
name = android::base::StringPrintf("Service '%s' (pid %d)", svc->name, pid);
} else {
name = android::base::StringPrintf("Untracked pid %d", pid);
}
NOTICE("%s %s\n", name.c_str(), DescribeStatus(status).c_str());
if (!svc) {
return true;
}
// TODO: all the code from here down should be a member function on service.
// 當flag為RESTART,且不是ONESHOT時镰吆,先kill進程組內(nèi)所有子進程或子線程
if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) {
NOTICE("Service '%s' (pid %d) killing any children in process group\n", svc->name, pid);
kill(-pid, SIGKILL);
}
// Remove any sockets we may have created.
// 移除當前服務svc中所有創(chuàng)建過的socket
for (socketinfo* si = svc->sockets; si; si = si->next) {
char tmp[128];
snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name);
unlink(tmp);
}
// 當flags為EXEC時帘撰,釋放相應的服務
if (svc->flags & SVC_EXEC) {
INFO("SVC_EXEC pid %d finished...\n", svc->pid);
waiting_for_exec = false;
list_remove(&svc->slist);
free(svc->name);
free(svc);
return true;
}
svc->pid = 0;
svc->flags &= (~SVC_RUNNING);
// Oneshot processes go into the disabled state on exit,
// except when manually restarted.
// 對于ONESHOT服務,使其進入disabled狀態(tài)
if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) {
svc->flags |= SVC_DISABLED;
}
// Disabled and reset processes do not get restarted automatically.
// 禁用和重置的服務万皿,都不再自動重啟
if (svc->flags & (SVC_DISABLED | SVC_RESET)) {
// 設置相應的service狀態(tài)為stopped
svc->NotifyStateChange("stopped");
return true;
}
// 服務在4分鐘內(nèi)重啟次數(shù)超過4次摧找,則重啟手機進入recovery模式
time_t now = gettime();
if ((svc->flags & SVC_CRITICAL) && !(svc->flags & SVC_RESTART)) {
if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {
if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {
ERROR("critical process '%s' exited %d times in %d minutes; "
"rebooting into recovery mode\n", svc->name,
CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60);
android_reboot(ANDROID_RB_RESTART2, 0, "recovery");
return true;
}
} else {
svc->time_crashed = now;
svc->nr_crashed = 1;
}
}
svc->flags &= (~SVC_RESTART);
svc->flags |= SVC_RESTARTING;
// Execute all onrestart commands for this service.
// 執(zhí)行當前service中所有onrestart命令
struct listnode* node;
list_for_each(node, &svc->onrestart.commands) {
command* cmd = node_to_item(node, struct command, clist);
cmd->func(cmd->nargs, cmd->args);
}
// 設置相應的service狀態(tài)未restarting
svc->NotifyStateChange("restarting");
return true;
}
總結(jié)一下:
當init進程調(diào)用signal_handler_init后,一旦受到子進程終止帶來的SIGCHLD消息后牢硅,將利用信號處理者SIGCHLD_handler向signal_write_fd寫入信息蹬耘;epoll句柄監(jiān)聽到signal_read_fd收到消息后婆赠,將調(diào)用handle_signal進行處理休里。如下圖
10妙黍、第10部分
這部分主要分為兩部分拭嫁,上半部分是調(diào)用property_load_boot_defaults()函數(shù)解析根目錄的default.prop的屬性抓于,設置默認屬性配置的相關(guān)工作捉撮。下半部分是調(diào)用start_prperty_service()函數(shù)巾遭,啟動屬性服務灼舍,并接受屬性的socket的fd加入到epoll中,也定義了處理函數(shù)炫乓。那我們依次來看下
10.1厢岂、property_load_boot_defaults()函數(shù)解析
代碼在property_service.cpp 494行
void property_load_boot_defaults() {
load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT, NULL);
}
我們看到他調(diào)用load_properties_from_file函數(shù)塔粒,那我們來看下load_properties_from_file函數(shù)筐摘。
代碼在property_service.cpp 426行
/*
* Filter is used to decide which properties to load: NULL loads all keys,
* "ro.foo.*" is a prefix match, and "ro.foo.bar" is an exact match.
*/
static void load_properties_from_file(const char* filename, const char* filter) {
Timer t;
std::string data;
if (read_file(filename, &data)) {
data.push_back('\n');
load_properties(&data[0], filter);
}
NOTICE("(Loading properties from %s took %.2fs.)\n", filename, t.duration());
}
PS:所謂充電模式是指充電器開機時設備進入的狀態(tài)咖熟。這是kernel和init進程會啟動馍管,但是大部分服務都不會啟動
10.2确沸、start_property_service()函數(shù)解析
代碼在property_service.cpp 570行
void start_property_service() {
property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
0666, 0, 0, NULL);
if (property_set_fd == -1) {
ERROR("start_property_service socket creation failed: %s\n", strerror(errno));
exit(1);
}
listen(property_set_fd, 8);
register_epoll_handler(property_set_fd, handle_property_set_fd);
}
start_property_service()函數(shù)創(chuàng)建了socket罗捎,然后監(jiān)聽桨菜,并且調(diào)用register_epoll_handler()函數(shù)把socket的fd放入epoll中
函數(shù)創(chuàng)建了socket,然后監(jiān)聽泻红,并且調(diào)用register_epoll_handler函數(shù)把socket的fd放入了epoll中
11谊路、第11部分
這部分主要是解析init.rc文件根悼,我們將會用單獨一篇文章來講解挤巡。這里先略過
12矿卑、第12部分
本部分是將把Action加入執(zhí)行隊列中
代碼在init.cpp 570行
// 執(zhí)行init.rc中觸發(fā)器為 on early-init的語句,即將early-init的Action添加到鏈表action_queue中
action_for_each_trigger("early-init", action_add_queue_tail);
// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
// 等冷插拔設備初始化完成糊肤,即創(chuàng)建wait_for_coldboot_done Action并添加到action_queue和action_list中
queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
// ... so that we can start queuing up actions that require stuff from /dev.
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
// 設備組合鍵的初始化操作馆揉,創(chuàng)建keychord_init Action 并添加到鏈表action_queue和action_list中
queue_builtin_action(keychord_init_action, "keychord_init");
// 創(chuàng)建console_init動作并添加到鏈表action_queue和action_list中
queue_builtin_action(console_init_action, "console_init");
// Trigger all the boot actions to get us started.
// 執(zhí)行init.rc文件中觸發(fā)器為 on init 的語句升酣,將init動作添加到鏈表action_queue中
action_for_each_trigger("init", action_add_queue_tail);
// Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
// wasn't ready immediately after wait_for_coldboot_done
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
// Don't mount filesystems or start core system services in charger mode.
char bootmode[PROP_VALUE_MAX];
// 當處于充電模式态罪,則charger加入執(zhí)行隊列复颈;否則late-init加入隊列。
if (property_get("ro.bootmode", bootmode) > 0 && strcmp(bootmode, "charger") == 0) {
action_for_each_trigger("charger", action_add_queue_tail);
} else {
action_for_each_trigger("late-init", action_add_queue_tail);
}
// Run all property triggers based on current state of the properties.
// 觸發(fā)器為屬性是否設置
queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
上面大量的調(diào)用了action_for_each_trigger函數(shù)凿菩、action_add_queue_tai函數(shù)和queue_builtin_action蓄髓,那我們就依次研究下這兩個函數(shù)
12.1 action_for_each_trigger()函數(shù)解析
代碼在init_parser.cpp 546行
void action_for_each_trigger(const char *trigger,
void (*func)(struct action *act))
{
struct listnode *node, *node2;
struct action *act;
struct trigger *cur_trigger;
list_for_each(node, &action_list) {
// 遍歷每個action
act = node_to_item(node, struct action, alist);
list_for_each(node2, &act->triggers) {
// 遍歷每個action的triggers
cur_trigger = node_to_item(node2, struct trigger, nlist);
// 判斷是否與傳入的trigger名字匹配
if (!strcmp(cur_trigger->name, trigger)) {
// 調(diào)用回調(diào)函數(shù)
func(act);
}
}
}
}
12.2 action_add_queue_tail()函數(shù)解析
代碼在init_parser.cpp 643行
void action_add_queue_tail(struct action *act)
{
if (list_empty(&act->qlist)) {
list_add_tail(&action_queue, &act->qlist);
}
}
這個函數(shù)就是把action加入到執(zhí)行列表中
12.3 queue_builtin_action()函數(shù)解析
代碼在init_parser.cpp 623行
void queue_builtin_action(int (*func)(int nargs, char **args), const char *name)
{
action* act = (action*) calloc(1, sizeof(*act));
trigger* cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger));
cur_trigger->name = name;
list_init(&act->triggers);
list_add_tail(&act->triggers, &cur_trigger->nlist);
list_init(&act->commands);
list_init(&act->qlist);
command* cmd = (command*) calloc(1, sizeof(*cmd));
cmd->func = func;
cmd->args[0] = const_cast<char*>(name);
cmd->nargs = 1;
list_add_tail(&act->commands, &cmd->clist);
list_add_tail(&action_list, &act->alist);
action_add_queue_tail(act);
}
這個函數(shù)的話会喝,就是直接創(chuàng)建一個action,然后新建command译红,關(guān)鍵是func會調(diào)用函數(shù)設置好,最后把action加入執(zhí)行隊列中。
queue_builtin_action函數(shù)用來動態(tài)生成一個Action并插入到執(zhí)行列表"action_queue中"诗宣。插入的Action由一個函數(shù)指針和一個表示名字的字符串組成想诅。Android在以前版本中直接調(diào)用這些函數(shù)來完成某些初始化的工作,但是忘古,這些函數(shù)可能會依賴init.rc里面定義的一些命令和服務的執(zhí)行情況∷杩埃現(xiàn)在把這些初始化函數(shù)也通過Action的形式插入到執(zhí)行列表中旦袋,這樣就能控制他們的執(zhí)行順序了。
插入的函數(shù)有:
- wait_for_coldboot_done_action()函數(shù),等待冷插拔設備初始化完成
- mix_hwrng_into_linux_rng_action()函數(shù)鲜戒,從硬件PNG的設備文件/dev/hw_random中讀取512字節(jié)并寫到Linux RNG的設備文件/dev/urandom中
- keychord_init_action()函數(shù):初始化組合鍵監(jiān)聽模塊
- console_init_action()函數(shù):在屏幕個上顯示Android字樣的Logo
- property_service_init_action()函數(shù):初始化屬性服務,讀取系統(tǒng)預制的屬性值
- singal_init_action()函數(shù):初始化信號處理模塊失都。
- check_startup_action()函數(shù):檢查是否已經(jīng)完成init進程初始化粹庞,如果完成則刪除.booting文件。
- queue_property_triggers_action()函數(shù):檢查Action列表中通過修改屬性來觸發(fā)的Action延刘,查看相關(guān)屬性值是否已經(jīng)設置觉阅,如果已經(jīng)設置叮趴,則將Action加入到執(zhí)行列表中。
執(zhí)行流程如下圖:
13宫静、第13部分
代碼在init.cp 1109行
while (true) {
// 判斷是否還有事件需要處理
if (!waiting_for_exec) {
//依次執(zhí)行每個action中攜帶的command對應的執(zhí)行函數(shù)
execute_one_command();
// 重啟一些掛掉的進程
restart_processes();
}
// 決定timeout的時間,將影響while循環(huán)的間隔
int timeout = -1;
// 有進程需要重啟是,等待進程重啟
if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}
if (!action_queue_empty() || cur_action) {
timeout = 0;
}
// 進行性能數(shù)據(jù)采樣
bootchart_sample(&timeout);
epoll_event ev;
// 沒有事件來的話,最多阻塞timeout時間
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) {
// 根據(jù)上下文知道套才,epoll句柄(即epoll_fd)主要監(jiān)聽子進程結(jié)束,及其他進程設置系統(tǒng)屬性的請求 ,根據(jù)事件的到來携兵,執(zhí)行對應處理函數(shù)
((void (*)()) ev.data.ptr)();
}
}
最后init進程會進入到一個無線循環(huán)中去炭懊,在這個無線循環(huán)中,init進程會做以下五件事:
第一件事:調(diào)用函數(shù)execute_one_command來檢查action_queue列表是否為空。如果不為空的話加矛,那么init進程就將保存在列表頭部中的action移除,并且執(zhí)行這個被移除的action塌鸯。由于前面我們將一個名稱為"console_init"的action添加到action_queue列表中星持,因此饥努,在這個無線循環(huán)中,這個action就會被執(zhí)行,即console_init_action函數(shù)會被調(diào)用拱烁。
第二件事:調(diào)用函數(shù)restart_processes來檢查系統(tǒng)中是否有進程需要重啟生蚁。在啟動腳本init.rc中,我們可以指定一個進程在退出之后會自動重啟戏自。在這種情況下邦投,函數(shù)restart_processes就會檢查是否存在需要重新啟動的進程,如果存在的話擅笔,那么就將它重新啟動起來志衣。
第三件事:處理系統(tǒng)屬性變化事件。當我們調(diào)用函數(shù)property_set來改變一個系統(tǒng)屬性時猛们,系統(tǒng)就會通過一個socket(通過調(diào)用函數(shù)get_property_set_fd可以獲得它的文件描述符)來向init進程發(fā)送一個屬性值改變事件通知念脯。init進程接受到這個屬性值改變事件之后,就會調(diào)用函數(shù)handle_property_set_fd來進行相應的處理弯淘。后面在分析第三個開機畫面顯示過程時绿店,我們就會看到,SurfaceFlinger服務就是通過修改“ctl.start”和“ctl.stop”屬性來啟動和停止三個開機畫面的庐橙。
第四件事:處理一種被稱為"chorded keyboard"的鍵盤輸入時間假勿。這種類型為"chorded keyboard"的鍵盤設備通過不同的按鍵組合來描述不同的命令或者操作,它對應的設備為/dev/keychord态鳖。我們可以通過調(diào)用函數(shù)get_keychord_fd()來獲的這個設備的文件描述符转培,以便可以監(jiān)控它的輸入事件,并且調(diào)用函數(shù)handle_keychord來對這些輸入事件進行處理浆竭。
第五件事:回收僵尸進程浸须。我們知道,在Linux內(nèi)核中兆蕉,如果父進程不等待子進程結(jié)束就退出羽戒,那么當子進程結(jié)束的時候,就會變成一個僵尸進程虎韵,從而占用系統(tǒng)的資源易稠。為了回收這些僵尸進程,init進程會安裝一個SIGCHLD信號接收器包蓝。當這些父進程已經(jīng)退出了子進程退出的時候驶社,內(nèi)核就會發(fā)出一個SIGCHLD信號企量,給init進程,init進程就可以通過一個socket(通過調(diào)用函數(shù)get_signal_fd可以獲得它的文件描述符)來將接受到的SIGCHLD信號讀取回來亡电,并且調(diào)用函數(shù)handle_signal來對接收到的SIGCHLD信號來進行處理届巩,即回收哪些已經(jīng)變成僵尸進程的子進程。
PS:后面三件事都是可以通過文件描述符來描述的份乒,因此恕汇,initJ進程的入口函數(shù)main使用poll機制來同時輪訓它們,以便可以提高效率或辖。
下面 這里我們來看一下execute_one_command()函數(shù)和restart_processes()函數(shù)的具體執(zhí)行
13.1 execute_one_command函數(shù)的執(zhí)行
代碼在init.cpp 584行
void execute_one_command() {
Timer t;
char cmd_str[256] = "";
char name_str[256] = "";
//如果是第一次啟動瘾英,所以都是NULL,所以肯定可以進入這個判斷
//如果不是第一次啟動颂暇,因為得到cur_action或者cur_command都是null缺谴,并且如果這個command是當前action的最后一個command的話,會進入下面的這個判斷
if (!cur_action || !cur_command || is_last_command(cur_action, cur_command)) {
// 依次從action_queue獲取action
cur_action = action_remove_queue_head();
cur_command = NULL;
if (!cur_action) {
// 如果沒有action了耳鸯,則返回
return;
}
build_triggers_string(name_str, sizeof(name_str), cur_action);
INFO("processing action %p (%s)\n", cur_action, name_str);
// 如果是一個新的action的話湿蛔,會執(zhí)行到這一步去獲得first command
cur_command = get_first_command(cur_action);
} else {
// 如果還在action的內(nèi)部鏈表中,如果仍然存在沒有獲取到的command的話县爬,則會去獲取一下一個command
cur_command = get_next_command(cur_action, cur_command);
}
if (!cur_command) {
//如果可以獲取到command為空的話阳啥,會返回,反之捌省,繼續(xù)
return;
}
// 會調(diào)用這個command的func去執(zhí)行苫纤,執(zhí)行的參數(shù)個數(shù)為nargs,命令為args
int result = cur_command->func(cur_command->nargs, cur_command->args);
if (klog_get_level() >= KLOG_INFO_LEVEL) {
for (int i = 0; i < cur_command->nargs; i++) {
strlcat(cmd_str, cur_command->args[i], sizeof(cmd_str));
if (i < cur_command->nargs - 1) {
strlcat(cmd_str, " ", sizeof(cmd_str));
}
}
char source[256];
if (cur_command->filename) {
snprintf(source, sizeof(source), " (%s:%d)", cur_command->filename, cur_command->line);
} else {
*source = '\0';
}
INFO("Command '%s' action=%s%s returned %d took %.2fs\n",
cmd_str, cur_action ? name_str : "", source, result, t.duration());
}
}
其實這個函數(shù)就是執(zhí)行一個command纲缓。大體流程如下:
- 首選從action_queue取下struct action *act賦值給cur_action
- 其次從cur_action獲得struct command * 賦值給curcommand
- 最后執(zhí)行cur_command->func(cur_command->nargs, cur_command->args)
13.1.1 action_remove_queue_head函數(shù)的執(zhí)行
上面調(diào)用了action_remove_queue_head()函數(shù),我們來看下
代碼在init_parser.cpp 650行
struct action *action_remove_queue_head(void)
{
// 先做非空判斷
if (list_empty(&action_queue)) {
return 0;
} else {
// 如果還有未被執(zhí)行的隊列的話喊废,就將node指向現(xiàn)在的action_queue的頭指針
struct listnode *node = list_head(&action_queue);
// 取出action
struct action *act = node_to_item(node, struct action, qlist);
// 刪將這個節(jié)點從整個action_queue的列表中刪除
list_remove(node);
// 刪除節(jié)點后祝高,為了安全起見,將node自己指向自己污筷,以避免出現(xiàn)野指針工闺。
list_init(node);
// 返回 已經(jīng)找到的action
return act;
}
}
我們知道,其實是從action_queue中拿每一個結(jié)構(gòu)體的瓣蛀,拿到action之后陆蟆,就從action里面去取對應的command了。
13.1.2 action_remove_queue_head函數(shù)的執(zhí)行
代碼在init.cpp 543行
// 由于這個函數(shù)主要是從一個action里面找到一個command惋增,所以在傳遞的時候只傳遞action即可
static struct command *get_first_command(struct action *act)
{
struct listnode *node;
// 將node 指向action的commands結(jié)構(gòu)體
node = list_head(&act->commands);
if (!node || list_empty(&act->commands))
// 如果這話節(jié)點不存在叠殷,或者這個action的commands結(jié)構(gòu)體為空,則返回null
return NULL;
// 返回 第一個節(jié)點
return node_to_item(node, struct command, clist);
}
13.1.3 get_next_command函數(shù)的執(zhí)行
{
static struct command *get_next_command(struct action *act, struct command *cmd)
{
struct listnode *node;
node = cmd->clist.next;
// 如果不存在诈皿,則返回null
if (!node)
return NULL;
// 如果這個節(jié)點已經(jīng)是頭節(jié)點了林束,則返回null
if (node == &act->commands)
return NULL;
// 返回 next節(jié)點
return node_to_item(node, struct command, clist);
}
這個函數(shù) 主要是返回當前commands的下一個command
13.2 restart_processes()函數(shù)的執(zhí)行
當內(nèi)存不足時像棘,Android系統(tǒng)會自動殺死一些進程來釋放空間,所以當某些重要服務被殺壶冒,同時該服務進程并未設置為oneshot缕题,則必須重新啟動該服務進程。
代碼在init.cpp 473行
static void restart_processes()
{
process_needs_restart = 0;
service_for_each_flags(SVC_RESTARTING,
restart_service_if_needed);
}
我們看到這個函數(shù)什么都做胖腾,就是調(diào)用了service_for_each_flags函數(shù)烟零,那我們再來研究下這個函數(shù)
代碼在init_parser.cpp 533行
void service_for_each_flags(unsigned matchflags,
void (*func)(struct service *svc))
{
struct listnode *node;
struct service *svc;
list_for_each(node, &service_list) {
svc = node_to_item(node, struct service, slist);
if (svc->flags & matchflags) {
func(svc);
}
}
}
通過代碼我們知道 函數(shù)service_for_each_flags主要是循環(huán)遍歷服務鏈表,查找標志位為SVC_RESTARTING的服務咸作,當該服務進程死亡的時候锨阿,init進程監(jiān)控進程死亡事件,在處理該事件的時候會為該進程設置SVC_RESTARTING標志性宏,并調(diào)用restart_service_if_needed函數(shù)重啟服務群井。
我們來看下restart_service_if_needed函數(shù)
代碼在init.cpp 457行
static void restart_service_if_needed(struct service *svc)
{
time_t next_start_time = svc->time_started + 5;
if (next_start_time <= gettime()) {
svc->flags &= (~SVC_RESTARTING);
service_start(svc, NULL);
return;
}
if ((next_start_time < process_needs_restart) ||
(process_needs_restart == 0)) {
process_needs_restart = next_start_time;
}
}
只有當前時間大于服務啟動時間時,清除服務重啟標志并啟動該服務毫胜,
上一篇文章 1序言书斜、bootloader引導與Linux啟動
下一篇文章 Android系統(tǒng)啟動——3init.rc解析