1. 介紹
init進程是Linux系統第一個用戶進程,是Android系統應用程序的根進程,即1號進程(PID為1);Android中的init文件位於/init,代碼位於system/core/init目錄
Linux中第一個進程為init_task,也即0號進程(PID為0),init進程由init_task進程fork而來,在kernel初始化完成后init_task便化身為idle進程
更多內核初始化init_task和init進程的信息,參考<Android 8.0 : 系統啟動流程之Linux內核>
首先說明一下,筆者的代碼分析基於Android 9.0
------------------------------------------------------------------------------- | 鏡像 | 內容 | 掛載點 | 加載方式 | ------------------------------------------------------------------------------- | ramdisk.img | $(OUT)/root | / | 內核加載 | | ramdisk-recovery.img | $(OUT)/recovery/root | / | | | boot.img | $(OUT)/kernel + ramdisk.img | | | | recovery.img | ramdisk-recovery.img | | | | system.img | $(OUT)/system | /system | init加載 | | userdata.img | $(OUT)/data | /data | init加載 | -------------------------------------------------------------------------------
2. 雲竹
if (!strcmp(basename(argv[0]), "ueventd")) { return ueventd_main(argc, argv); } if (!strcmp(basename(argv[0]), "watchdogd")) { return watchdogd_main(argc, argv); } if (argc > 1 && !strcmp(argv[1], "subcontext")) { InitKernelLogging(argv); const BuiltinFunctionMap function_map; return SubcontextMain(argc, argv, &function_map); } if (REBOOT_BOOTLOADER_ON_PANIC) { InstallRebootSignalHandlers(); }
2.1 ueventd和watchdogd
在Android.mk中
LOCAL_POST_INSTALL_CMD := $(hide) mkdir -p $(TARGET_ROOT_OUT)/sbin; \ ln -sf ../init $(TARGET_ROOT_OUT)/sbin/ueventd; \ ln -sf ../init $(TARGET_ROOT_OUT)/sbin/watchdogd
這里是在/sbin/目錄下創建init的軟連接,因為ueventd和watchdogd應用的代碼也位於init目錄中,通過程序名稱來決定運行的代碼
而在system/core/rootdir/init.rc文件中
on early-init ... start ueventd ... service ueventd /sbin/ueventd class core critical seclabel u:r:ueventd:s0 shutdown critical
由此可見,ueventd會在init解析rc文件的early-init階段被執行;而watchdogd由廠商來定制是否要運行
2.2 信號處理
當編譯userdebug或者eng版本時,會在Android.mk中打開REBOOT_BOOTLOADER_ON_PANIC選項,該選項打開時會注冊init進程的特殊信號處理函數,當init收到SIGABRT、SIGBUS、SIGSEGV等異常信號時Android系統將進入bootloader模式
3. 第一階段
這里簡單介紹下Android Device Tree(DT)
// CASE 1: 默認路徑 /proc/device-tree/firmware/android // CASE 2: 內核命令行/proc/cmdline中定義 // androidboot.android_dt_dir=/sys/bus/platform/devices/ANDR0001:00/properties/android/ /sys/bus/platform/devices/ANDR0001:00/properties/android/
DT中定義了Android中初始化中需要的一些參數, 筆者該目錄內容如下
# tree /sys/bus/platform/devices/ANDR0001:00/properties/android/ . |---compatible // "android,firmware" |---fstab | |---compatible // "android,fstab" | |---product | | |---compatible // "android,product" | | |---dev // "/dev/block/pci/pci0000:00/0000:00:1c.0/by-name/product" | | |---fsmgr_flags // "wait,slotselect,avb" | | |---mnt_flags // "ro" | | |---type // "ext4" | |---vendor | | |---compatible // "android,vendor" | | |---dev // "/dev/block/pci/pci0000:00/0000:00:1c.0/by-name/vendor" | | |---fsmgr_flags // "wait,slotselect,avb" | | |---mnt_flags // "ro" | | |---type // "ext4" |---vbmeta | |---compatible // "android,vbmeta" | |---parts // "vbmeta,boot,system,vendor,tos,product"
3.1 掛載文件系統
首先掛載了如下文件系統
---------------------------------------------- | 設備 | 類型 | 掛載目錄 | ---------------------------------------------- | tmpfs | tmpfs | /dev | | devpts | devpts | /dev/pts | | proc | proc | /proc | | sysfs | sysfs | /sys | | selinuxfs | selinuxfs | /sys/fs/selinux | | tmpfs | tmpfs | /mnt | ----------------------------------------------
創建如下文件夾
/dev/socket: 用於Android套接字
創建如下字符設備文件
/dev/kmsg: 1/11 /dev/kmsg_debug: 1/11 /dev/random: 1/8 /dev/urandom: 1/9
3.2 日志初始化
InitKernelLogging(argv) // FIXME: 將[標准輸入/標准輸出/錯誤輸出]重定向到/dev/null open("/sys/fs/selinux/null", O_RDWR); dup2(fd, 0/1/2); // 設置日志輸出函數, 然后獲取環境變量ANDROID_LOG_TAGS並解析從而設置最小輸出等級 android::base::InitLogging(argv, &android::base::KernelLogger, InitAborter) // 將日志寫入/dev/kmsg android::base::KernelLogger
3.3 掛載分區
DoFirstStageMount() // 檢測DT的fstab配置是否支持, 如果支持則直接跳過掛載 android::init::is_android_dt_value_expected("fstab/compatible", "android,fstab") // 獲取Android DT(設備樹)並獲取$(DT)/fstab/compatible的值, 然后與android,fstab比較 android::init::read_android_dt_file("fstab/compatible", &dt_content) // 檢測DT的vbmeta配置, 如果支持則使用FirstStageMountVBootV2, 否則使用FirstStageMountVBootV1 android::init::FirstStageMount::Create() android::init::FirstStageMount::FirstStageMount() // 獲取$(DT)/fstab/目錄定義的分區及屬性, 筆者當前包含product和vendor fs_mgr_read_fstab_dt() read_fstab_from_dt() fs_mgr_read_fstab_file() /* * 讀取fstab * - 首先嘗試$(DT)/boot_devices * - 不成功則依次嘗試 * /odm/etc/fstab.$(platform) * /vendor/etc/fstab.$(platform) * /fstab.$(platform) */ fs_mgr_get_boot_devices() // 獲取$(DT)/vbmeta/parts的值, 當前為: vbmeta,boot,system,vendor,tos,product android::init::FirstStageMountVBootV2::FirstStageMountVBootV2() // 掛載fastb中的分區列表 android::init::FirstStageMount::DoFirstStageMount() android::init::FirstStageMount::InitDevices() android::init::FirstStageMount::GetRequiredDevices() android::init::FirstStageMount::InitRequiredDevices() // 依次掛載各分區 android::init::FirstStageMount::MountPartitions() android::init::FirstStageMount::SetUpDmVerity() fs_mgr_do_mount_one()
3.4 AVB初始化
SetInitAvbVersionInRecovery() // 如果不是Revery模式則直接返回 IsRecoveryMode() // 如果不兼容vbmeta則直接返回 IsDtVbmetaCompatible()
android::init::FirstStageMountVBootV2()
// 獲取vbmeta/parts對應的分區
read_android_dt_file("vbmeta/parts", &device_tree_vbmeta_parts_)
// 初始化設備
android::init::FirstStageMount::InitDevices() // 獲取AVB Handler FsManagerAvbHandle::Open(FirstStageMountVBootV2::by_name_symlink_map_) // 設置環境變量INIT_AVB_VERSION setenv("INIT_AVB_VERSION", avb_handle->avb_version().c_str(), 1)
AVB(Android Verified Boot),主要用於防止系統文件本身被篡改,還包含了防止系統回滾的功能
3.5 seccomp初始化
// 此處決定是否使能全局seccomp; 如果沒有使能, zygote也會使能 // 讀取/proc/cmdline內容, 如果包含androidboot.seccomp=global則調用set_global_seccomp_filter // set_global_seccomp_filter位於bionic/libc/seccomp/seccomp_policy.cpp global_seccomp()
3.6 SELinux初始化
// 將selinux日志重定向到內核日志輸出, 即/dev/kmsg SelinuxSetupKernelLogging() SelinuxInitialize() /* * 加載SELinux策略
* 如果IsSplitPolicyDevice返回true, 執行LoadSplitPolicy, 否則執行LoadMonolithicPolicy
*/ android::init::LoadPolicy() // 檢測/system/etc/selinux/plat_sepolicy.cil文件是否存在 android::init::IsSplitPolicyDevice() android::init::LoadSplitPolicy() /* * 依次查找以下預編譯SELinux文件
* "/vendor/etc/selinux/precompiled_sepolicy" * "/odm/etc/selinux/precompiled_sepolicy" */ android::init::FindPrecompiledSplitPolicy(&file) open(file, O_RDONLY | O_CLOEXEC | O_BINARY) selinux_android_load_policy_from_fd(fd, file) set_selinuxmnt("/sys/fs/selinux") mmap(NULL, , PROT_READ, MAP_PRIVATE, fd, 0) // 將策略文件寫入到 /sys/fs/selinux/load security_load_policy(,) // 從文件/sepolicy加載策略
android::init::LoadMonolithicPolicy() selinux_android_load_policy() open("/sepolicy", O_RDONLY | O_NOFOLLOW | O_CLOEXEC) selinux_android_load_policy_from_fd(fd, "/sepolicy") /* * 設置selinux狀態
* 如果內核selinux狀態與自定義selinux狀態進行比較, 最終以自定義selinux狀態為准
*/ // 獲取內核selinux狀態 security_getenforce() // 獲取Android自定義selinux狀態 android::init::IsEnforcing() // 如果未定義ALLOW_PERMISSIVE_SELINUX, 直接返回Enforcing, 否則才調用StatusFromCmdline // 讀取/proc/cmdline內容, 如果包含androidboot.selinux=permissive則返回Permissive android::init::StatusFromCmdline() // 設置當前selinux狀態, 其實是將0/1寫入/sys/fs/selinux/enforce security_setenforce() // 設置checkreqprot為0, 該值決定SELinux通過程序(1)還是通過內核(0)響應進行安全檢查
WriteFile("/sys/fs/selinux/checkreqprot", "0") // 獲取SELinux策略加載時間並設置到設置環境變量INIT_SELINUX_TOOK setenv("INIT_SELINUX_TOOK", std::to_string(t.duration().count()).c_str(), 1); // 將init文件的Context恢復成file_contexts中的初始配置
selinux_android_restorecon("/init", 0)
SEAndroid整體架構圖如下圖所示
3.7 第二階段准備
// 將環境變量INIT_SECOND_STAGE設置為1, 准備進入第二階段 setenv("INIT_SECOND_STAGE", "true", 1); // 設置環境變量INIT_STARTED_AT setenv("INIT_STARTED_AT", start_ms, 1); // 重新執行/init execv("/init", { /init, nullptr });
4. 第二階段
4.1 創建密鑰環
keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);
關於密鑰環,參考:
<Linux密鑰保留服務入門>
<Kernel Key Retention Service>
4.2 啟動准備
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
創建/dev/.booting文件,用於標識當前正在啟動中,當firmware_mounts_complete被觸發時刪除
4.3 Property服務初始化
property_init() // 創建目錄: O/RWX、G/X、O/X mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH) // 創建序列化的System Property CreateSerializedPropertyInfo() // 讀取System Property並加載到PropertyInfoEntry LoadPropertyInfoFromFile("/system/etc/selinux/plat_property_contexts", &e) LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts", &e) LoadPropertyInfoFromFile(/vendor/etc/selinux/nonplat_property_contexts", &e) // 將加載的System Property序列化 BuildTrie(e, "u:object_r:default_prop:s0", "string", &s) // 將序列化的System Property寫入文件 WriteStringToFile(s, "/dev/__properties__/property_info",,,,) // 恢復property_info文件的Context selinux_android_restorecon("/dev/__properties__/property_info", 0) // 初始化System Property內存區域 __system_property_area_init() SystemProperties::AreaInit("/dev/__properties__", false) ContextsSerialized::Initialize(true, "/dev/__properties__", false) ContextsSerialized::InitializeProperties() // 加載System Property默認路徑 android::properties::PropertyInfoAreaFile::LoadDefaultPath() // 將文件加載到內存 LoadPath("/dev/__properties__/property_info") // 初始化ContextNode ContextsSerialized::InitializeContextNodes() // 獲取ContextNode數量 android::properties::PropertyInfoAreaFile::num_contexts() // 創建num_contexts * ContextNode大小共享內核 mmap(, , PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS , -1, 0) // 創建ContextNode ContextNode(PropertyInfoAreaFile::context(i), "/dev/__properties__") // 將所有property對應的Security Context寫入文件
ContextNode::Open(true, false)
// 將property序列化並保持至/dev/__properties/properties_serial ContextsSerialized::MapSerialPropertyArea(true, false)
以下是初始化完成后/dev/__properties__目錄文件列表
# ls -al /dev/__properties__ drwx--x--x 2 root root 2360 2011-11-11 19:11 . drwxr-xr-x 18 root root 4820 2011-11-11 19:11 .. -r--r--r-- 1 root root 131072 2011-11-11 19:11 properties_serial -r--r--r-- 1 root root 29660 2011-11-11 19:11 property_info -r--r--r-- 1 root root 131072 2011-11-11 19:11 u:object_r:audio_prop:s0 -r--r--r-- 1 root root 131072 2011-11-11 19:11 u:object_r:bluetooth_prop:s0 ... -r--r--r-- 1 root root 131072 2019-10-01 08:00 u:object_r:system_prop:s0 -r--r--r-- 1 root root 131072 2011-11-11 19:11 u:object_r:system_radio_prop:s0 ... -r--r--r-- 1 root root 131072 2011-11-11 19:11 u:object_r:vendor_default_prop:s0 -r--r--r-- 1 root root 131072 2011-11-11 19:11 u:object_r:vendor_persist_prop:s0 ... -r--r--r-- 1 root root 131072 2011-11-11 19:11 u:object_r:wifi_prop:s0
4.4 處理設備樹和命令行
/* * DT中的屬性優先級高於內核命令行的屬性
*/ // 處理Android設備樹中定義的屬性 process_kernel_dt(); // 檢測是否兼容Android, 否則直接返回 is_android_dt_value_expected("compatible", "android,firmware") // 讀取/proc/cmdline, 將androidboot.name=value的鍵值對保存為ro.boot.name=value屬性 process_kernel_cmdline(); import_kernel_cmdline(false, ) import_kernel_nv(key, value) property_set("ro.boot." + key.substr(12), value)
4.5 RO屬性相關
/* * 將部分ro.boot.*屬性轉換為ro.*屬性, 前者值不存在則設置默認值
* { "ro.boot.serialno", "ro.serialno", "", }, * { "ro.boot.mode", "ro.bootmode", "unknown", }, * { "ro.boot.baseband", "ro.baseband", "unknown", }, * { "ro.boot.bootloader", "ro.bootloader", "unknown", }, * { "ro.boot.hardware", "ro.hardware", "unknown", }, * { "ro.boot.revision", "ro.revision", "0", }, */ export_kernel_boot_props() // 將第一階段設置的環境變量設置為property屬性
property_set("ro.boottime.init", getenv("INIT_STARTED_AT")); property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK")); property_set("ro.boot.avb_version", getenv("INIT_AVB_VERSION")) // 清空環境變量
unsetenv("INIT_SECOND_STAGE"); unsetenv("INIT_STARTED_AT"); unsetenv("INIT_SELINUX_TOOK"); unsetenv("INIT_AVB_VERSION");
4.6 SELinux再次初始化
// 將selinux日志重定向到內核日志輸出, 即/dev/kmsg SelinuxSetupKernelLogging(); // FIXME: SelabelInitialize(); selinux_android_file_context_handle() selinux_android_set_sehandle() // 恢復file_contexts中的初始配置 SelinuxRestoreContext(); selinux_android_restorecon("/dev", 0); selinux_android_restorecon("/dev/kmsg", 0); ...
4.7 EPOLL實例
// 創建EPOLL epoll_create1(EPOLL_CLOEXEC) /* * 創建一個相互連接的套接字對 * 接收SIGCHLD信號時往其中一個套接字寫 * 另一個套接字的讀則注冊到EPOLL中 */ sigchld_handler_init() socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) act.sa_handler = SIGCHLD_handler; |-> write(signal_write_fd, "1", 1) sigaction(SIGCHLD, &act, 0); register_epoll_handler(signal_read_fd, handle_signal); read(signal_read_fd, buf, sizeof(buf)) <-| ReapAnyOutstandingChildren() <-| /* * 添加SIGTERM信號處理函數, 並注冊到EPOLL中 */ InstallSigtermHandler() sigaddset(&mask, SIGTERM); sigterm_signal_fd = signalfd(-1, &mask, SFD_CLOEXEC); register_epoll_handler(sigterm_signal_fd, HandleSigtermSignal); read(sigterm_signal_fd, &, ) <-| HandlePowerctlMessage("shutdown,container") <-|
EPOLL的使用方法如下:
- epoll_create/epoll_create1創建一個EPOLL實例
- epoll_ctl向EPOLL實例注冊/修改/刪除監聽事件 - epoll_wait等待事件發生 - close關閉EPOLL實例
4.8 Property相關服務
// 加載系統啟動屬性 property_load_boot_defaults(); load_properties_from_file("/system/etc/prop.default", NULL) load_properties_from_file("/product/build.prop", NULL) load_properties_from_file("/odm/default.prop", NULL) load_properties_from_file("/vendor/default.prop", NULL) ReadFile() LoadProperties() // 將property鍵值對設置到系統中 HandlePropertySet(key, value, ,) // 設置persist.sys.usb.config屬性值 update_sys_usb_config() /* * OEM Lock和AVB相關, 涉及如下屬性 * ro.oem_unlock_supported * ro.boot.verifiedbootstate * ro.boot.flash.locked */ export_oem_lock_status(); // 啟動屬性系統服務 start_property_service(); // 創建socket, 用於監聽寫系統屬性的請求 CreateSocket("property_service") lisent(fd, 8) // 注冊到EPOLL中 register_epoll_handler(fd, handle_property_set_fd) HandlePropertySet(key, value)<-| // 設置sys.usb.controller屬性值 set_usb_controller();
下一篇請參考<Android init介紹(下)>