本文基于 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.cppint 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_STRCHECKCALL(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);}// 返回 PIDreturn 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 rootgroup root readproc reserved_disk// 创建了两个名为"zygote"和"usap_pool_primary"的socket,权限为660,所有者和组都是root和system。socket zygote stream 660 root systemsocket usap_pool_primary stream 660 root system// 定义了当服务重启时要执行的命令,包括执行一些命令、写入一些系统文件、重启一些服务等。onrestart exec_background - system system -- /system/bin/vdc volume abort_fuseonrestart write /sys/power/state on# NOTE: If the wakelock name here is changed, then also# update it in SystemSuspend.cpponrestart write /sys/power/wake_lock zygote_kwlonrestart restart audioserveronrestart restart cameraserveronrestart restart mediaonrestart restart media.tuneronrestart restart netdonrestart 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取经之路]