原文鏈接:https://blog.csdn.net/yiranfeng/article/details/103549394
摘要:init進程是linux系統中用戶空間的第一個進程,進程號爲1.當bootloader啓動後,啓動kernel,kernel啓動完後,在用戶空間啓動init進程,再通過init進程,來讀取init.rc中的相關配置,從而來啓動其他相關進程以及其他操作。
閱讀本文大約需要花費50分鐘。
文章的內容主要還是從源碼進行分析,雖然又臭又長,但是如果想要學習Android系統源碼,這是必要走的路,沒有捷徑。
相對於碎片學習,我更傾向於靜下心來花費1個小時認真的學習一段內容。
文章首發微信公衆號:IngresGe
專注於Android系統級源碼分析,Android的平臺設計,歡迎關注我,謝謝!
[Android取經之路] 的源碼都基於Android-Q(10.0) 進行分析
[Android取經之路] 系列文章:
《系統啓動篇》
Android10.0 日誌系統分析(一)-logd、logcat 指令說明、分類和屬性
Android10.0 日誌系統分析(二)-logd、logcat架構分析及日誌系統初始化
Android10.0 日誌系統分析(三)-logd、logcat讀寫日誌源碼分析
Android10.0 日誌系統分析(四)-selinux、kernel日誌在logd中的實現
《Binder通信原理》
Android10.0 Binder通信原理(一)Binder、HwBinder、VndBinder概要
Android10.0 Binder通信原理(二)-Binder入門篇
Android10.0 Binder通信原理(三)-ServiceManager篇
Android10.0 Binder通信原理(四)-Native-C\C++實例分析
Android10.0 Binder通信原理(五)-Binder驅動分析
Android10.0 Binder通信原理(六)-Binder數據如何完成定向打擊
Android10.0 Binder通信原理(七)-Framework binder示例
Android10.0 Binder通信原理(八)-Framework層分析
Android10.0 Binder通信原理(九)-AIDL Binder示例
Android10.0 Binder通信原理(十)-AIDL原理分析-Proxy-Stub設計模式
Android10.0 Binder通信原理(十一)-Binder總結
Android init 啓動進程主要分三個階段分析:
概述,Init如何被啓動
Init進程啓動的源碼分析
rc語法分析
1.概述:
init進程是linux系統中用戶空間的第一個進程,進程號爲1.
當bootloader啓動後,啓動kernel,kernel啓動完後,在用戶空間啓動init進程,再通過init進程,來讀取init.rc中的相關配置,從而來啓動其他相關進程以及其他操作。
init進程被賦予了很多重要工作,init進程啓動主要分爲兩個階段:
第一個階段完成以下內容:
ueventd/watchdogd跳轉及環境變量設置
掛載文件系統並創建目錄
初始化日誌輸出、掛載分區設備
啓用SELinux安全策略
開始第二階段前的準備
第二個階段完成以下內容:
初始化屬性系統
執行SELinux第二階段並恢復一些文件安全上下文
新建epoll並初始化子進程終止信號處理函數
設置其他系統屬性並開啓屬性服務
2.架構
2.1 Init進程如何被啓動?
Init進程是在Kernel啓動後,啓動的第一個用戶空間進程,PID爲1。
kernel_init啓動後,完成一些init的初始化操作,然後去系統根目錄下依次找ramdisk_execute_command和execute_command設置的應用程序,如果這兩個目錄都找不到,就依次去根目錄下找 /sbin/init,/etc/init,/bin/init,/bin/sh 這四個應用程序進行啓動,只要這些應用程序有一個啓動了,其他就不啓動了。
Android系統一般會在根目錄下放一個init的可執行文件,也就是說Linux系統的init進程在內核初始化完成後,就直接執行init這個文件。
2.2Init進程啓動後,做了哪些事?
Init進程啓動後,首先掛載文件系統、再掛載相應的分區,啓動SELinux安全策略,啓動屬性服務,解析rc文件,並啓動相應屬性服務進程,初始化epoll,依次設置signal、property、keychord這3個fd可讀時相對應的回調函數。進入無線循環,用來響應各個進程的變化與重建。
3.kernel啓動init進程 源碼分析
3.1 kernel_init
kernel/msm-4.19/init/main.c
kernel/msm-4.19/init/main.c
kernel_init()
|
run_init_process(ramdisk_execute_command) //運行可執行文件,啓動init進程
static int __ref kernel_init(void *unused)
{
kernel_init_freeable(); //進行init進程的一些初始化操作
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();// 等待所有異步調用執行完成,,在釋放內存前,必須完成所有的異步 __init 代碼
free_initmem();// 釋放所有init.* 段中的內存
mark_rodata_ro(); //arm64空實現
system_state = SYSTEM_RUNNING;// 設置系統狀態爲運行狀態
numa_default_policy(); // 設定NUMA系統的默認內存訪問策略
flush_delayed_fput(); // 釋放所有延時的struct file結構體
if (ramdisk_execute_command) { //ramdisk_execute_command的值爲"/init"
if (!run_init_process(ramdisk_execute_command)) //運行根目錄下的init程序
return 0;
pr_err("Failed to execute %s\n", ramdisk_execute_command);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) { //execute_command的值如果有定義就去根目錄下找對應的應用程序,然後啓動
if (!run_init_process(execute_command))
return 0;
pr_err("Failed to execute %s. Attempting defaults...\n",
execute_command);
}
if (!run_init_process("/sbin/init") || //如果ramdisk_execute_command和execute_command定義的應用程序都沒有找到,
//就到根目錄下找 /sbin/init,/etc/init,/bin/init,/bin/sh 這四個應用程序進行啓動
!run_init_process("/etc/init") ||
!run_init_process("/bin/init") ||
!run_init_process("/bin/sh"))
return 0;
panic("No init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}
3.2 do_basic_setup
kernel_init_freeable()
|
do_basic_setup()
static void __init do_basic_setup(void)
{
cpuset_init_smp();//針對SMP系統,初始化內核control group的cpuset子系統。
usermodehelper_init();// 創建khelper單線程工作隊列,用於協助新建和運行用戶空間程序
shmem_init();// 初始化共享內存
driver_init();// 初始化設備驅動
init_irq_proc();//創建/proc/irq目錄, 並初始化系統中所有中斷對應的子目錄
do_ctors();// 執行內核的構造函數
usermodehelper_enable();// 啓用usermodehelper
do_initcalls();//遍歷initcall_levels數組,調用裏面的initcall函數,這裏主要是對設備、驅動、文件系統進行初始化,
//之所有將函數封裝到數組進行遍歷,主要是爲了好擴展
random_int_secret_init();//初始化隨機數生成池
}
4. Init 進程啓動源碼分析
我們主要是分析Android Q(10.0) 的init的代碼。
涉及源碼文件:
platform/system/core/init/main.cpp
platform/system/core/init/init.cpp
platform/system/core/init/ueventd.cpp
platform/system/core/init/selinux.cpp
platform/system/core/init/subcontext.cpp
platform/system/core/base/logging.cpp
platform/system/core/init/first_stage_init.cpp
platform/system/core/init/first_stage_main.cpp
platform/system/core/init/first_stage_mount.cpp
platform/system/core/init/keyutils.h
platform/system/core/init/property_service.cpp
platform/external/selinux/libselinux/src/label.c
platform/system/core/init/signal_handler.cpp
platform/system/core/init/service.cpp
4.1 Init 進程入口
前面已經通過kernel_init,啓動了init進程,init進程屬於一個守護進程,準確的說,它是Linux系統中用戶控制的第一個進程,它的進程號爲1。它的生命週期貫穿整個Linux內核運行的始終。Android中所有其它的進程共同的鼻祖均爲init進程。
可以通過"adb shell ps |grep init" 的命令來查看init的進程號。
Android Q(10.0) 的init入口函數由原先的init.cpp 調整到了main.cpp,把各個階段的操作分離開來,使代碼更加簡潔命令,接下來我們就從main函數開始學習。
[system/core/init/main.cpp]
/*
* 1.第一個參數argc表示參數個數,第二個參數是參數列表,也就是具體的參數
* 2.main函數有四個參數入口,
*一是參數中有ueventd,進入ueventd_main
*二是參數中有subcontext,進入InitLogging 和SubcontextMain
*三是參數中有selinux_setup,進入SetupSelinux
*四是參數中有second_stage,進入SecondStageMain
*3.main的執行順序如下:
* (1)ueventd_main init進程創建子進程ueventd,
* 並將創建設備節點文件的工作託付給ueventd,ueventd通過兩種方式創建設備節點文件
* (2)FirstStageMain 啓動第一階段
* (3)SetupSelinux 加載selinux規則,並設置selinux日誌,完成SELinux相關工作
* (4)SecondStageMain 啓動第二階段
*/
int main(int argc, char** argv) {
//當argv[0]的內容爲ueventd時,strcmp的值爲0,!strcmp爲1
//1表示true,也就執行ueventd_main,ueventd主要是負責設備節點的創建、權限設定等一些列工作
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
//當傳入的參數個數大於1時,執行下面的幾個操作
if (argc > 1) {
//參數爲subcontext,初始化日誌系統,
if (!strcmp(argv[1], "subcontext")) {
android::base::InitLogging(argv, &android::base::KernelLogger);
const BuiltinFunctionMap function_map;
return SubcontextMain(argc, argv, &function_map);
}
//參數爲“selinux_setup”,啓動Selinux安全策略
if (!strcmp(argv[1], "selinux_setup")) {
return SetupSelinux(argv);
}
//參數爲“second_stage”,啓動init進程第二階段
if (!strcmp(argv[1], "second_stage")) {
return SecondStageMain(argc, argv);
}
}
// 默認啓動init進程第一階段
return FirstStageMain(argc, argv);
}
4.2 ueventd_main
代碼路徑:platform/system/core/init/ueventd.cpp
Android根文件系統的鏡像中不存在“/dev”目錄,該目錄是init進程啓動後動態創建的。
因此,建立Android中設備節點文件的重任,也落在了init進程身上。爲此,init進程創建子進程ueventd,並將創建設備節點文件的工作託付給ueventd。
ueventd通過兩種方式創建設備節點文件。
第一種方式對應“冷插拔”(Cold Plug),即以預先定義的設備信息爲基礎,當ueventd啓動後,統一創建設備節點文件。這一類設備節點文件也被稱爲靜態節點文件。
第二種方式對應“熱插拔”(Hot Plug),即在系統運行中,當有設備插入USB端口時,ueventd就會接收到這一事件,爲插入的設備動態創建設備節點文件。這一類設備節點文件也被稱爲動態節點文件。
int ueventd_main(int argc, char** argv) {
//設置新建文件的默認值,這個與chmod相反,這裏相當於新建文件後的權限爲666
umask(000);
//初始化內核日誌,位於節點/dev/kmsg, 此時logd、logcat進程還沒有起來,
//採用kernel的log系統,打開的設備節點/dev/kmsg, 那麼可通過cat /dev/kmsg來獲取內核log。
android::base::InitLogging(argv, &android::base::KernelLogger);
//註冊selinux相關的用於打印log的回調函數
SelinuxSetupKernelLogging();
SelabelInitialize();
//解析xml,根據不同SOC廠商獲取不同的hardware rc文件
auto ueventd_configuration = ParseConfig({"/ueventd.rc", "/vendor/ueventd.rc",
"/odm/ueventd.rc", "/ueventd." + hardware + ".rc"});
//冷啓動
if (access(COLDBOOT_DONE, F_OK) != 0) {
ColdBoot cold_boot(uevent_listener, uevent_handlers);
cold_boot.Run();
}
for (auto& uevent_handler : uevent_handlers) {
uevent_handler->ColdbootDone();
}
//忽略子進程終止信號
signal(SIGCHLD, SIG_IGN);
// Reap and pending children that exited between the last call to waitpid() and setting SIG_IGN
// for SIGCHLD above.
//在最後一次調用waitpid()和爲上面的sigchld設置SIG_IGN之間退出的獲取和掛起的子級
while (waitpid(-1, nullptr, WNOHANG) > 0) {
}
//監聽來自驅動的uevent,進行“熱插拔”處理
uevent_listener.Poll([&uevent_handlers](const Uevent& uevent) {
for (auto& uevent_handler : uevent_handlers) {
uevent_handler->HandleUevent(uevent); //熱啓動,創建設備
}
return ListenerAction::kContinue;
});
return 0;
}
4.3 init 進程啓動第一階段
代碼路徑:platform\system\core\init\first_stage_init.cpp
init進程第一階段做的主要工作是掛載分區,創建設備節點和一些關鍵目錄,初始化日誌輸出系統,啓用SELinux安全策略
第一階段完成以下內容:
/* 01. 創建文件系統目錄並掛載相關的文件系統 */
/* 02. 屏蔽標準的輸入輸出/初始化內核log系統 */
4.3.1 FirstStageMain
int FirstStageMain(int argc, char** argv) {
//init crash時重啓引導加載程序
//這個函數主要作用將各種信號量,如SIGABRT,SIGBUS等的行爲設置爲SA_RESTART,一旦監聽到這些信號即執行重啓系統
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers();
}
//清空文件權限
umask(0);
CHECKCALL(clearenv());
CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
//在RAM內存上獲取基本的文件系統,剩餘的被rc文件所用
CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
CHECKCALL(mkdir("/dev/pts", 0755));
CHECKCALL(mkdir("/dev/socket", 0755));
CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));
#define MAKE_STR(x) __STRING(x)
CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));
#undef MAKE_STR
// 非特權應用不能使用Andrlid cmdline
CHECKCALL(chmod("/proc/cmdline", 0440));
gid_t groups[] = {AID_READPROC};
CHECKCALL(setgroups(arraysize(groups), groups));
CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));
CHECKCALL(mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11)));
if constexpr (WORLD_WRITABLE_KMSG) {
CHECKCALL(mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11)));
}
CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8)));
CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9)));
//這對於日誌包裝器是必需的,它在ueventd運行之前被調用
CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));
CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));
//在第一階段掛在tmpfs、mnt/vendor、mount/product分區。其他的分區不需要在第一階段加載,
//只需要在第二階段通過rc文件解析來加載。
CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=1000"));
//創建可供讀寫的vendor目錄
CHECKCALL(mkdir("/mnt/vendor", 0755));
// /mnt/product is used to mount product-specific partitions that can not be
// part of the product partition, e.g. because they are mounted read-write.
CHECKCALL(mkdir("/mnt/product", 0755));
// 掛載APEX,這在Android 10.0中特殊引入,用來解決碎片化問題,類似一種組件方式,對Treble的增強,
// 不寫谷歌特殊更新不需要完整升級整個系統版本,只需要像升級APK一樣,進行APEX組件升級
CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=0"));
// /debug_ramdisk is used to preserve additional files from the debug ramdisk
CHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=0"));
#undef CHECKCALL
//把標準輸入、標準輸出和標準錯誤重定向到空設備文件"/dev/null"
SetStdioToDevNull(argv);
//在/dev目錄下掛載好 tmpfs 以及 kmsg
//這樣就可以初始化 /kernel Log 系統,供用戶打印log
InitKernelLogging(argv);
...
/* 初始化一些必須的分區
*主要作用是去解析/proc/device-tree/firmware/android/fstab,
* 然後得到"/system", "/vendor", "/odm"三個目錄的掛載信息
*/
if (!DoFirstStageMount()) {
LOG(FATAL) << "Failed to mount required partitions early ...";
}
struct stat new_root_info;
if (stat("/", &new_root_info) != 0) {
PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk";
old_root_dir.reset();
}
if (old_root_dir && old_root_info.st_dev != new_root_info.st_dev) {
FreeRamdisk(old_root_dir.get(), old_root_info.st_dev);
}
SetInitAvbVersionInRecovery();
static constexpr uint32_t kNanosecondsPerMillisecond = 1e6;
uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;
setenv("INIT_STARTED_AT", std::to_string(start_ms).c_str(), 1);
//啓動init進程,傳入參數selinux_steup
// 執行命令: /system/bin/init selinux_setup
const char* path = "/system/bin/init";
const char* args[] = {path, "selinux_setup", nullptr};
execv(path, const_cast<char**>(args));
PLOG(FATAL) << "execv(\"" << path << "\") failed";
return 1;
}
4.4 加載SELinux規則
SELinux是「Security-Enhanced Linux」的簡稱,是美國國家安全局「NSA=The National Security Agency」
和SCC(Secure Computing Corporation)開發的 Linux的一個擴張強制訪問控制安全模塊。
在這種訪問控制體系的限制下,進程只能訪問那些在他的任務中所需要文件。
selinux有兩種工作模式:
permissive,所有的操作都被允許(即沒有MAC),但是如果違反權限的話,會記錄日誌,一般eng模式用
enforcing,所有操作都會進行權限檢查。一般user和user-debug模式用
不管是security_setenforce還是security_getenforce都是去操作/sys/fs/selinux/enforce 文件, 0表示permissive 1表示enforcing
4.4.1 SetupSelinux
說明:初始化selinux,加載SELinux規則,配置SELinux相關log輸出,並啓動第二階段
代碼路徑: platform\system\core\init\selinux.cpp
/*此函數初始化selinux,然後執行init以在init selinux中運行*/
int SetupSelinux(char** argv) {
//初始化Kernel日誌
InitKernelLogging(argv);
// Debug版本init crash時重啓引導加載程序
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers();
}
//註冊回調,用來設置需要寫入kmsg的selinux日誌
SelinuxSetupKernelLogging();
//加載SELinux規則
SelinuxInitialize();
/*
*我們在內核域中,希望轉換到init域。在其xattrs中存儲selabel的文件系統(如ext4)不需要顯式restorecon,
*但其他文件系統需要。尤其是對於ramdisk,如對於a/b設備的恢復映像,這是必需要做的一步。
*其實就是當前在內核域中,在加載Seliux後,需要重新執行init切換到C空間的用戶態
*/
if (selinux_android_restorecon("/system/bin/init", 0) == -1) {
PLOG(FATAL) << "restorecon failed of /system/bin/init failed";
}
//準備啓動innit進程,傳入參數second_stage
const char* path = "/system/bin/init";
const char* args[] = {path, "second_stage", nullptr};
execv(path, const_cast<char**>(args));
/*
*執行 /system/bin/init second_stage, 進入第二階段
*/
PLOG(FATAL) << "execv(\"" << path << "\") failed";
return 1;
}
4.4.2 SelinuxInitialize()
/*加載selinux 規則*/
void SelinuxInitialize() {
LOG(INFO) << "Loading SELinux policy";
if (!LoadPolicy()) {
LOG(FATAL) << "Unable to load SELinux policy";
}
//獲取當前Kernel的工作模式
bool kernel_enforcing = (security_getenforce() == 1);
//獲取工作模式的配置
bool is_enforcing = IsEnforcing();
//如果當前的工作模式與配置的不同,就將當前的工作模式改掉
if (kernel_enforcing != is_enforcing) {
if (security_setenforce(is_enforcing)) {
PLOG(FATAL) << "security_setenforce(" << (is_enforcing ? "true" : "false")
<< ") failed";
}
}
if (auto result = WriteFile("/sys/fs/selinux/checkreqprot", "0"); !result) {
LOG(FATAL) << "Unable to write to /sys/fs/selinux/checkreqprot: " << result.error();
}
}
/*
*加載SELinux規則
*這裏區分了兩種情況,這兩種情況只是區分從哪裏加載安全策略文件,
*第一個是從 /vendor/etc/selinux/precompiled_sepolicy 讀取,
*第二個是從 /sepolicy 讀取,他們最終都是調用selinux_android_load_policy_from_fd方法
*/
bool LoadPolicy() {
return IsSplitPolicyDevice() ? LoadSplitPolicy() : LoadMonolithicPolicy();
}
4.5 init進程啓動第二階段
第二階段主要內容:
創建進程會話密鑰並初始化屬性系統
進行SELinux第二階段並恢復一些文件安全上下文
新建epoll並初始化子進程終止信號處理函數,詳細看第五節-信號處理
啓動匹配屬性的服務端, 詳細查看第六節-屬性服務
解析init.rc等文件,建立rc文件的action 、service,啓動其他進程,詳細查看第七節-rc文件解析
4.5.1 SecondStageMain
int SecondStageMain(int argc, char** argv) {
/* 01. 創建進程會話密鑰並初始化屬性系統 */
keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);
//創建 /dev/.booting 文件,就是個標記,表示booting進行中
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
// 初始化屬性系統,並從指定文件讀取屬性
property_init();
/* 02. 進行SELinux第二階段並恢復一些文件安全上下文 */
SelinuxRestoreContext();
/* 03. 新建epoll並初始化子進程終止信號處理函數 */
Epoll epoll;
if (auto result = epoll.Open(); !result) {
PLOG(FATAL) << result.error();
}
InstallSignalFdHandler(&epoll);
/* 04. 設置其他系統屬性並開啓系統屬性服務*/
StartPropertyService(&epoll);
/* 05 解析init.rc等文件,建立rc文件的action 、service,啓動其他進程*/
ActionManager& am = ActionManager::GetInstance();
ServiceList& sm = ServiceList::GetInstance();
LoadBootScripts(am, sm);
}
代碼流程詳細解析:
int SecondStageMain(int argc, char** argv) {
/*
*init crash時重啓引導加載程序
*這個函數主要作用將各種信號量,如SIGABRT,SIGBUS等的行爲設置爲SA_RESTART,一旦監聽到這些信號即執行重啓系統
*/
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers();
}
//把標準輸入、標準輸出和標準錯誤重定向到空設備文件"/dev/null"
SetStdioToDevNull(argv);
//在/dev目錄下掛載好 tmpfs 以及 kmsg
//這樣就可以初始化 /kernel Log 系統,供用戶打印log
InitKernelLogging(argv);
LOG(INFO) << "init second stage started!";
// 01. 創建進程會話密鑰並初始化屬性系統
keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);
//創建 /dev/.booting 文件,就是個標記,表示booting進行中
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
// 初始化屬性系統,並從指定文件讀取屬性
property_init();
/*
* 1.如果參數同時從命令行和DT傳過來,DT的優先級總是大於命令行的
* 2.DT即device-tree,中文意思是設備樹,這裏面記錄自己的硬件配置和系統運行參數,
*/
process_kernel_dt(); // 處理 DT屬性
process_kernel_cmdline(); // 處理命令行屬性
// 處理一些其他的屬性
export_kernel_boot_props();
// Make the time that init started available for bootstat to log.
property_set("ro.boottime.init", getenv("INIT_STARTED_AT"));
property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK"));
// Set libavb version for Framework-only OTA match in Treble build.
const char* avb_version = getenv("INIT_AVB_VERSION");
if (avb_version) property_set("ro.boot.avb_version", avb_version);
// See if need to load debug props to allow adb root, when the device is unlocked.
const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");
if (force_debuggable_env && AvbHandle::IsDeviceUnlocked()) {
load_debug_prop = "true"s == force_debuggable_env;
}
// 基於cmdline設置memcg屬性
bool memcg_enabled = android::base::GetBoolProperty("ro.boot.memcg",false);
if (memcg_enabled) {
// root memory control cgroup
mkdir("/dev/memcg", 0700);
chown("/dev/memcg",AID_ROOT,AID_SYSTEM);
mount("none", "/dev/memcg", "cgroup", 0, "memory");
// app mem cgroups, used by activity manager, lmkd and zygote
mkdir("/dev/memcg/apps/",0755);
chown("/dev/memcg/apps/",AID_SYSTEM,AID_SYSTEM);
mkdir("/dev/memcg/system",0550);
chown("/dev/memcg/system",AID_SYSTEM,AID_SYSTEM);
}
// 清空這些環境變量,之前已經存到了系統屬性中去了
unsetenv("INIT_STARTED_AT");
unsetenv("INIT_SELINUX_TOOK");
unsetenv("INIT_AVB_VERSION");
unsetenv("INIT_FORCE_DEBUGGABLE");
// Now set up SELinux for second stage.
SelinuxSetupKernelLogging();
SelabelInitialize();
/*
* 02. 進行SELinux第二階段並恢復一些文件安全上下文
* 恢複相關文件的安全上下文,因爲這些文件是在SELinux安全機制初始化前創建的,
* 所以需要重新恢復上下文
*/
SelinuxRestoreContext();
/*
* 03. 新建epoll並初始化子進程終止信號處理函數
* 創建epoll實例,並返回epoll的文件描述符
*/
Epoll epoll;
if (auto result = epoll.Open(); !result) {
PLOG(FATAL) << result.error();
}
/*
*主要是創建handler處理子進程終止信號,註冊一個signal到epoll進行監聽
*進行子繼承處理
*/
InstallSignalFdHandler(&epoll);
// 進行默認屬性配置相關的工作
property_load_boot_defaults(load_debug_prop);
UmountDebugRamdisk();
fs_mgr_vendor_overlay_mount_all();
export_oem_lock_status();
/*
*04. 設置其他系統屬性並開啓系統屬性服務
*/
StartPropertyService(&epoll);
MountHandler mount_handler(&epoll);
//爲USB存儲設置udc Contorller, sys/class/udc
set_usb_controller();
// 匹配命令和函數之間的對應關係
const BuiltinFunctionMap function_map;
Action::set_function_map(&function_map);
if (!SetupMountNamespaces()) {
PLOG(FATAL) << "SetupMountNamespaces failed";
}
// 初始化文件上下文
subcontexts = InitializeSubcontexts();
/*
*05 解析init.rc等文件,建立rc文件的action 、service,啓動其他進程
*/
ActionManager& am = ActionManager::GetInstance();
ServiceList& sm = ServiceList::GetInstance();
LoadBootScripts(am, sm);
// Turning this on and letting the INFO logging be discarded adds 0.2s to
// Nexus 9 boot time, so it's disabled by default.
if (false) DumpState();
// 當GSI腳本running時,確保GSI狀態可用.
if (android::gsi::IsGsiRunning()) {
property_set("ro.gsid.image_running", "1");
} else {
property_set("ro.gsid.image_running", "0");
}
am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
// 執行rc文件中觸發器爲 on early-init 的語句
am.QueueEventTrigger("early-init");
// 等冷插拔設備初始化完成
am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
// 開始查詢來自 /dev的 action
am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");
am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
// 設備組合鍵的初始化操作
Keychords keychords;
am.QueueBuiltinAction(
[&epoll, &keychords](const BuiltinArguments& args) -> Result<Success> {
for (const auto& svc : ServiceList::GetInstance()) {
keychords.Register(svc->keycodes());
}
keychords.Start(&epoll, HandleKeychord);
return Success();
},
"KeychordInit");
//在屏幕上顯示Android 靜態LOGO
am.QueueBuiltinAction(console_init_action, "console_init");
// 執行rc文件中觸發器爲on init的語句
am.QueueEventTrigger("init");
// Starting the BoringSSL self test, for NIAP certification compliance.
am.QueueBuiltinAction(StartBoringSslSelfTest, "StartBoringSslSelfTest");
// Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
// wasn't ready immediately after wait_for_coldboot_done
am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
// Initialize binder before bringing up other system services
am.QueueBuiltinAction(InitBinder, "InitBinder");
// 當設備處於充電模式時,不需要mount文件系統或者啓動系統服務
// 充電模式下,將charger假如執行隊列,否則把late-init假如執行隊列
std::string bootmode = GetProperty("ro.bootmode", "");
if (bootmode == "charger") {
am.QueueEventTrigger("charger");
} else {
am.QueueEventTrigger("late-init");
}
// 基於屬性當前狀態 運行所有的屬性觸發器.
am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
while (true) {
// By default, sleep until something happens.
auto epoll_timeout = std::optional<std::chrono::milliseconds>{};
if (do_shutdown && !shutting_down) {
do_shutdown = false;
if (HandlePowerctlMessage(shutdown_command)) {
shutting_down = true;
}
}
//依次執行每個action中攜帶command對應的執行函數
if (!(waiting_for_prop || Service::is_exec_service_running())) {
am.ExecuteOneCommand();
}
if (!(waiting_for_prop || Service::is_exec_service_running())) {
if (!shutting_down) {
auto next_process_action_time = HandleProcessActions();
// If there's a process that needs restarting, wake up in time for that.
if (next_process_action_time) {
epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(
*next_process_action_time - boot_clock::now());
if (*epoll_timeout < 0ms) epoll_timeout = 0ms;
}
}
// If there's more work to do, wake up again immediately.
if (am.HasMoreCommands()) epoll_timeout = 0ms;
}
// 循環等待事件發生
if (auto result = epoll.Wait(epoll_timeout); !result) {
LOG(ERROR) << result.error();
}
}
return 0;
}
5. 信號處理
init是一個守護進程,爲了防止init的子進程成爲殭屍進程(zombie process),需要init在子進程在結束時獲取子進程的結束碼,通過結束碼將程序表中的子進程移除,防止成爲殭屍進程的子進程佔用程序表的空間(程序表的空間達到上限時,系統就不能再啓動新的進程了,會引起嚴重的系統問題)。
子進程重啓流程如下圖所示:
信號處理主要工作:
初始化信號signal句柄
循環處理子進程
註冊epoll句柄
處理子進程終止
注: EPOLL類似於POLL,是Linux中用來做事件觸發的,跟EventBus功能差不多。linux很長的時間都在使用select來做事件觸發,它是通過輪詢來處理的,輪詢的fd數目越多,自然耗時越多,對於大量的描述符處理,EPOLL更有優勢
5.1 InstallSignalFdHandler
在linux當中,父進程是通過捕捉SIGCHLD信號來得知子進程運行結束的情況,SIGCHLD信號會在子進程終止的時候發出,瞭解這些背景後,我們來看看init進程如何處理這個信號。
首先,新建一個sigaction結構體,sa_handler是信號處理函數,指向內核指定的函數指針SIG_DFL和Android 9.0及之前的版本不同,這裏不再通過socket的讀寫句柄進行接收信號,改成了內核的信號處理函數SIG_DFL。
然後,sigaction(SIGCHLD, &act, nullptr) 這個是建立信號綁定關係,也就是說當監聽到SIGCHLD信號時,由act這個sigaction結構體處理
最後,RegisterHandler 的作用就是signal_read_fd(之前的s[1])收到信號,觸發handle_signal
終上所述,InstallSignalFdHandler函數的作用就是,接收到SIGCHLD信號時觸發HandleSignalFd進行信號處理
信號處理示意圖:
代碼路徑:platform/system/core/init.cpp
說明:該函數主要的作用是初始化子進程終止信號處理過程
static void InstallSignalFdHandler(Epoll* epoll) {
// SA_NOCLDSTOP使init進程只有在其子進程終止時纔會受到SIGCHLD信號
const struct sigaction act { .sa_handler = SIG_DFL, .sa_flags = SA_NOCLDSTOP };
sigaction(SIGCHLD, &act, nullptr);
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
if (!IsRebootCapable()) {
// 如果init不具有 CAP_SYS_BOOT的能力,則它此時正值容器中運行
// 在這種場景下,接收SIGTERM 將會導致系統關閉
sigaddset(&mask, SIGTERM);
}
if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) {
PLOG(FATAL) << "failed to block signals";
}
// 註冊處理程序以解除對子進程中的信號的阻止
const int result = pthread_atfork(nullptr, nullptr, &UnblockSignals);
if (result != 0) {
LOG(FATAL) << "Failed to register a fork handler: " << strerror(result);
}
//創建信號句柄
signal_fd = signalfd(-1, &mask, SFD_CLOEXEC);
if (signal_fd == -1) {
PLOG(FATAL) << "failed to create signalfd";
}
//信號註冊,當signal_fd收到信號時,觸發HandleSignalFd
if (auto result = epoll->RegisterHandler(signal_fd, HandleSignalFd); !result) {
LOG(FATAL) << result.error();
}
}
5.2 RegisterHandler
代碼路徑:/platform/system/core/epoll.cpp
說明:信號註冊,把fd句柄加入到 epoll_fd_的監聽隊列中
Result<void> Epoll::RegisterHandler(int fd, std::function<void()> handler, uint32_t events) {
if (!events) {
return Error() << "Must specify events";
}
auto [it, inserted] = epoll_handlers_.emplace(fd, std::move(handler));
if (!inserted) {
return Error() << "Cannot specify two epoll handlers for a given FD";
}
epoll_event ev;
ev.events = events;
// std::map's iterators do not get invalidated until erased, so we use the
// pointer to the std::function in the map directly for epoll_ctl.
ev.data.ptr = reinterpret_cast<void*>(&it->second);
// 將fd的可讀事件加入到epoll_fd_的監聽隊列中
if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &ev) == -1) {
Result<void> result = ErrnoError() << "epoll_ctl failed to add fd";
epoll_handlers_.erase(fd);
return result;
}
return {};
}
5.3 HandleSignalFd
代碼路徑:platform/system/core/init.cpp
說明:監控SIGCHLD信號,調用 ReapAnyOutstandingChildren 來 終止出現問題的子進程
static void HandleSignalFd() {
signalfd_siginfo siginfo;
ssize_t bytes_read = TEMP_FAILURE_RETRY(read(signal_fd, &siginfo, sizeof(siginfo)));
if (bytes_read != sizeof(siginfo)) {
PLOG(ERROR) << "Failed to read siginfo from signal_fd";
return;
}
//監控SIGCHLD信號
switch (siginfo.ssi_signo) {
case SIGCHLD:
ReapAnyOutstandingChildren();
break;
case SIGTERM:
HandleSigtermSignal(siginfo);
break;
default:
PLOG(ERROR) << "signal_fd: received unexpected signal " << siginfo.ssi_signo;
break;
}
}
5.4 ReapOneProcess
代碼路徑:/platform/system/core/sigchld_handle.cpp
說明:ReapOneProcess是最終的處理函數了,這個函數先用waitpid找出掛掉進程的pid,然後根據pid找到對應Service,
最後調用Service的Reap方法清除資源,根據進程對應的類型,決定是否重啓機器或重啓進程
void ReapAnyOutstandingChildren() {
while (ReapOneProcess()) {
}
}
static bool ReapOneProcess() {
siginfo_t siginfo = {};
//用waitpid函數獲取狀態發生變化的子進程pid
//waitpid的標記爲WNOHANG,即非阻塞,返回爲正值就說明有進程掛掉了
if (TEMP_FAILURE_RETRY(waitid(P_ALL, 0, &siginfo, WEXITED | WNOHANG | WNOWAIT)) != 0) {
PLOG(ERROR) << "waitid failed";
return false;
}
auto pid = siginfo.si_pid;
if (pid == 0) return false;
// 當我們知道當前有一個殭屍pid,我們使用scopeguard來清楚該pid
auto reaper = make_scope_guard([pid] { TEMP_FAILURE_RETRY(waitpid(pid, nullptr, WNOHANG)); });
std::string name;
std::string wait_string;
Service* service = nullptr;
if (SubcontextChildReap(pid)) {
name = "Subcontext";
} else {
//通過pid找到對應的service
service = ServiceList::GetInstance().FindService(pid, &Service::pid);
if (service) {
name = StringPrintf("Service '%s' (pid %d)", service->name().c_str(), pid);
if (service->flags() & SVC_EXEC) {
auto exec_duration = boot_clock::now() - service->time_started();
auto exec_duration_ms =
std::chrono::duration_cast<std::chrono::milliseconds>(exec_duration).count();
wait_string = StringPrintf(" waiting took %f seconds", exec_duration_ms / 1000.0f);
} else if (service->flags() & SVC_ONESHOT) {
auto exec_duration = boot_clock::now() - service->time_started();
auto exec_duration_ms =
std::chrono::duration_cast<std::chrono::milliseconds>(exec_duration)
.count();
wait_string = StringPrintf(" oneshot service took %f seconds in background",exec_duration_ms / 1000.0f);
}
} else {
name = StringPrintf("Untracked pid %d", pid);
}
}
if (siginfo.si_code == CLD_EXITED) {
LOG(INFO) << name << " exited with status " << siginfo.si_status << wait_string;
} else {
LOG(INFO) << name << " received signal " << siginfo.si_status << wait_string;
}
//沒有找到service,說明已經結束了,退出
if (!service) return true;
service->Reap(siginfo);//清除子進程相關的資源
if (service->flags() & SVC_TEMPORARY) {
ServiceList::GetInstance().RemoveService(*service); //移除該service
}
return true;
}
6.屬性服務
我們在開發和調試過程中看到通過property_set可以輕鬆設置系統屬性,那幹嘛這裏還要啓動一個屬性服務呢?這裏其實涉及到一些權限的問題,不是所有進程都可以隨意修改任何的系統屬性,
Android將屬性的設置統一交由init進程管理,其他進程不能直接修改屬性,而只能通知init進程來修改,而在這過程中,init進程可以進行權限控制,我們來看看具體的流程是什麼
6.1 property_init
代碼路徑:platform/system/core/property_service.cpp
說明:初始化屬性系統,並從指定文件讀取屬性,並進行SELinux註冊,進行屬性權限控制
清除緩存,這裏主要是清除幾個鏈表以及在內存中的映射,新建property_filename目錄,這個目錄的值爲 /dev/_properties_
然後就是調用CreateSerializedPropertyInfo加載一些系統屬性的類別信息,最後將加載的鏈表寫入文件並映射到內存
void property_init() {
//設置SELinux回調,進行權限控制
selinux_callback cb;
cb.func_audit = PropertyAuditCallback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);
CreateSerializedPropertyInfo();
if (__system_property_area_init()) {
LOG(FATAL) << "Failed to initialize property area";
}
if (!property_info_area.LoadDefaultPath()) {
LOG(FATAL) << "Failed to load serialized property info file";
}
}
通過CreateSerializedPropertyInfo 來加載以下目錄的contexts:
1)與SELinux相關
/system/etc/selinux/plat_property_contexts
/vendor/etc/selinux/vendor_property_contexts
/vendor/etc/selinux/nonplat_property_contexts
/product/etc/selinux/product_property_contexts
/odm/etc/selinux/odm_property_contexts
2)與SELinux無關
/plat_property_contexts
/vendor_property_contexts
/nonplat_property_contexts
/product_property_contexts
/odm_property_contexts
6.2 StartPropertyService
代碼路徑: platform/system/core/init.cpp
說明:啓動屬性服務
首先創建一個socket並返回文件描述符,然後設置最大併發數爲8,其他進程可以通過這個socket通知init進程修改系統屬性,
最後註冊epoll事件,也就是當監聽到property_set_fd改變時調用handle_property_set_fd
void StartPropertyService(Epoll* epoll) {
property_set("ro.property_service.version", "2");
//建立socket連接
if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
false, 0666, 0, 0, {})) {
property_set_fd = *result;
} else {
PLOG(FATAL) << "start_property_service socket creation failed: " << result.error();
}
// 最大監聽8個併發
listen(property_set_fd, 8);
// 註冊property_set_fd,當收到句柄改變時,通過handle_property_set_fd來處理
if (auto result = epoll->RegisterHandler(property_set_fd, handle_property_set_fd); !result) {
PLOG(FATAL) << result.error();
}
}
6.3 handle_property_set_fd
代碼路徑:platform/system/core/property_service.cpp
說明:建立socket連接,然後從socket中讀取操作信息,根據不同的操作類型,調用HandlePropertySet做具體的操作
HandlePropertySet是最終的處理函數,以"ctl"開頭的key就做一些Service的Start,Stop,Restart操作,其他的就是調用property_set進行屬性設置,不管是前者還是後者,都要進行SELinux安全性檢查,只有該進程有操作權限才能執行相應操作
static void handle_property_set_fd() {
static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */
// 等待客戶端連接
int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC);
if (s == -1) {
return;
}
ucred cr;
socklen_t cr_size = sizeof(cr);
// 獲取連接到此socket的進程的憑據
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
close(s);
PLOG(ERROR) << "sys_prop: unable to get SO_PEERCRED";
return;
}
// 建立socket連接
SocketConnection socket(s, cr);
uint32_t timeout_ms = kDefaultSocketTimeout;
uint32_t cmd = 0;
// 讀取socket中的操作信息
if (!socket.RecvUint32(&cmd, &timeout_ms)) {
PLOG(ERROR) << "sys_prop: error while reading command from the socket";
socket.SendUint32(PROP_ERROR_READ_CMD);
return;
}
// 根據操作信息,執行對應處理,兩者區別一個是以char形式讀取,一個以String形式讀取
switch (cmd) {
case PROP_MSG_SETPROP: {
char prop_name[PROP_NAME_MAX];
char prop_value[PROP_VALUE_MAX];
if (!socket.RecvChars(prop_name, PROP_NAME_MAX, &timeout_ms) ||
!socket.RecvChars(prop_value, PROP_VALUE_MAX, &timeout_ms)) {
PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP): error while reading name/value from the socket";
return;
}
prop_name[PROP_NAME_MAX-1] = 0;
prop_value[PROP_VALUE_MAX-1] = 0;
std::string source_context;
if (!socket.GetSourceContext(&source_context)) {
PLOG(ERROR) << "Unable to set property '" << prop_name << "': getpeercon() failed";
return;
}
const auto& cr = socket.cred();
std::string error;
uint32_t result = HandlePropertySet(prop_name, prop_value, source_context, cr, &error);
if (result != PROP_SUCCESS) {
LOG(ERROR) << "Unable to set property '" << prop_name << "' from uid:" << cr.uid
<< " gid:" << cr.gid << " pid:" << cr.pid << ": " << error;
}
break;
}
case PROP_MSG_SETPROP2: {
std::string name;
std::string value;
if (!socket.RecvString(&name, &timeout_ms) ||
!socket.RecvString(&value, &timeout_ms)) {
PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP2): error while reading name/value from the socket";
socket.SendUint32(PROP_ERROR_READ_DATA);
return;
}
std::string source_context;
if (!socket.GetSourceContext(&source_context)) {
PLOG(ERROR) << "Unable to set property '" << name << "': getpeercon() failed";
socket.SendUint32(PROP_ERROR_PERMISSION_DENIED);
return;
}
const auto& cr = socket.cred();
std::string error;
uint32_t result = HandlePropertySet(name, value, source_context, cr, &error);
if (result != PROP_SUCCESS) {
LOG(ERROR) << "Unable to set property '" << name << "' from uid:" << cr.uid
<< " gid:" << cr.gid << " pid:" << cr.pid << ": " << error;
}
socket.SendUint32(result);
break;
}
default:
LOG(ERROR) << "sys_prop: invalid command " << cmd;
socket.SendUint32(PROP_ERROR_INVALID_CMD);
break;
}
}
7.第三階段init.rc
當屬性服務建立完成後,init的自身功能基本就告一段落,接下來需要來啓動其他的進程。但是init進程如何其他其他進程呢?其他進程都是一個二進制文件,我們可以直接通過exec的命令方式來啓動,例如 ./system/bin/init second_stage,來啓動init進程的第二階段。但是Android系統有那麼多的Native進程,如果都通過傳exec在代碼中一個個的來執行進程,那無疑是一個災難性的設計。
在這個基礎上Android推出了一個init.rc的機制,即類似通過讀取配置文件的方式,來啓動不同的進程。接下來我們就來看看init.rc是如何工作的。
init.rc是一個配置文件,內部由Android初始化語言編寫(Android Init Language)編寫的腳本。
init.rc在手機的目錄:./init.rc
init.rc主要包含五種類型語句:
Action
Command
Service
Option
Import
7.1 Action
動作表示了一組命令(commands)組成.動作包括一個觸發器,決定了何時運行這個動作
Action: 通過觸發器trigger,即以on開頭的語句來決定執行相應的service的時機,具體有如下時機:
on early-init; 在初始化早期階段觸發;
on init; 在初始化階段觸發;
on late-init; 在初始化晚期階段觸發;
on boot/charger: 當系統啓動/充電時觸發;
on property:<key>=<value>: 當屬性值滿足條件時觸發;
7.2 Command
command是action的命令列表中的命令,或者是service中的選項 onrestart 的參數命令,命令將在所屬事件發生時被一個個地執行.
下面列舉常用的命令
class_start <service_class_name>: 啓動屬於同一個class的所有服務;
class_stop <service_class_name> : 停止指定類的服務
start <service_name>: 啓動指定的服務,若已啓動則跳過;
stop <service_name>: 停止正在運行的服務
setprop <name> <value>:設置屬性值
mkdir <path>:創建指定目錄
symlink <target> <sym_link>: 創建連接到<target>的<sym_link>符號鏈接;
write <path> <string>: 向文件path中寫入字符串;
exec: fork並執行,會阻塞init進程直到程序完畢;
exprot <name> <name>:設定環境變量;
loglevel <level>:設置log級別
hostname <name> : 設置主機名
import <filename> :導入一個額外的init配置文件
7.3 Service
服務Service,以 service開頭,由init進程啓動,一般運行在init的一個子進程,所以啓動service前需要判斷對應的可執行文件是否存在。
命令:service <name><pathname> [ <argument> ]* <option> <option>
參數
含義
<name>
表示此服務的名稱
<pathname>
此服務所在路徑因爲是可執行文件,所以一定有存儲路徑。
<argument>
啓動服務所帶的參數
<option>
對此服務的約束選項
init生成的子進程,定義在rc文件,其中每一個service在啓動時會通過fork方式生成子進程。
例如: service servicemanager /system/bin/servicemanager代表的是服務名爲servicemanager,服務執行的路徑爲/system/bin/servicemanager。
7.4 Options
Options是Service的可選項,與service配合使用
disabled: 不隨class自動啓動,只有根據service名才啓動;
oneshot: service退出後不再重啓;
user/group: 設置執行服務的用戶/用戶組,默認都是root;
class:設置所屬的類名,當所屬類啓動/退出時,服務也啓動/停止,默認爲default;
onrestart:當服務重啓時執行相應命令;
socket: 創建名爲/dev/socket/<name>的socket
critical: 在規定時間內該service不斷重啓,則系統會重啓並進入恢復模式
default: 意味着disabled=false,oneshot=false,critical=false。
7.5 import
用來導入其他的rc文件
命令:import <filename>
7.6 init.rc 解析過程
7.6.1 LoadBootScripts
代碼路徑:platform\system\core\init\init.cpp
說明:如果沒有特殊配置ro.boot.init_rc,則解析./init.rc
把/system/etc/init,/product/etc/init,/product_services/etc/init,/odm/etc/init,
/vendor/etc/init 這幾個路徑加入init.rc之後解析的路徑,在init.rc解析完成後,解析這些目錄裏的rc文件
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
Parser parser = CreateParser(action_manager, service_list);
std::string bootscript = GetProperty("ro.boot.init_rc", "");
if (bootscript.empty()) {
parser.ParseConfig("/init.rc");
if (!parser.ParseConfig("/system/etc/init")) {
late_import_paths.emplace_back("/system/etc/init");
}
if (!parser.ParseConfig("/product/etc/init")) {
late_import_paths.emplace_back("/product/etc/init");
}
if (!parser.ParseConfig("/product_services/etc/init")) {
late_import_paths.emplace_back("/product_services/etc/init");
}
if (!parser.ParseConfig("/odm/etc/init")) {
late_import_paths.emplace_back("/odm/etc/init");
}
if (!parser.ParseConfig("/vendor/etc/init")) {
late_import_paths.emplace_back("/vendor/etc/init");
}
} else {
parser.ParseConfig(bootscript);
}
}
Android7.0後,init.rc進行了拆分,每個服務都有自己的rc文件,他們基本上都被加載到/system/etc/init,/vendor/etc/init, /odm/etc/init等目錄,等init.rc解析完成後,會來解析這些目錄中的rc文件,用來執行相關的動作。
代碼路徑:platform\system\core\init\init.cpp
說明:創建Parser解析對象,例如service、on、import對象
Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
Parser parser;
parser.AddSectionParser(
"service", std::make_unique<ServiceParser>(&service_list, subcontexts, std::nullopt));
parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, subcontexts));
parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
return parser;
}
7.6.2 執行Action動作
按順序把相關Action加入觸發器隊列,按順序爲 early-init -> init -> late-init. 然後在循環中,執行所有觸發器隊列中Action帶Command的執行函數。
am.QueueEventTrigger("early-init");
am.QueueEventTrigger("init");
am.QueueEventTrigger("late-init");
...
while (true) {
if (!(waiting_for_prop || Service::is_exec_service_running())) {
am.ExecuteOneCommand();
}
}
7.6.2 Zygote啓動
從Android 5.0的版本開始,Android支持64位的編譯,因此zygote本身也支持32位和64位。通過屬性ro.zygote來控制不同版本的zygote進程啓動。
在init.rc的import段我們看到如下代碼:
import /init.${ro.zygote}.rc // 可以看出init.rc不再直接引入一個固定的文件,而是根據屬性ro.zygote的內容來引入不同的文件
init.rc位於/system/core/rootdir下。在這個路徑下還包括四個關於zygote的rc文件。
分別是init.zygote32.rc,init.zygote32_64.rc,init.zygote64.rc,init.zygote64_32.rc,由硬件決定調用哪個文件。
這裏拿64位處理器爲例,init.zygote64.rc的代碼如下所示:
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
class main # class是一個option,指定zygote服務的類型爲main
priority -20
user root
group root readproc reserved_disk
socket zygote stream 660 root system # socket關鍵字表示一個option,創建一個名爲dev/socket/zygote,類型爲stream,權限爲660的socket
socket usap_pool_primary stream 660 root system
onrestart write /sys/android_power/request_state wake # onrestart是一個option,說明在zygote重啓時需要執行的command
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 zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server 解析:
service zygote :init.zygote64.rc 中定義了一個zygote服務。 init進程就是通過這個service名稱來創建zygote進程
/system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server解析:
zygote這個服務,通過執行進行/system/bin/app_process64 並傳入4個參數進行運行:
參數1:-Xzygote 該參數將作爲虛擬機啓動時所需的參數
參數2:/system/bin 代表虛擬機程序所在目錄
參數3:--zygote 指明以ZygoteInit.java類中的main函數作爲虛擬機執行入口
參數4:--start-system-server 告訴Zygote進程啓動systemServer進程
8.總結
init進程第一階段做的主要工作是掛載分區,創建設備節點和一些關鍵目錄,初始化日誌輸出系統,啓用SELinux安全策略。
init進程第二階段主要工作是初始化屬性系統,解析SELinux的匹配規則,處理子進程終止信號,啓動系統屬性服務,可以說每一項都很關鍵,如果說第一階段是爲屬性系統,SELinux做準備,那麼第二階段就是真正去把這些功能落實。
init進行第三階段主要是解析init.rc 來啓動其他進程,進入無限循環,進行子進程實時監控。