本篇文章已受權微信公衆號 顧林海 獨家發佈java
Android系統基於Linux,init進程是Android系統中用戶空間的第一個進程,進程號爲1,init源代碼在system/core/init目錄下。既然init進程是Android系統用戶空間的第一個進程,所以擔負着很是重要的責任,主要負責如下兩件事:linux
解析配置init.rc,而後啓動系統各類native進程,好比Zygote進程、SurfaceFlinger進程以及media進程等。android
初始化並啓動屬性服務。ios
init的入口函數是main,代碼以下所示:web
//路徑:/system/core/init/init.cpp
int main(int argc, char** argv) {
//註釋1
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
//註釋2
if (!strcmp(basename(argv[0]), "watchdogd")) {
return watchdogd_main(argc, argv);
}
//註釋3
if (REBOOT_BOOTLOADER_ON_PANIC) {
install_reboot_signal_handlers();
}
...
}
複製代碼
註釋1處判斷當前進程是否是ueventd。init進程建立子進程ueventd,並將建立設備節點文件的工做託付給ueventd。ueventd主要是負責設備節點的建立、權限設定等一系列工做。服務經過使用uevent,監控驅動發送的消息,作進一步處理。安全
ueventd經過兩種方式建立設備節點文件。bash
「冷插拔」(Cold Plug),即以預先定義的設備信息爲基礎,當ueventd啓動後,統一建立設備節點文件。這一類設備節點文件也被稱爲靜態節點文件。微信
「熱插拔」(Hot Plug),即在系統運行中,當有設備插入USB端口時,ueventd就會接收到這一事件,爲插入的設備動態建立設備節點文件。這一類設備節點文件也被稱爲動態節點文件。數據結構
註釋2處判斷當前進程是否是watchdogd。Android系統在長時間的運行下會面臨各類軟硬件的問題,爲了解決這個問題,Android開發了WatchDog類做爲軟件看門狗來監控SystemServer中的線程,一旦發現問題,WatchDog會殺死SystemServer進程,SystemServer的父進程Zygote接收到SystemServer的死亡信號後,會殺死本身。Zygote進程死亡的信號傳遞到init進程後,init進程會殺死Zygote進程全部的子進程並重啓Zygote。app
註釋3處判斷是否緊急重啓,若是是緊急重啓,就安裝對應的消息處理器。
//路徑:/system/core/init/init.cpp
int main(int argc, char** argv) {
...
//註釋1
add_environment("PATH", _PATH_DEFPATH);
//註釋2
bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
if (is_first_stage) {
// 用於記錄啓動時間
boot_clock::time_point start_time = boot_clock::now();
// 清除屏蔽字(file mode creation mask),保證新建的目錄的訪問權限不受屏蔽字影響
umask(0);
// 掛載tmpfs文件系統
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
// 掛載devpts文件系統
mount("devpts", "/dev/pts", "devpts", 0, NULL);
#define MAKE_STR(x) __STRING(x)
// 掛載proc文件系統
mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
//8.0新增, 收緊了cmdline目錄的權限
chmod("/proc/cmdline", 0440);
// 8.0新增,增長了個用戶組
gid_t groups[] = { AID_READPROC };
setgroups(arraysize(groups), groups);
// 掛載sysfs文件系統
mount("sysfs", "/sys", "sysfs", 0, NULL);
// 8.0新增
mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL)
// 提早建立了kmsg設備節點文件,用於輸出log信息
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));
...
}
...
}
複製代碼
註釋1處添加環境變量。註釋2處獲取本次啓動是不是系統啓動的第一階段,若是是第一階段,進入下面的if語句中,建立並掛載相關的文件系統。
以上建立並掛載的五類文件系統分別以下所示:
tmpfs:一種虛擬內存文件系統,它會將全部的文件存儲在虛擬內存中,若是你將tmpfs文件系統卸載後,那麼其下的全部的內容將不復存在。tmpfs既可使用RAM,也可使用交換分區,會根據你的實際須要而改變大小。tmpfs的速度很是驚人,畢竟它是駐留在RAM中的,即便用了交換分區,性能仍然很是卓越。因爲tmpfs是駐留在RAM的,所以它的內容是不持久的。斷電後,tmpfs的內容就消失了,這也是被稱做tmpfs的根本緣由。
devpts:爲僞終端提供了一個標準接口,它的標準掛接點是/dev/ pts。只要pty的主複合設備/dev/ptmx被打開,就會在/dev/pts下動態的建立一個新的pty設備文件。
proc:一個很是重要的虛擬文件系統,它能夠看做是內核內部數據結構的接口,經過它咱們能夠得到系統的信息,同時也可以在運行時修改特定的內核參數。
sysfs:與proc文件系統相似,也是一個不佔有任何磁盤空間的虛擬文件系統。它一般被掛接在/sys目錄下。sysfs文件系統是Linux2.6內核引入的,它把鏈接在系統上的設備和總線組織成爲一個分級的文件,使得它們能夠在用戶空間存取。
selinuxfs:用於支持SELinux的文件系統,SELinux提供了一套規則來編寫安全策略文件,這套規則被稱之爲 SELinux Policy 語言。
//路徑:/system/core/init/init.cpp
int main(int argc, char** argv) {
...
if (is_first_stage) {
...
//重定向輸入輸出/內核Log系統
InitKernelLogging(argv);
LOG(INFO) << "init first stage started!";
//掛在一些分區設備
if (!DoFirstStageMount()) {
LOG(ERROR) << "Failed to mount required partitions early ...";
panic();
}
//註釋1
SetInitAvbVersionInRecovery();
//註釋2
selinux_initialize(true);
...
}
...
}
複製代碼
註釋1處初始化安全框架AVB(Android Verified Boot),AVB主要用於防止系統文件自己被篡改,還包含了防止系統回滾的功能,以避免有人試圖回滾系統並利用之前的漏洞。註釋2處調用selinux_initialize啓動SELinux。
//路徑:/system/core/init/init.cpp
int main(int argc, char** argv) {
...
if (is_first_stage) {
...
}
...
//註釋1
property_init();
...
//註釋2
signal_handler_init();
...
//註釋3
start_property_service();
...
}
複製代碼
註釋1處經過property_init函數對屬性服務進行初始化,註釋3經過start_property_service函數啓動屬性服務。註釋2處signal_handler_init設置子進程退出的信號處理函數,當子進程異常退出的時候,init進程會去捕獲異常信息,當它捕獲到這些異常信息以後,就會調用該函數設置的相應的捕獲函數來處理。好比init進程的子進程Zygote死以後,init進程捕獲到這些異常信息,就會調用handle_signal()函數去重啓Zygote進程。
//路徑:/system/core/init/init.cpp
int main(int argc, char** argv) {
...
if (is_first_stage) {
...
}
...
if (bootscript.empty()) {
//註釋1
parser.ParseConfig("/init.rc");
...
} else {
...
}
...
while (true) {
...
ServiceManager::GetInstance().IsWaitingForExec())) {
//註釋2
am.ExecuteOneCommand();
}
if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
//註釋3
restart_processes();
...
}
...
}
return 0;
}
複製代碼
註釋1處解析init.rc配置文件,在註釋2處執行子進程對應的命令,也就是執行init.rc文件裏配置的命令。在註釋3處重啓死掉的service。
在init.rc中使用的語言稱爲Android Init Language,翻譯過來就是「Android初始化語言」,init語言共有五種類型的表達式,分別以下所示:
Action:Action中包含了一系列的Command。
Command:init語言中的命令。
Service:由init進程啓動的服務。
Option:對服務的配置選項。
Import:引入其餘配置文件。
Action表達式的語法以下所示:
on <trigger> [&& <trigger>]*
<command>
<command>
<command>
複製代碼
這裏的trigger是Action執行的觸發器,當觸發器條件知足時,command會被執行。觸發器有以下兩類:
事件觸發器:當指定的事件發生時觸發。事件可能由「trigger」命令發出,也多是init進程經過QueueEventTrigger()函數發出。
屬性觸發器:當指定的屬性知足某個值時觸發。
Action中的Command是init語言定義的命令,全部支持的命令以下表:
命令 | 參數格式 | 說明 |
---|---|---|
bootchart_init | - | 啓動bootchart |
chmod | octal-mode path | 改變文件的訪問權限 |
chown | owner group path | 改變文件的擁有者和組 |
class_start | serviceclass | 啓動指定類別的服務 |
class_stop | serviceclass | 中止並「disable」指定類別的服務 |
class_reset | serviceclass | 中止指定類別的服務,可是不「disable」它們 |
copy | src dst | 複製文件 |
domainname | name | 設置域名 |
enable | servicename | enable一個被disable的服務 |
exec | [seclabel[user[group]]] -- command [argument]* | fork一個子進程來執行指定的命令 |
export | name value | 導出環境變量 |
hostname | name | 設置host名稱 |
ifup | iterface | 使網卡在線 |
insmod | path | 安裝指定路徑的模塊 |
load_all_props | - | 從/system、/vendor等路徑載入屬性 |
load_persist_props | - | 載入持久化的屬性 |
loglevel | level | 設置內核的日誌級別 |
mkdir | path[mode][owner][group] | 建立目錄 |
mount_all | fstab[path]*[--option] | 掛載文件系統而且導入指定的.rc文件 |
mount | typedevicedir[flag]*[options] | 掛載一個文件系統 |
powerctl | - | 內部實現使用 |
restart | service | 重啓服務 |
restorecon | path[path]* | 設定文件的安全上下文 |
restorecon_recursive | path[path]* | restorecon的遞歸版本 |
rm | path | 對於指定路徑調用unlink(2) |
rmdir | path | 刪除文件夾 |
setprop | namevalue | 設置屬性值 |
setrlimit | resourcecurmax | 指定資源的rlimit |
start | service | 啓動服務 |
stop | service | 中止服務 |
swapon_all | fstab | 在指定文件上調用fs_mgr_swapon_all |
symlink | targetpath | 建立符合連接 |
sysclktz | mins_west_of_gmt | 指定系統時鐘基準 |
trigger | event | 觸發一個事件 |
umount | path | ummount指定的文件系統 |
verity_load_state | - | 內部實現使用 |
verity_update_state | mount_point | 內部實現使用 |
wait | path[timeout] | 等待某個文件存在直到超時,若存在則直接返回 |
write | pathcontent | 寫入內容到指定文件 |
Service是init進程啓動的可執行程序,Service表達式的語法以下所示:
service <name> <pathname> [ <argument> ]* <option>
<option>
複製代碼
Option是對服務的修飾,它們影響着init進程如何以及什麼時候啓動服務。全部支持的Option入下所示:
Option | 參數格式 | 說明 |
---|---|---|
critical | - | 標識爲系統關鍵服務,該服務若退出屢次將致使系統重啓到recovery模式 |
disabled | - | 不會隨着類別自動啓動,必須明確start |
setenv | name value | 爲啓動的進程設置環境變量 |
socket | nametypeperm[user[group[seclabel]]] | 建立UNIX Domain Socket |
user | username | 在執行服務以前切換用戶 |
group | groupname[groupname]* | 在執行服務以前切換組 |
seclabel | seclabel | 在執行服務以前切換seclabel |
oneshot | - | 一次性服務,死亡後不用重啓 |
class | name | 指定服務的類別 |
onrestart | - | 當服務重啓時執行命令 |
writepid | file... | 寫入子進程的pid到指定文件 |
import是一個關鍵字,而不是一個命令,能夠在.rc文件中經過這個關鍵字來加載其餘的.rc文件,它的語法以下:
import path
複製代碼
path能夠是另外一個.rc文件,也能夠是一個文件夾。若是是文件夾,那麼這個文件夾下面的全部文件都會被導入,可是它不會循環加載子目錄中的文件。
init.rc文件有以下配置代碼:
...import /init.${ro.zygote}.rc...on nonencrypted
class_start main
class_start late_start...
複製代碼
在init.rc文件的開頭使用了import類型語句來引入Zygote啓動腳本,其中ro.zygote根據不一樣的內容引入不一樣的文件,從Android 5.0開始,Android開始支持64位程序,Zygote就有了32位和64位之分,以下圖所示:
查看init.zygote64.rc的代碼以下所示:
service zygote /system/bin/app_process64 -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用於通知init進程建立名爲zygote的進程,這個進程執行程序的路徑爲/system/bin/app_process64,後面的代碼是傳遞給app_process64的參數,class main指的是Zygote的classname爲main。在解析Service類型語句時會將Service對象加入Service鏈表中。
再回過頭看init.rc配置文件:
...import /init.${ro.zygote}.rc...on nonencrypted
class_start main
class_start late_start...
複製代碼
class_start是一個command,對應的函數是do_class_start,用於啓動classname爲main的Service,也就是前面的Zygote,所以class_start main是用來啓動Zygote的,do_class_start函數在builtins.cpp中定義,代碼以下所示:
//路徑:/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;
}
複製代碼
ForEachServiceInClass函數會遍歷Service鏈表,找到classname爲main的Zygote,並執行StartIfNotDisabled函數,代碼以下所示:
//路徑:/system/core/init/service.cpp
bool Service::StartIfNotDisabled() {
if (!(flags_ & SVC_DISABLED)) {
return Start();
} else {
flags_ |= SVC_DISABLED_START;
}
return true;
}
複製代碼
若是Service沒有在其對應的rc文件中設置disabled選項,就會調用Start函數,Start函數以下所示:
//路徑:/system/core/init/service.cpp
bool Service::Start() {
...
pid_t pid = -1;
if (namespace_flags_) {
pid = clone(nullptr, nullptr, namespace_flags_ | SIGCHLD, nullptr);
} else {
//註釋1
pid = fork();
}
if (pid == 0) {
...
//註釋2
if (execve(strs[0], (char**) &strs[0], (char**) ENV) < 0) {
PLOG(ERROR) << "cannot execve('" << strs[0] << "')";
}
...
}
...
}
複製代碼
在註釋1處經過fork函數建立子進程,並返回pid,若是pid爲0說明當前代碼邏輯在子線程中運行,接着執行註釋2處的execve函數,來啓動Service子進程,進入Service的main函數中,若是Service是Zygote,執行程序的路徑是/system/bin/app_process64,對應的文件是app_main.cpp,也就是會進入app_main.cpp的main函數中。代碼以下所示:
int main(int argc, char* const argv[])
{
...
while (i < argc) {
const char* arg = argv[i++];
if (strcmp(arg, "--zygote") == 0) {
//註釋1
zygote = true;
niceName = ZYGOTE_NICE_NAME;
} else if (strcmp(arg, "--start-system-server") == 0) {
startSystemServer = true;
} else if (strcmp(arg, "--application") == 0) {
application = true;
} else if (strncmp(arg, "--nice-name=", 12) == 0) {
niceName.setTo(arg + 12);
} else if (strncmp(arg, "--", 2) != 0) {
className.setTo(arg);
break;
} else {
--i;
break;
}
}
...
if (zygote) {
//註釋2
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.");
}
}
複製代碼
在註釋1處判斷執行命令時是否帶了--zygote,若是攜帶了,zygote賦值爲true,接在註釋2處判斷若是zygote爲true,就會經過runtime.start啓動com.android.internal.os.ZygoteInit。