Android 10.0系統啓動之init進程-[Android取經之路]

原文鏈接: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取經之路] 系列文章:

《系統啓動篇》

Android系統架構
Android是怎麼啓動的
Android 10.0系統啓動之init進程
Android10.0系統啓動之Zygote進程
Android 10.0 系統啓動之SystemServer進程
Android 10.0 系統服務之ActivityMnagerService
Android10.0系統啓動之Launcher(桌面)啓動流程
Android10.0應用進程創建過程以及Zygote的fork流程
Android 10.0 PackageManagerService(一)工作原理及啓動流程
Android 10.0 PackageManagerService(二)權限掃描
Android 10.0 PackageManagerService(三)APK掃描
Android 10.0 PackageManagerService(四)APK安裝流程


《日誌系統篇》

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 來啓動其他進程,進入無限循環,進行子進程實時監控。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章