本文基于 Android 14.0.0_r2 的系统启动流程分析。
一、概述
init 进程属于一个守护进程,准确的说,它是 Linux 系统中用户控制的第一个进程,它的进程号为 1,它的生命周期贯穿整个 Linux 内核运行的始终。Android 中所有其它的进程共同的鼻祖均为 init 进程。
可以通过 adb shell ps | grep init
命令来查看 init 的进程号:
wutianhao@wutianhao-Ubuntu:~$ adb shell ps | grep init
root 1 0 10858080 932 0 0 S init
二、init 进程入口
init 入口函数是 main.cpp,它把各个阶段的操作分离开来,使代码更加简洁:
/system/core/init/main.cpp
int main(int argc, char** argv) {
...
// 设置进程最高优先级 -20最高,20最低
setpriority(PRIO_PROCESS, 0, -20);
// 当 argv[0] 的内容为 ueventd 时,strcmp的值为 0,!strcmp 为 1;
// 1 表示 true,也就执行 ueventd_main;
// ueventd 主要是负责设备节点的创建、权限设定等一些列工作。
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
if (argc > 1) {
// 参数为 subcontext,初始化日志系统。
if (!strcmp(argv[1], "subcontext")) {
android::base::InitLogging(argv, &android::base::KernelLogger);
const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
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);
}
main 函数有四个参数入口:
- 参数中有 ueventd,进入 ueventd_main。
- 参数中有 subcontext,进入 InitLogging 和 SubcontextMain。
- 参数中有 selinux_setup,进入 SetupSelinux。
- 参数中有 second_stage,进入 SecondStageMain。
main 函数的执行顺序:
- ueventd_main:init 进程创建子进程 ueventd,并将创建设备节点文件的工作托付给 ueventd,ueventd 通过两种方式创建设备节点文件。
- FirstStageMain:启动第一阶段。
- SetupSelinux:加载 selinux 规则,并设置 selinux 日志,完成 SELinux 相关工作。
- SecondStageMain:启动第二阶段。
三、ueventd_main
ueventd_main 函数是 Android 系统中 ueventd
服务的主入口函数,负责处理和响应来自 Linux 内核的 uevents(设备事件)。
源码路径:/system/core/init/ueventd.cpp
-
初始化
int ueventd_main(int argc, char** argv) { umask(000); android::base::InitLogging(argv, &android::base::KernelLogger); ... }
- 首先调用 umask(000) 来设置进程创建文件时不受 umask 影响,确保新创建的文件具有指定的精确权限。
- 接着初始化日志系统以便记录相关信息。
-
SELinux 设置
int ueventd_main(int argc, char** argv) { ... SelinuxSetupKernelLogging(); SelabelInitialize(); ... }
- 调用
SelinuxSetupKernelLogging()
和SelabelInitialize()
函数来配置 SELinux 相关的日志以及标签库初始化。
- 调用
-
创建 UeventHandler 对象
int ueventd_main(int argc, char** argv) { ... std::vector<std::unique_ptr<UeventHandler>> uevent_handlers; auto ueventd_configuration = GetConfiguration(); uevent_handlers.emplace_back(std::make_unique<DeviceHandler>( std::move(ueventd_configuration.dev_permissions), std::move(ueventd_configuration.sysfs_permissions), std::move(ueventd_configuration.subsystems), android::fs_mgr::GetBootDevices(), true)); uevent_handlers.emplace_back(std::make_unique<FirmwareHandler>( std::move(ueventd_configuration.firmware_directories), std::move(ueventd_configuration.external_firmware_handlers))); if (ueventd_configuration.enable_modalias_handling) { std::vector<std::string> base_paths = {"/odm/lib/modules", "/vendor/lib/modules"}; uevent_handlers.emplace_back(std::make_unique<ModaliasHandler>(base_paths)); } ... }
- 根据获取到的配置信息,创建并存储多个不同类型的 UeventHandler 子类实例,如 DeviceHandler、FirmwareHandler 和可能的 ModaliasHandler。这些处理器分别负责特定类型的设备事件处理,如挂载设备节点、管理固件目录等。
-
初始化 UeventListener
int ueventd_main(int argc, char** argv) { ... UeventListener uevent_listener(ueventd_configuration.uevent_socket_rcvbuf_size); ... }
- 创建一个 UeventListener 对象,用于监听内核通过 uevent socket 发送的 uevents,并配置接收缓冲区大小。
-
冷启动处理
int ueventd_main(int argc, char** argv) { ... if (!android::base::GetBoolProperty(kColdBootDoneProp, false)) { ColdBoot cold_boot(uevent_listener, uevent_handlers, ueventd_configuration.enable_parallel_restorecon, ueventd_configuration.parallel_restorecon_dirs); cold_boot.Run(); } ... }
- 检查系统是否完成冷启动(android::base::GetBoolProperty(kColdBootDoneProp, false)),如果尚未完成,则执行 ColdBoot 类的 Run() 方法进行冷启动相关的设备事件处理和权限恢复。
-
冷启动完成通知
int ueventd_main(int argc, char** argv) { ... for (auto& uevent_handler : uevent_handlers) { uevent_handler->ColdbootDone(); } ... }
- 所有 UeventHandler 对象调用 ColdbootDone() 方法以表明冷启动阶段已完成。
-
信号处理
int ueventd_main(int argc, char** argv) { ... signal(SIGCHLD, SIG_IGN); while (waitpid(-1, nullptr, WNOHANG) > 0) { } ... }
- 忽略子进程结束信号 SIGCHLD,并清理任何已退出但未被收集的子进程。
-
恢复优先级
int ueventd_main(int argc, char** argv) { ... setpriority(PRIO_PROCESS, 0, 0); ... }
- 调用
setpriority(PRIO_PROCESS, 0, 0)
来恢复进程的默认优先级。
- 调用
-
主循环
int ueventd_main(int argc, char** argv) { ... uevent_listener.Poll([&uevent_handlers](const Uevent& uevent) { for (auto& uevent_handler : uevent_handlers) { uevent_handler->HandleUevent(uevent); } return ListenerAction::kContinue; }); ... }
- 进入主循环,使用 UeventListener 的 Poll() 方法监听 uevents。当接收到 uevent 时,遍历所有 UeventHandler 对象并调用它们的 HandleUevent() 方法来处理相应的事件。
总结:ueventd_main
函数在 Android 启动过程中扮演着核心角色,它负责监听和处理与硬件设备状态变化相关的事件,确保系统能够正确识别并响应设备添加、移除或属性更改等操作,从而使得设备驱动和用户空间能够有效地交互。
四、FirstStageMain
FirstStageMain 是 Android 系统启动流程中第一阶段初始化的主入口函数,它负责在系统启动早期进行一系列关键的系统设置和挂载操作。
源码路径:/system/core/init/first_stage_init.cpp
-
信号处理
int FirstStageMain(int argc, char** argv) { if (REBOOT_BOOTLOADER_ON_PANIC) { InstallRebootSignalHandlers(); } ... }
- 如果定义了
REBOOT_BOOTLOADER_ON_PANIC
,则安装重启到引导加载器的信号处理程序,在系统出现 panic 时执行。
- 如果定义了
-
时间戳记录与错误检查宏
int FirstStageMain(int argc, char** argv) { ... boot_clock::time_point start_time = boot_clock::now(); std::vector<std::pair<std::string, int>> errors; #define CHECKCALL(x) \ if ((x) != 0) errors.emplace_back(#x " failed", errno); ... }
- 记录启动时间点。
- 定义一个宏
CHECKCALL(x)
,用于调用函数x
并检查其返回值是否为0(表示成功),若非0,则将错误信息添加至错误列表中。
-
设置文件或目录的默认权限
int FirstStageMain(int argc, char** argv) { ... umask(0); ... }
- 当 umask 值为 0 时,意味着新建的文件或目录将具有最大权限,即对于文件来说是 666(rw-rw-rw-),对于目录来说是 777(rwxrwxrwx)。
-
环境清理与基本文件系统准备
int FirstStageMain(int argc, char** argv) { ... CHECKCALL(clearenv()); CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1)); CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755")); CHECKCALL(mkdir("/dev/pts", 0755)); CHECKCALL(mkdir("/dev/socket", 0755)); CHECKCALL(mkdir("/dev/dm-user", 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 CHECKCALL(chmod("/proc/cmdline", 0440)); std::string cmdline; android::base::ReadFileToString("/proc/cmdline", &cmdline); chmod("/proc/bootconfig", 0440); std::string bootconfig; android::base::ReadFileToString("/proc/bootconfig", &bootconfig); 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)); ... }
- 清除当前进程的环境变量。
- 设置 PATH 环境变量为默认值。
- 挂载临时文件系统 tmpfs 到
/dev
目录下,并创建必要的子目录如/dev/pts
、/dev/socket
和/dev/dm-user
。 - 按需挂载 proc、sysfs、selinuxfs 文件系统并调整相关权限。
-
特殊设备节点创建
int FirstStageMain(int argc, char** argv) { ... 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))); CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2))); CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3))); CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV, "mode=0755,uid=0,gid=1000")); CHECKCALL(mkdir("/mnt/vendor", 0755)); CHECKCALL(mkdir("/mnt/product", 0755)); CHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV, "mode=0755,uid=0,gid=0")); CHECKCALL(mount("tmpfs", kSecondStageRes, "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV, "mode=0755,uid=0,gid=0")) #undef CHECKCALL ... }
- 创建 kmsg、random、urandom、ptmx 和 null 等特殊设备节点。
-
日志初始化与权限控制
int FirstStageMain(int argc, char** argv) { ... SetStdioToDevNull(argv); InitKernelLogging(argv); ... }
- 将标准输入输出重定向至 /dev/null,以避免不必要的输出干扰。
- 初始化内核日志功能。
-
挂载特定临时文件系统
int FirstStageMain(int argc, char** argv) { ... LOG(INFO) << "init first stage started!"; auto old_root_dir = std::unique_ptr<DIR, decltype(&closedir)>{opendir("/"), closedir}; if (!old_root_dir) { PLOG(ERROR) << "Could not opendir(\"/\"), not freeing ramdisk"; } struct stat old_root_info; if (stat("/", &old_root_info) != 0) { PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk"; old_root_dir.reset(); } ... }
- 在
/mnt
、/mnt/vendor
和/mnt/product
下挂载临时文件系统,为后续挂载分区做准备。 - 创建
/debug_ramdisk
和第二阶段资源存储目录,并挂载临时文件系统。
- 在
-
模块加载及计时
int FirstStageMain(int argc, char** argv) { ... auto want_console = ALLOW_FIRST_STAGE_CONSOLE ? FirstStageConsole(cmdline, bootconfig) : 0; auto want_parallel = bootconfig.find("androidboot.load_modules_parallel = \"true\"") != std::string::npos; boot_clock::time_point module_start_time = boot_clock::now(); int module_count = 0; if (!LoadKernelModules(IsRecoveryMode() && !ForceNormalBoot(cmdline, bootconfig), want_console, want_parallel, module_count)) { if (want_console != FirstStageConsoleParam::DISABLED) { LOG(ERROR) << "Failed to load kernel modules, starting console"; } else { LOG(FATAL) << "Failed to load kernel modules"; } } if (module_count > 0) { auto module_elapse_time = std::chrono::duration_cast<std::chrono::milliseconds>( boot_clock::now() - module_start_time); setenv(kEnvInitModuleDurationMs, std::to_string(module_elapse_time.count()).c_str(), 1); LOG(INFO) << "Loaded " << module_count << " kernel modules took " << module_elapse_time.count() << " ms"; } ... }
- 加载内核模块,可以按照配置选择是否并行加载,并统计加载耗时。
-
创建设备节点与控制台启动
int FirstStageMain(int argc, char** argv) { ... bool created_devices = false; if (want_console == FirstStageConsoleParam::CONSOLE_ON_FAILURE) { if (!IsRecoveryMode()) { created_devices = DoCreateDevices(); if (!created_devices) { LOG(ERROR) << "Failed to create device nodes early"; } } StartConsole(cmdline); } ... }
- 根据需要创建设备节点。
- 根据配置决定是否启动控制台。
-
ramdisk 属性复制
int FirstStageMain(int argc, char** argv) { ... if (access(kBootImageRamdiskProp, F_OK) == 0) { std::string dest = GetRamdiskPropForSecondStage(); std::string dir = android::base::Dirname(dest); std::error_code ec; if (!fs::create_directories(dir, ec) && !!ec) { LOG(FATAL) << "Can't mkdir " << dir << ": " << ec.message(); } if (!fs::copy_file(kBootImageRamdiskProp, dest, ec)) { LOG(FATAL) << "Can't copy " << kBootImageRamdiskProp << " to " << dest << ": " << ec.message(); } LOG(INFO) << "Copied ramdisk prop to " << dest; } ... }
- 将 bootimage 中 ramdisk 的属性复制到指定位置以便于第二阶段使用。
-
调试模式支持
int FirstStageMain(int argc, char** argv) { ... if (access("/force_debuggable", F_OK) == 0) { constexpr const char adb_debug_prop_src[] = "/adb_debug.prop"; constexpr const char userdebug_plat_sepolicy_cil_src[] = "/userdebug_plat_sepolicy.cil"; std::error_code ec; if (access(adb_debug_prop_src, F_OK) == 0 && !fs::copy_file(adb_debug_prop_src, kDebugRamdiskProp, ec)) { LOG(WARNING) << "Can't copy " << adb_debug_prop_src << " to " << kDebugRamdiskProp << ": " << ec.message(); } if (access(userdebug_plat_sepolicy_cil_src, F_OK) == 0 && !fs::copy_file(userdebug_plat_sepolicy_cil_src, kDebugRamdiskSEPolicy, ec)) { LOG(WARNING) << "Can't copy " << userdebug_plat_sepolicy_cil_src << " to " << kDebugRamdiskSEPolicy << ": " << ec.message(); } setenv("INIT_FORCE_DEBUGGABLE", "true", 1); } ... }
- 当检测到 “/force_debuggable” 文件存在时,会启用用户debug模式相关的设置,例如允许adb root访问等。
-
切换根文件系统
int FirstStageMain(int argc, char** argv) { ... if (ForceNormalBoot(cmdline, bootconfig)) { mkdir("/first_stage_ramdisk", 0755); PrepareSwitchRoot(); if (mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr) != 0) { PLOG(FATAL) << "Could not bind mount /first_stage_ramdisk to itself"; } SwitchRoot("/first_stage_ramdisk"); } ... }
- 如果满足条件(例如非恢复模式且不强制正常启动),则执行切换根文件系统的操作,包括创建新目录、绑定挂载以及调用
SwitchRoot()
函数。
- 如果满足条件(例如非恢复模式且不强制正常启动),则执行切换根文件系统的操作,包括创建新目录、绑定挂载以及调用
-
完成第一阶段挂载
int FirstStageMain(int argc, char** argv) { ... if (!DoFirstStageMount(!created_devices)) { LOG(FATAL) << "Failed to mount required partitions early ..."; } ... }
- 执行
DoFirstStageMount()
函数来挂载启动过程中所需的必要分区。
- 执行
-
释放旧 ramdisk 资源
int FirstStageMain(int argc, char** argv) { ... 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); } ... }
- 验证旧根文件系统是否已成功切换,如果已切换,则释放旧的 ramdisk 相关资源。
-
其他系统设置
int FirstStageMain(int argc, char** argv) { ... SetInitAvbVersionInRecovery(); setenv(kEnvFirstStageStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(), 1);
- 设置 AVB 版本信息等额外操作。
-
进入 SetupSelinux
int FirstStageMain(int argc, char** argv) { ... const char* path = "/system/bin/init"; const char* args[] = {path, "selinux_setup", nullptr}; auto fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC); dup2(fd, STDOUT_FILENO); dup2(fd, STDERR_FILENO); close(fd); execv(path, const_cast<char**>(args)); PLOG(FATAL) << "execv(\"" << path << "\") failed"; return 1; }
- 通过 execv() 函数执行 /system/bin/init 进程,传入 “selinux_setup” 参数作为子进程的启动参数,开始进入 SetupSelinux。
总结:整个 FirstStageMain
函数确保了 Android 系统在初始启动阶段能够正确地设置文件系统结构、加载必需的内核模块、建立基础设备节点以及安全地切换到下一阶段的初始化流程。
五、SetupSelinux
SetupSelinux 是 Android 系统启动流程中用于设置和初始化 SELinux 环境的关键函数。
源码路径:/system/core/init/selinux.cpp
-
标准输入输出重定向与内核日志初始化
int SetupSelinux(char** argv) { SetStdioToDevNull(argv); InitKernelLogging(argv); ... }
SetStdioToDevNull(argv)
将标准输入、输出和错误重定向至/dev/null
,防止无用的日志输出。InitKernelLogging(argv)
初始化内核日志功能。
-
信号处理
int SetupSelinux(char** argv) { ... if (REBOOT_BOOTLOADER_ON_PANIC) { InstallRebootSignalHandlers(); } ... }
- 如果定义了
REBOOT_BOOTLOADER_ON_PANIC
,则安装重启到引导加载器的信号处理程序。
- 如果定义了
-
计时并挂载分区
int SetupSelinux(char** argv) { ... boot_clock::time_point start_time = boot_clock::now(); MountMissingSystemPartitions(); ... }
- 记录开始时间点,并调用
MountMissingSystemPartitions()
挂载必要的系统分区。
- 记录开始时间点,并调用
-
SELinux 内核日志设置
int SetupSelinux(char** argv) { ... SelinuxSetupKernelLogging(); ... }
- 调用
SelinuxSetupKernelLogging()
来设置SELinux相关的内核日志参数。
- 调用
-
准备和读取SELinux策略
int SetupSelinux(char** argv) { ... PrepareApexSepolicy(); std::string policy; ReadPolicy(&policy); CleanupApexSepolicy(); ... }
- 准备Apex SELinux策略文件 (
PrepareApexSepolicy
)。 - 读取SELinux策略文件的内容并将它存储在字符串变量 policy 中。
- 清理 Apex SELinux 策略文件相关资源。
- 准备Apex SELinux策略文件 (
-
管理 snapuserd 守护进程
int SetupSelinux(char** argv) { ... auto snapuserd_helper = SnapuserdSelinuxHelper::CreateIfNeeded(); if (snapuserd_helper) { snapuserd_helper->StartTransition(); } ... }
- 创建或获取一个 SnapuserdSelinuxHelper 对象来管理 snapuserd 守护进程的 SELinux 上下文转换。
- 如果需要,杀死旧的 snapuserd 进程以避免产生审计消息,并开始转换过程。
-
加载 SELinux 策略
int SetupSelinux(char** argv) { ... LoadSelinuxPolicy(policy); ... }
- 使用之前读取的 policy 字符串内容加载 SELinux 策略 (LoadSelinuxPolicy(policy))。
-
完成 snapuserd 的 SELinux 上下文转换
int SetupSelinux(char** argv) { ... if (snapuserd_helper) { snapuserd_helper->FinishTransition(); snapuserd_helper = nullptr; } ... }
- 如果存在 snapuserd_helper,完成其 SELinux 上下文转换过程,并释放该对象。
-
恢复上下文与设置强制执行模式
int SetupSelinux(char** argv) { ... if (selinux_android_restorecon("/dev/selinux/", SELINUX_ANDROID_RESTORECON_RECURSE) == -1) { PLOG(FATAL) << "restorecon failed of /dev/selinux failed"; } SelinuxSetEnforcement(); ... }
- 恢复
/dev/selinux/
目录及其子目录下的文件到正确的SELinux上下文。 - 设置 SELinux 进入强制执行模式 (
SelinuxSetEnforcement
)。
- 恢复
-
针对 init 进程进行额外的上下文恢复
int SetupSelinux(char** argv) { ... if (selinux_android_restorecon("/system/bin/init", 0) == -1) { PLOG(FATAL) << "restorecon failed of /system/bin/init failed"; } ... }
- 特别对 /system/bin/init 进行 SELinux 上下文恢复,确保其拥有正确权限以便进行后续启动操作。
-
设置环境变量
int SetupSelinux(char** argv) { ... setenv(kEnvSelinuxStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(), 1); ... }
- 设置环境变量 kEnvSelinuxStartedAt 记录 SELinux 启动的时间点。
-
执行第二阶段 init 进程
int SetupSelinux(char** argv) { ... const char* path = "/system/bin/init"; const char* args[] = {path, "second_stage", nullptr}; execv(path, const_cast<char**>(args)); PLOG(FATAL) << "execv(\"" << path << "\") failed"; return 1; }
- 准备参数数组,包含命令路径 “/system/bin/init” 和参数 “second_stage”。
- 使用 execv 系统调用执行新的 init 进程,进入系统的第二阶段初始化。
- 如果 execv 函数返回(通常表示出错),会记录致命错误并退出程序。由于在成功执行 execv 后不会返回,所以这里的 PLOG(FATAL) … 是一种异常情况处理。
总结:通过 SetupSelinux 函数的执行,Android 系统能够在启动时正确地建立和启用 SELinux 安全机制,为后续系统的运行提供安全保障。
六、SecondStageMain
SecondStageMain 函数是 Android 系统启动过程中的第二个阶段,主要负责更深层次的系统初始化工作。
源码路径:/system/core/init/init.cpp
-
信号处理
int SecondStageMain(int argc, char** argv) { if (REBOOT_BOOTLOADER_ON_PANIC) { InstallRebootSignalHandlers(); } boot_clock::time_point start_time = boot_clock::now(); trigger_shutdown = [](const std::string& command) { shutdown_state.TriggerShutdown(command); }; SetStdioToDevNull(argv); InitKernelLogging(argv); LOG(INFO) << "init second stage started!"; SelinuxSetupKernelLogging(); ... }
- 设置重启至引导加载器的信号处理程序,并忽略 SIGPIPE 信号,防止因管道破裂导致的进程崩溃。
-
资源准备与清理
int SecondStageMain(int argc, char** argv) { ... if (setenv("PATH", _PATH_DEFPATH, 1) != 0) { PLOG(FATAL) << "Could not set $PATH to '" << _PATH_DEFPATH << "' in second stage"; } { struct sigaction action = {.sa_flags = SA_RESTART}; action.sa_handler = [](int) {}; sigaction(SIGPIPE, &action, nullptr); } if (auto result = WriteFile("/proc/1/oom_score_adj", StringPrintf("%d", DEFAULT_OOM_SCORE_ADJUST)); !result.ok()) { LOG(ERROR) << "Unable to write " << DEFAULT_OOM_SCORE_ADJUST << " to /proc/1/oom_score_adj: " << result.error(); } keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1); close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000)); const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE"); bool load_debug_prop = false; if (force_debuggable_env && AvbHandle::IsDeviceUnlocked()) { load_debug_prop = "true"s == force_debuggable_env; } unsetenv("INIT_FORCE_DEBUGGABLE"); if (!load_debug_prop) { UmountDebugRamdisk(); } ... }
- 设置 PATH 变量值。
- 调整 init 进程及其子进程的内存管理器 OOM 分数调整值。
- 设置会话密钥环,保证进程间共享加密密钥的安全性。
- 标记正在启动状态,打开
/dev/.booting
文件。 - 根据解锁状态和环境变量决定是否加载调试属性,并卸载调试 ramdisk。
-
系统服务和属性初始化
int SecondStageMain(int argc, char** argv) { ... PropertyInit(); UmountSecondStageRes(); if (load_debug_prop) { UmountDebugRamdisk(); } MountExtraFilesystems(); SelabelInitialize(); SelinuxRestoreContext(); ... }
- 初始化属性服务,加载系统属性。
- 卸载第二阶段资源。
- 挂载额外的文件系统。
- 初始化 SELinux 并恢复上下文。
-
事件循环与回调设置
int SecondStageMain(int argc, char** argv) { ... Epoll epoll; if (auto result = epoll.Open(); !result.ok()) { PLOG(FATAL) << result.error(); } epoll.SetFirstCallback(ReapAnyOutstandingChildren); InstallSignalFdHandler(&epoll); InstallInitNotifier(&epoll); StartPropertyService(&property_fd); ... }
- 使用 Epoll 实现 I/O 多路复用,设置信号处理回调,处理子进程退出事件。
- 安装 Init 通知器,启动属性服务。
-
关键数据记录与设置
int SecondStageMain(int argc, char** argv) { ... RecordStageBoottimes(start_time); if (const char* avb_version = getenv("INIT_AVB_VERSION"); avb_version != nullptr) { SetProperty("ro.boot.avb_version", avb_version); } unsetenv("INIT_AVB_VERSION"); ... }
- 记录启动阶段的时间点供 bootstat 使用。
- 设置 libavb 版本属性。
-
系统特定功能
int SecondStageMain(int argc, char** argv) { ... fs_mgr_vendor_overlay_mount_all(); export_oem_lock_status(); MountHandler mount_handler(&epoll); SetUsbController(); SetKernelVersion(); const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap(); Action::set_function_map(&function_map); if (!SetupMountNamespaces()) { PLOG(FATAL) << "SetupMountNamespaces failed"; } InitializeSubcontext(); ActionManager& am = ActionManager::GetInstance(); ServiceList& sm = ServiceList::GetInstance(); LoadBootScripts(am, sm); ... }
- 负责厂商层叠挂载、导出 OEM 锁定状态、挂载处理器、设置 USB 控制器、设置内核版本等。
- 设置内置函数映射表,加载启动脚本。
-
初始化命名空间与控制组
int SecondStageMain(int argc, char** argv) { ... if (false) DumpState(); auto is_running = android::gsi::IsGsiRunning() ? "1" : "0"; SetProperty(gsi::kGsiBootedProp, is_running); auto is_installed = android::gsi::IsGsiInstalled() ? "1" : "0"; SetProperty(gsi::kGsiInstalledProp, is_installed); ... }
- 设置挂载命名空间。
- 初始化子上下文。
-
调度内置动作
int SecondStageMain(int argc, char** argv) { ... am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups"); am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict"); am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux"); am.QueueBuiltinAction(ConnectEarlyStageSnapuserdAction, "ConnectEarlyStageSnapuserd"); am.QueueEventTrigger("early-init"); am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done"); am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits"); Keychords keychords; am.QueueBuiltinAction( [&epoll, &keychords](const BuiltinArguments& args) -> Result<void> { for (const auto& svc : ServiceList::GetInstance()) { keychords.Register(svc->keycodes()); } keychords.Start(&epoll, HandleKeychord); return {}; }, "KeychordInit"); am.QueueEventTrigger("init"); ... }
- 队列化一系列内置动作,如设置 cgroups、设置 kptr_restrict 等安全选项。
- 注册并启动按键组合(Keychord)处理。
-
启动与触发事件
int SecondStageMain(int argc, char** argv) { ... 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"); ... }
- 根据不同的启动模式触发相应事件(正常启动、充电模式等)。
- 基于当前属性状态触发属性关联的事件。
-
主循环
int SecondStageMain(int argc, char** argv) { ... while (true) { const boot_clock::time_point far_future = boot_clock::time_point::max(); boot_clock::time_point next_action_time = far_future; auto shutdown_command = shutdown_state.CheckShutdown(); if (shutdown_command) { LOG(INFO) << "Got shutdown_command '" << *shutdown_command << "' Calling HandlePowerctlMessage()"; HandlePowerctlMessage(*shutdown_command); } if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) { am.ExecuteOneCommand(); if (am.HasMoreCommands()) { next_action_time = boot_clock::now(); } } if (!IsShuttingDown()) { auto next_process_action_time = HandleProcessActions(); if (next_process_action_time) { next_action_time = std::min(next_action_time, *next_process_action_time); } } std::optional<std::chrono::milliseconds> epoll_timeout; if (next_action_time != far_future) { epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>( std::max(next_action_time - boot_clock::now(), 0ns)); } auto epoll_result = epoll.Wait(epoll_timeout); if (!epoll_result.ok()) { LOG(ERROR) << epoll_result.error(); } if (!IsShuttingDown()) { HandleControlMessages(); SetUsbController(); } } ... }
- 在无限循环中,根据队列中的动作计划执行相关命令。
- 监听并处理控制消息,如电源管理命令(如关机、重启等)。
- 处理进程管理和重启操作。
- 检查并更新 USB 控制器状态。
总结:SecondStageMain
函数对 Android 系统进行全面深入的初始化,包括设置系统资源、挂载文件系统、启动关键服务、初始化安全性组件以及执行启动脚本等。整个函数通过事件循环持续监控并响应系统内部及外部事件,直至系统完全启动就绪。
七、信号处理
init 是一个守护进程,为了防止 init 的子进程成为僵尸进程(zombie process),需要 init 在子进程在结束时获取子进程的结束码,通过结束码将程序表中的子进程移除,防止成为僵尸进程的子进程占用程序表的空间(程序表的空间达到上限时,系统就不能再启动新的进程了,会引起严重的系统问题)。
子进程重启流程如下图所示:
信号处理主要工作:
- 初始化信号 signal 句柄
- 循环处理子进程
- 注册 epoll 句柄
- 处理子进程终止
注意:EPOLL 类似于 POLL,是 Linux 中用来做事件触发的,跟 EventBus 功能差不多。Linux 很长的时间都在使用 select 来做事件触发,它是通过轮询来处理的,轮询的 fd 数目越多,自然耗时越多,对于大量的描述符处理,EPOLL 更有优势。
-
InstallSignalFdHandler
InstallSignalFdHandler 用于在 Linux 系统中设置信号处理。
源码路径:/system/core/init/init.cpp
static void InstallSignalFdHandler(Epoll* epoll) { // 设置一个默认的 SIGCHLD 信号处理器,并使用 sigaction 函数应用了 SA_NOCLDSTOP 标志。这可以防止当子进程停止或继续时,signalfd 接收到 SIGCHLD 信号。 const struct sigaction act { .sa_handler = SIG_DFL, .sa_flags = SA_NOCLDSTOP }; sigaction(SIGCHLD, &act, nullptr); // 创建一个信号集,将 SIGCHLD 信号添加到该集中。 sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGCHLD); // 如果当前进程没有重启能力(可能是在容器中运行),那么它还会将 SIGTERM 信号添加到信号集中。这是因为在容器环境中,接收到 SIGTERM 信号通常会导致系统关闭。 if (!IsRebootCapable()) { sigaddset(&mask, SIGTERM); } // 使用 sigprocmask 函数阻塞这些信号。如果阻塞失败,它将记录一条致命错误日志。 if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) { PLOG(FATAL) << "failed to block signals"; } // 使用 pthread_atfork 函数注册一个 fork 处理器,该处理器在子进程中解除信号阻塞。如果注册失败,它将记录一条致命错误日志。 const int result = pthread_atfork(nullptr, nullptr, &UnblockSignals); if (result != 0) { LOG(FATAL) << "Failed to register a fork handler: " << strerror(result); } // 使用 signalfd 函数创建一个信号文件描述符,用于接收信号。如果创建失败,它将记录一条致命错误日志。 signal_fd = signalfd(-1, &mask, SFD_CLOEXEC); if (signal_fd == -1) { PLOG(FATAL) << "failed to create signalfd"; } // 使用 epoll->RegisterHandler 函数注册一个处理器,用于处理从信号文件描述符接收到的信号。如果注册失败,它将记录一条致命错误日志。 constexpr int flags = EPOLLIN | EPOLLPRI; if (auto result = epoll->RegisterHandler(signal_fd, HandleSignalFd, flags); !result.ok()) { LOG(FATAL) << result.error(); } }
InstallSignalFdHandler
函数的主要目的是设置一个系统,使得当特定的信号发生时,可以通过文件描述符来接收和处理这些信号。 -
RegisterHandler
RegisterHandler 在 Epoll 类中定义。这个函数的目的是注册一个处理器(Handler)来处理特定文件描述符(fd)的事件。这是通过 Linux 的 epoll 机制实现的,epoll 是一种 I/O 多路复用技术。
源码路径:/system/core/init/epoll.cpp
Result<void> Epoll::RegisterHandler(int fd, Handler handler, uint32_t events) { // 检查是否指定了事件(events)。如果没有指定任何事件,函数将返回一个错误。 if (!events) { return Error() << "Must specify events"; } // 尝试将文件描述符(fd)和一个包含事件和处理器的 Info 对象插入到 epoll_handlers_ 映射中。 auto [it, inserted] = epoll_handlers_.emplace( fd, Info{ .events = events, .handler = std::move(handler), }); // 如果插入失败(也就是说,给定的文件描述符已经有一个处理器),函数将返回一个错误。 if (!inserted) { return Error() << "Cannot specify two epoll handlers for a given FD"; } // 创建一个 epoll_event 结构体,该结构体包含了事件和文件描述符。 epoll_event ev = { .events = events, .data.fd = fd, }; // 使用 epoll_ctl 函数将文件描述符添加到 epoll 实例中。如果这个操作失败,它将从 epoll_handlers_ 映射中删除文件描述符,并返回一个错误。 if (epoll_ctl(epoll_fd_.get(), EPOLL_CTL_ADD, fd, &ev) == -1) { Result<void> result = ErrnoError() << "epoll_ctl failed to add fd"; epoll_handlers_.erase(fd); return result; } // 如果所有操作都成功,函数将返回一个空的 Result 对象,表示操作成功。 return {}; }
RegisterHandler
函数的主要用途是设置 epoll,使得当文件描述符上发生指定的事件时,可以调用相应的处理器来处理这些事件。 -
HandleSignalFd
HandleSignalFd 用于处理通过 signal_fd 接收到的信号。这个函数使用了 Linux 的 signalfd 机制,该机制允许通过文件描述符接收信号。
源码路径:/system/core/init/init.cpp
static void HandleSignalFd() { // 定义了一个 signalfd_siginfo 结构体,用于存储从 signal_fd 读取的信号信息。 signalfd_siginfo siginfo; // 尝试从 signal_fd 读取信号信息。如果读取失败,将记录一条错误日志并返回。 ssize_t bytes_read = TEMP_FAILURE_RETRY(read(signal_fd, &siginfo, sizeof(siginfo))); // 如果成功读取到信号信息,将检查读取的字节数是否等于 signalfd_siginfo 的大小。如果不等,将记录一条错误日志并返回。 if (bytes_read != sizeof(siginfo)) { PLOG(ERROR) << "Failed to read siginfo from signal_fd"; return; } switch (siginfo.ssi_signo) { case SIGCHLD: ReapAnyOutstandingChildren(); break; case SIGTERM: HandleSigtermSignal(siginfo); break; default: LOG(ERROR) << "signal_fd: received unexpected signal " << siginfo.ssi_signo; break; } }
HandleSignalFd
函数的主要用途是处理通过 signal_fd 接收到的信号,以便在接收到特定的信号时执行相应的操作。 -
ReapOneProcess
ReapOneProcess 用于处理子进程的结束。它使用了 Linux 的 waitid 和 waitpid 系统调用来收集子进程的退出状态,防止子进程变成僵尸进程。
源码路径:/system/core/init/sigchld_handler.cpp
void ReapAnyOutstandingChildren() { while (ReapOneProcess() != 0) { } } static pid_t ReapOneProcess() { // 定义一个 siginfo_t 结构体,用于存储从 waitid 系统调用中获取的信息。 siginfo_t siginfo = {}; // 调用 waitid 系统调用来获取一个僵尸进程的 PID,或者得知没有更多的僵尸进程需要处理。注意,这个调用并不实际收集僵尸进程的退出状态,这个操作在后面进行。 if (TEMP_FAILURE_RETRY(waitid(P_ALL, 0, &siginfo, WEXITED | WNOHANG | WNOWAIT)) != 0) { PLOG(ERROR) << "waitid failed"; return 0; } // 如果 waitid 调用失败,它将记录一条错误日志并返回 0。 const pid_t pid = siginfo.si_pid; if (pid == 0) { DCHECK_EQ(siginfo.si_signo, 0); return 0; } // 如果 waitid 调用成功,它将检查返回的 PID 是否为 0。如果是,它将确保信号编号为 0,然后返回0。如果 PID 不为 0,它将确保信号编号为SIGCHLD。 DCHECK_EQ(siginfo.si_signo, SIGCHLD); // 创建一个作用域保护器(scope guard)。这个保护器在函数返回时会自动调用 waitpid 系统调用来收集僵尸进程的退出状态。 auto reaper = make_scope_guard([pid] { TEMP_FAILURE_RETRY(waitpid(pid, nullptr, WNOHANG)); }); std::string name; std::string wait_string; Service* service = nullptr; // 尝试找到与 PID 对应的服务。如果找到,将记录服务的名称和 PID,以及服务的执行时间。如果没有找到,将记录 PID。 if (SubcontextChildReap(pid)) { name = "Subcontext"; } else { 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; } // 如果没有找到与 PID 对应的服务,将记录一条日志,并返回 PID。 if (!service) { LOG(INFO) << name << " did not have an associated service entry and will not be reaped"; return pid; } // 如果找到了服务,将调用服务的 Reap 方法来处理服务的结束。 service->Reap(siginfo); // 如果服务是临时的,将从服务列表中移除服务。 if (service->flags() & SVC_TEMPORARY) { ServiceList::GetInstance().RemoveService(*service); } // 返回 PID return pid; }
ReapOneProcess
函数的主要用途是处理子进程的结束,收集子进程的退出状态,防止子进程变成僵尸进程,并处理与子进程相关的服务。
八、属性服务
我们在开发和调试过程中看到通过 property_set
可以轻松设置系统属性,那干嘛这里还要启动一个属性服务呢?这里其实涉及到一些权限的问题,不是所有进程都可以随意修改任何的系统属性,Android 将属性的设置统一交由 init 进程管理,其他进程不能直接修改属性,而只能通知 init 进程来修改,而在这过程中,init 进程可以进行权限控制,我们来看看具体的流程是什么:
-
PropertyInit
PropertyInit 用于初始化 Android 系统的属性服务。这个服务用于管理系统的属性,这些属性是一些键值对,可以被系统的各个部分用来配置和通信。
源码路径:/system/core/init/property_service.cpp
void PropertyInit() { // 设置一个 SELinux 回调函数 PropertyAuditCallback,用于处理 SELinux 的审计事件。 selinux_callback cb; cb.func_audit = PropertyAuditCallback; selinux_set_callback(SELINUX_CB_AUDIT, cb); // 创建一个名为 /dev/__properties__ 的目录,这个目录用于存储系统属性的信息。 mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH); // 调用 CreateSerializedPropertyInfo 函数来创建序列化的属性信息。 CreateSerializedPropertyInfo(); // 调用 __system_property_area_init 函数来初始化属性区域。如果初始化失败,它将记录一条致命错误日志并退出。 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"; } // 处理内核设备树(DT)和内核命令行中的参数。如果这两种方式都提供了参数,设备树中的属性将优先于命令行中的属性。 ProcessKernelDt(); ProcessKernelCmdline(); // 处理启动配置。 ProcessBootconfig(); // 将内核变量传播到 init 使用的内部变量以及当前需要的属性。 ExportKernelBootProps(); // 加载启动默认属性。 PropertyLoadBootDefaults(); }
PropertyInit
函数的主要用途是初始化系统属性服务,以便系统的各个部分可以使用系统属性进行配置和通信。 -
StartPropertyService
StartPropertyService 函数用于启动属性服务。
源码路径:/system/core/init/property_service.cpp
void StartPropertyService(int* epoll_socket) { // 调用 InitPropertySet 函数来初始化一个名为 ro.property_service.version 的系统属性,其值为"2"。 InitPropertySet("ro.property_service.version", "2"); // 创建一个 UNIX 域套接字对,用于 property_service 和 init 之间的通信。如果套接字对的创建失败,它将记录一条致命错误日志并退出。 int sockets[2]; if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != 0) { PLOG(FATAL) << "Failed to socketpair() between property_service and init"; } // 将套接字对的一个端点赋值给 epoll_socket 和 from_init_socket,另一个端点赋值给 init_socket。 *epoll_socket = from_init_socket = sockets[0]; init_socket = sockets[1]; // 调用 StartSendingMessages 函数来开始发送消息。 StartSendingMessages(); // 创建一个名为 PROP_SERVICE_NAME 的套接字,用于处理属性设置请求。如果套接字的创建失败,它将记录一条致命错误日志并退出。 if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, false, false, 0666, 0, 0, {}); result.ok()) { property_set_fd = *result; } else { LOG(FATAL) << "start_property_service socket creation failed: " << result.error(); } // 调用 listen 函数来开始监听属性设置请求。 listen(property_set_fd, 8); // 创建一个新的线程来运行 PropertyServiceThread 函数。这个函数用于处理属性设置请求。 auto new_thread = std::thread{PropertyServiceThread}; property_service_thread.swap(new_thread); // 检查 ro.property_service.async_persist_writes 系统属性的值。 auto async_persist_writes = android::base::GetBoolProperty("ro.property_service.async_persist_writes", false); // 如果 async_persist_writes 为 true,将创建一个 PersistWriteThread 对象来异步写入持久化的属性。 if (async_persist_writes) { persist_write_thread = std::make_unique<PersistWriteThread>(); } }
StartPropertyService
函数的主要用途是启动属性服务,以便系统的各个部分可以使用系统属性进行配置和通信。 -
handle_property_set_fd
handle_property_set_fd 函数用于处理来自客户端的属性设置请求。这个函数通过接收和处理来自 UNIX 域套接字的消息来完成这个任务。
源码路径:/system/core/init/property_service.cpp
static void handle_property_set_fd() { static constexpr uint32_t kDefaultSocketTimeout = 2000; // 调用 accept4 函数来接收新的连接请求。如果接收失败,函数会直接返回。 int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC); if (s == -1) { return; } // 获取连接的对端的凭据(包括用户ID,组ID和进程ID)。 ucred cr; socklen_t cr_size = sizeof(cr); if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) { close(s); PLOG(ERROR) << "sys_prop: unable to get SO_PEERCRED"; return; } // 创建一个 SocketConnection 对象,用于处理和这个连接相关的操作。 SocketConnection socket(s, cr); uint32_t timeout_ms = kDefaultSocketTimeout; // 从套接字中读取一个 32 位的命令。如果读取失败,函数会发送一个错误码并返回。 uint32_t cmd = 0; if (!socket.RecvUint32(&cmd, &timeout_ms)) { PLOG(ERROR) << "sys_prop: error while reading command from the socket"; socket.SendUint32(PROP_ERROR_READ_CMD); return; } switch (cmd) { // 如果命令是 PROP_MSG_SETPROP,函数会从套接字中读取属性的名字和值,然后调用 HandlePropertySetNoSocket 函数来设置属性。 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; auto result = HandlePropertySetNoSocket(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; } // 如果命令是 PROP_MSG_SETPROP2,函数会从套接字中读取属性的名字和值,然后调用 HandlePropertySet 函数来设置属性。这个函数会处理异步属性设置的情况。 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; auto result = HandlePropertySet(name, value, source_context, cr, &socket, &error); if (!result) { return; } 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; } }
handle_property_set_fd
函数的主要用途是处理属性设置请求,以便系统的各个部分可以使用系统属性进行配置和通信。
九、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 在 Android 设备的目录:./init.rc
。init.rc 主要包含五种类型语句:Action
、Command
、Service
、Option
、Import
。
-
Action
Action 表示了一组命令(commands)组成.动作包括一个触发器,决定了何时运行这个 Action。Action 通过触发器(trigger),即以 on 开头的语句来决定执行相应的 service 的时机,具体有如下时机:
on early-init
:在初始化早期阶段触发on init
:在初始化阶段触发on late-init
:在初始化晚期阶段触发on boot/charger
:当系统启动/充电时触发on property:<key>=<value>
:当属性值满足条件时触发
-
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 配置文件
-
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
。 -
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。
-
Import
用来导入其他的 rc 文件。
命令:
import <filename>
-
init.rc 解析过程 - LoadBootScripts
LoadBootScripts 的主要任务是加载启动脚本。
源码路径:/system/core/init/init.cpp
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) { // 创建一个Parser对象,该对象用于解析启动脚本。 Parser parser = CreateParser(action_manager, service_list); // 尝试获取名为"ro.boot.init_rc"的属性,该属性的值应该是一个启动脚本的路径。 std::string bootscript = GetProperty("ro.boot.init_rc", ""); // 如果该属性不存在或者为空,那么它会尝试解析一系列默认的启动脚本。 if (bootscript.empty()) { parser.ParseConfig("/system/etc/init/hw/init.rc"); if (!parser.ParseConfig("/system/etc/init")) { // 如果在尝试解析时失败,那么它会将该路径添加到 late_import_paths 列表中,这意味着这些路径将在稍后再次尝试解析。 late_import_paths.emplace_back("/system/etc/init"); } parser.ParseConfig("/system_ext/etc/init"); if (!parser.ParseConfig("/vendor/etc/init")) { // 如果在尝试解析时失败,那么它会将该路径添加到 late_import_paths 列表中,这意味着这些路径将在稍后再次尝试解析。 late_import_paths.emplace_back("/vendor/etc/init"); } if (!parser.ParseConfig("/odm/etc/init")) { // 如果在尝试解析时失败,那么它会将该路径添加到 late_import_paths 列表中,这意味着这些路径将在稍后再次尝试解析。 late_import_paths.emplace_back("/odm/etc/init"); } if (!parser.ParseConfig("/product/etc/init")) { // 如果在尝试解析时失败,那么它会将该路径添加到 late_import_paths 列表中,这意味着这些路径将在稍后再次尝试解析。 late_import_paths.emplace_back("/product/etc/init"); } } else { // 如果"ro.boot.init_rc"属性存在并且不为空,那么它会尝试解析该属性指定的启动脚本。 parser.ParseConfig(bootscript); } }
总的来说,
LoadBootScripts
函数的目的是尽可能地加载和解析所有可用的启动脚本。 -
init.rc 解析过程 - CreateParser
CreateParser 的主要任务是创建并配置一个Parser对象。它接受两个参数,一个是ActionManager类型的action_manager,另一个是ServiceList类型的service_list。
源码路径:/system/core/init/init.cpp
Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) { // 创建一个 Parser 对象。 Parser parser; // 解析启动脚本中的"service"部分。 parser.AddSectionParser("service", std::make_unique<ServiceParser>( &service_list, GetSubcontext(), std::nullopt)); // 解析启动脚本中的"on"部分。 parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, GetSubcontext())); // 解析启动脚本中的"import"部分。 parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser)); // 返回配置好的 Parser 对象。 return parser; }
总的来说,这个函数的目的是创建一个能够解析启动脚本中的"service"、"on"和"import"部分的Parser对象。
-
init.rc 解析过程 - 执行 Action 动作
按顺序把相关 Action 加入触发器队列,按顺序为 early-init -> init -> late-init。然后在循环中,执行所有触发器队列中 Action 带 Command 的执行函数。
源码路径:/system/core/init/init.cpp
am.QueueEventTrigger("early-init"); am.QueueEventTrigger("init"); am.QueueEventTrigger("late-init"); ... while (true) { ... if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) { am.ExecuteOneCommand(); ... } ... } ...
-
init.rc 解析过程 - Zygote 启动
Android 支持 64 位的编译,因此 zygote 本身也支持 32 位和 64 位。通过属性 ro.zygote 来控制不同版本的 zygote 进程启动。在 init.rc 的 import 段我们看到如下代码:
import /system/etc/init/hw/init.${ro.zygote}.rc
init.rc 位于
/system/core/rootdir/
下。在这个路径下还包括三个关于 zygote 的 rc 文件。分别是init.zygote32.rc
、init.zygote64.rc
、init.zygote64_32.rc
,由硬件决定调用哪个文件。这里拿 64 位处理器为例,init.zygote64.rc 的代码如下所示:
// 定义了一个名为"zygote"的服务,它使用/system/bin/app_process64程序,并传递了一些参数来启动zygote进程。 service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote // 将此服务归类为主服务。 class main // 设置此服务的优先级为-20,这是最高优先级,意味着这个服务将优先于其他服务运行。 priority -20 // 设置此服务的用户和组为root,同时给予了读取进程信息和访问保留磁盘的权限。 user root group root readproc reserved_disk // 创建了两个名为"zygote"和"usap_pool_primary"的socket,权限为660,所有者和组都是root和system。 socket zygote stream 660 root system socket usap_pool_primary stream 660 root system // 定义了当服务重启时要执行的命令,包括执行一些命令、写入一些系统文件、重启一些服务等。 onrestart exec_background - system system -- /system/bin/vdc volume abort_fuse onrestart write /sys/power/state on # NOTE: If the wakelock name here is changed, then also # update it in SystemSuspend.cpp onrestart write /sys/power/wake_lock zygote_kwl onrestart restart audioserver onrestart restart cameraserver onrestart restart media onrestart restart media.tuner onrestart restart netd onrestart restart wificond // 设置了任务的性能配置文件。 task_profiles ProcessCapacityHigh MaxPerformance // 设置了一个名为"zygote-fatal"的目标,当zygote进程在定义的窗口时间内崩溃时,将会触发这个目标。 critical window=${zygote.critical_window.minute:-off} target=zygote-fatal
总的来说,这个脚本定义了 zygote 服务的行为和属性,包括它如何启动,它的权限,它的优先级,以及当它重启时应该执行的操作。
十、总结
init 进程启动过程分为三个阶段:
-
第一阶段:主要工作是挂载分区,创建设备节点和一些关键目录,初始化日志输出系统,并启用 SELinux 安全策略。
-
第二阶段:主要工作是初始化属性系统,解析 SELinux 的策略文件,处理子进程终止信号,启动系统属性服务。每一项都是关键的。如果说第一阶段是为属性系统和 SELinux 做准备,那么第二阶段就是真正去实现这些功能。
-
第三阶段:主要是解析 init.rc 文件来启动其他进程,并进入一个无限循环,进行子进程的实时监控。
参考
- Android 10.0系统启动之init进程-[Android取经之路]