本篇文章已授權微信公眾號 顧林海 獨家釋出
init程式啟動過程
Android系統基於Linux,init程式是Android系統中使用者空間的第一個程式,程式號為1,init原始碼在system/core/init目錄下。既然init程式是Android系統使用者空間的第一個程式,因此擔負著非常重要的責任,主要負責以下兩件事:
-
解析配置init.rc,然後啟動系統各種native程式,比如Zygote程式、SurfaceFlinger程式以及media程式等。
-
初始化並啟動屬性服務。
init程式的入口函式
init的入口函式是main,程式碼如下所示:
//路徑:/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通過兩種方式建立裝置節點檔案。
-
“冷插拔”(Cold Plug),即以預先定義的裝置資訊為基礎,當ueventd啟動後,統一建立裝置節點檔案。這一類裝置節點檔案也被稱為靜態節點檔案。
-
“熱插拔”(Hot Plug),即在系統執行中,當有裝置插入USB埠時,ueventd就會接收到這一事件,為插入的裝置動態建立裝置節點檔案。這一類裝置節點檔案也被稱為動態節點檔案。
註釋2處判斷當前程式是不是watchdogd。Android系統在長時間的執行下會面臨各種軟硬體的問題,為了解決這個問題,Android開發了WatchDog類作為軟體看門狗來監控SystemServer中的執行緒,一旦發現問題,WatchDog會殺死SystemServer程式,SystemServer的父程式Zygote接收到SystemServer的死亡訊號後,會殺死自己。Zygote程式死亡的訊號傳遞到init程式後,init程式會殺死Zygote程式所有的子程式並重啟Zygote。
註釋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
在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檔案,也可以是一個資料夾。如果是資料夾,那麼這個資料夾下面的所有檔案都會被匯入,但是它不會迴圈載入子目錄中的檔案。
啟動Zygote
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。