【Android】系统启动流程分析 —— init 进程启动过程

本文基于 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 函数的执行顺序:

  1. ueventd_main:init 进程创建子进程 ueventd,并将创建设备节点文件的工作托付给 ueventd,ueventd 通过两种方式创建设备节点文件。
  2. FirstStageMain:启动第一阶段。
  3. SetupSelinux:加载 selinux 规则,并设置 selinux 日志,完成 SELinux 相关工作。
  4. SecondStageMain:启动第二阶段。

三、ueventd_main

ueventd_main 函数是 Android 系统中 ueventd 服务的主入口函数,负责处理和响应来自 Linux 内核的 uevents(设备事件)。

源码路径:/system/core/init/ueventd.cpp

  1. 初始化

    int ueventd_main(int argc, char** argv) {umask(000);android::base::InitLogging(argv, &android::base::KernelLogger);...
    }
    
    • 首先调用 umask(000) 来设置进程创建文件时不受 umask 影响,确保新创建的文件具有指定的精确权限。
    • 接着初始化日志系统以便记录相关信息。
  2. SELinux 设置

    int ueventd_main(int argc, char** argv) {...SelinuxSetupKernelLogging();SelabelInitialize();...
    }
    
    • 调用 SelinuxSetupKernelLogging()SelabelInitialize() 函数来配置 SELinux 相关的日志以及标签库初始化。
  3. 创建 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。这些处理器分别负责特定类型的设备事件处理,如挂载设备节点、管理固件目录等。
  4. 初始化 UeventListener

    int ueventd_main(int argc, char** argv) {...UeventListener uevent_listener(ueventd_configuration.uevent_socket_rcvbuf_size);...
    }
    
    • 创建一个 UeventListener 对象,用于监听内核通过 uevent socket 发送的 uevents,并配置接收缓冲区大小。
  5. 冷启动处理

    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() 方法进行冷启动相关的设备事件处理和权限恢复。
  6. 冷启动完成通知

    int ueventd_main(int argc, char** argv) {...for (auto& uevent_handler : uevent_handlers) {uevent_handler->ColdbootDone();}...
    }
    
    • 所有 UeventHandler 对象调用 ColdbootDone() 方法以表明冷启动阶段已完成。
  7. 信号处理

    int ueventd_main(int argc, char** argv) {...signal(SIGCHLD, SIG_IGN);while (waitpid(-1, nullptr, WNOHANG) > 0) {}...
    }
    
    • 忽略子进程结束信号 SIGCHLD,并清理任何已退出但未被收集的子进程。
  8. 恢复优先级

    int ueventd_main(int argc, char** argv) {...setpriority(PRIO_PROCESS, 0, 0);...
    }
    
    • 调用 setpriority(PRIO_PROCESS, 0, 0) 来恢复进程的默认优先级。
  9. 主循环

    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

  1. 信号处理

    int FirstStageMain(int argc, char** argv) {if (REBOOT_BOOTLOADER_ON_PANIC) {InstallRebootSignalHandlers();}...
    }
    
    • 如果定义了 REBOOT_BOOTLOADER_ON_PANIC,则安装重启到引导加载器的信号处理程序,在系统出现 panic 时执行。
  2. 时间戳记录与错误检查宏

    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,则将错误信息添加至错误列表中。
  3. 设置文件或目录的默认权限

    int FirstStageMain(int argc, char** argv) {...umask(0);...
    }
    
    • 当 umask 值为 0 时,意味着新建的文件或目录将具有最大权限,即对于文件来说是 666(rw-rw-rw-),对于目录来说是 777(rwxrwxrwx)。
  4. 环境清理与基本文件系统准备

    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 文件系统并调整相关权限。
  5. 特殊设备节点创建

    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 等特殊设备节点。
  6. 日志初始化与权限控制

    int FirstStageMain(int argc, char** argv) {...SetStdioToDevNull(argv);InitKernelLogging(argv);...
    }
    
    • 将标准输入输出重定向至 /dev/null,以避免不必要的输出干扰。
    • 初始化内核日志功能。
  7. 挂载特定临时文件系统

    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 和第二阶段资源存储目录,并挂载临时文件系统。
  8. 模块加载及计时

    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";}...
    }
    
    • 加载内核模块,可以按照配置选择是否并行加载,并统计加载耗时。
  9. 创建设备节点与控制台启动

    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);}...
    }
    
    • 根据需要创建设备节点。
    • 根据配置决定是否启动控制台。
  10. 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 的属性复制到指定位置以便于第二阶段使用。
  11. 调试模式支持

    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访问等。
  12. 切换根文件系统

    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() 函数。
  13. 完成第一阶段挂载

    int FirstStageMain(int argc, char** argv) {...if (!DoFirstStageMount(!created_devices)) {LOG(FATAL) << "Failed to mount required partitions early ...";}...
    }
    
    • 执行 DoFirstStageMount() 函数来挂载启动过程中所需的必要分区。
  14. 释放旧 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 相关资源。
  15. 其他系统设置

    int FirstStageMain(int argc, char** argv) {...SetInitAvbVersionInRecovery();setenv(kEnvFirstStageStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(),1);
    
    • 设置 AVB 版本信息等额外操作。
  16. 进入 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

  1. 标准输入输出重定向与内核日志初始化

    int SetupSelinux(char** argv) {SetStdioToDevNull(argv);InitKernelLogging(argv);...
    }
    
    • SetStdioToDevNull(argv) 将标准输入、输出和错误重定向至 /dev/null,防止无用的日志输出。
    • InitKernelLogging(argv) 初始化内核日志功能。
  2. 信号处理

    int SetupSelinux(char** argv) {...if (REBOOT_BOOTLOADER_ON_PANIC) {InstallRebootSignalHandlers();}...
    }
    
    • 如果定义了 REBOOT_BOOTLOADER_ON_PANIC,则安装重启到引导加载器的信号处理程序。
  3. 计时并挂载分区

    int SetupSelinux(char** argv) {...boot_clock::time_point start_time = boot_clock::now();MountMissingSystemPartitions();...
    }
    
    • 记录开始时间点,并调用 MountMissingSystemPartitions() 挂载必要的系统分区。
  4. SELinux 内核日志设置

    int SetupSelinux(char** argv) {...SelinuxSetupKernelLogging();...
    }
    
    • 调用 SelinuxSetupKernelLogging() 来设置SELinux相关的内核日志参数。
  5. 准备和读取SELinux策略

    int SetupSelinux(char** argv) {...PrepareApexSepolicy();std::string policy;ReadPolicy(&policy);CleanupApexSepolicy();...
    }
    
    • 准备Apex SELinux策略文件 (PrepareApexSepolicy)。
    • 读取SELinux策略文件的内容并将它存储在字符串变量 policy 中。
    • 清理 Apex SELinux 策略文件相关资源。
  6. 管理 snapuserd 守护进程

    int SetupSelinux(char** argv) {...auto snapuserd_helper = SnapuserdSelinuxHelper::CreateIfNeeded();if (snapuserd_helper) {snapuserd_helper->StartTransition();}...
    }
    
    • 创建或获取一个 SnapuserdSelinuxHelper 对象来管理 snapuserd 守护进程的 SELinux 上下文转换。
    • 如果需要,杀死旧的 snapuserd 进程以避免产生审计消息,并开始转换过程。
  7. 加载 SELinux 策略

    int SetupSelinux(char** argv) {...LoadSelinuxPolicy(policy);...
    }
    
    • 使用之前读取的 policy 字符串内容加载 SELinux 策略 (LoadSelinuxPolicy(policy))。
  8. 完成 snapuserd 的 SELinux 上下文转换

    int SetupSelinux(char** argv) {...if (snapuserd_helper) {snapuserd_helper->FinishTransition();snapuserd_helper = nullptr;}...
    }
    
    • 如果存在 snapuserd_helper,完成其 SELinux 上下文转换过程,并释放该对象。
  9. 恢复上下文与设置强制执行模式

    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)。
  10. 针对 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 上下文恢复,确保其拥有正确权限以便进行后续启动操作。
  11. 设置环境变量

    int SetupSelinux(char** argv) {...setenv(kEnvSelinuxStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(), 1);...
    }
    
    • 设置环境变量 kEnvSelinuxStartedAt 记录 SELinux 启动的时间点。
  12. 执行第二阶段 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

  1. 信号处理

    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 信号,防止因管道破裂导致的进程崩溃。
  2. 资源准备与清理

    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。
  3. 系统服务和属性初始化

    int SecondStageMain(int argc, char** argv) {...PropertyInit();UmountSecondStageRes();if (load_debug_prop) {UmountDebugRamdisk();}MountExtraFilesystems();SelabelInitialize();SelinuxRestoreContext();...
    }
    
    • 初始化属性服务,加载系统属性。
    • 卸载第二阶段资源。
    • 挂载额外的文件系统。
    • 初始化 SELinux 并恢复上下文。
  4. 事件循环与回调设置

    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 通知器,启动属性服务。
  5. 关键数据记录与设置

    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 版本属性。
  6. 系统特定功能

    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 控制器、设置内核版本等。
    • 设置内置函数映射表,加载启动脚本。
  7. 初始化命名空间与控制组

    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);...
    }
    
    • 设置挂载命名空间。
    • 初始化子上下文。
  8. 调度内置动作

    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)处理。
  9. 启动与触发事件

    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");...
    }
    
    • 根据不同的启动模式触发相应事件(正常启动、充电模式等)。
    • 基于当前属性状态触发属性关联的事件。
  10. 主循环

    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 更有优势。

  1. 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 函数的主要目的是设置一个系统,使得当特定的信号发生时,可以通过文件描述符来接收和处理这些信号。

  2. 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,使得当文件描述符上发生指定的事件时,可以调用相应的处理器来处理这些事件。

  3. 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 接收到的信号,以便在接收到特定的信号时执行相应的操作。

  4. 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 进程可以进行权限控制,我们来看看具体的流程是什么:

  1. 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 函数的主要用途是初始化系统属性服务,以便系统的各个部分可以使用系统属性进行配置和通信。

  2. 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 函数的主要用途是启动属性服务,以便系统的各个部分可以使用系统属性进行配置和通信。

  3. 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 主要包含五种类型语句:ActionCommandServiceOptionImport

  1. Action

    Action 表示了一组命令(commands)组成.动作包括一个触发器,决定了何时运行这个 Action。Action 通过触发器(trigger),即以 on 开头的语句来决定执行相应的 service 的时机,具体有如下时机:

    • on early-init:在初始化早期阶段触发
    • on init:在初始化阶段触发
    • on late-init:在初始化晚期阶段触发
    • on boot/charger:当系统启动/充电时触发
    • on property:<key>=<value>:当属性值满足条件时触发
  2. Command

    Command 是 Action 的命令列表中的命令,或者是 Service 中的选项 onrestart 的参数命令,命令将在所属事件发生时被一个个地执行。

    下面列举常用的命令:

    • class_start <service_class_name>:启动属于同一个 class 的所有服务
    • class_stop <service_class_name>:停止指定类的服务
    • start <service_name>:启动指定的服务,若已启动则跳过
    • stop <service_name>:停止正在运行的服务
    • setprop <name> <value>:设置属性值
    • mkdir <path>:创建指定目录
    • symlink <target> <sym_link>:创建连接到 <target><sym_link> 符号链接
    • write <path> <string>:向文件 path 中写入字符串
    • exec:fork 并执行,会阻塞 init 进程直到程序完毕
    • exprot <name> <name>:设定环境变量
    • loglevel <level>:设置 log 级别
    • hostname <name>:设置主机名
    • import <filename>:导入一个额外的 init 配置文件
  3. Service

    服务 Service,以 service 开头,由 init 进程启动,一般运行在 init 的一个子进程,所以启动 service 前需要判断对应的可执行文件是否存在。

    命令:service <name><pathname> [ <argument> ]* <option> <option>

    参数含义
    <name>表示此服务的名称
    <pathname>此服务所在路径,因为是可执行文件,所以一定有存储路径。
    <argument>启动服务所带的参数
    <option>对此服务的约束选项

    init 生成的子进程,定义在 rc 文件,其中每一个 service 在启动时会通过 fork 方式生成子进程。

    例如:service servicemanager /system/bin/servicemanager 代表的是服务名为 servicemanager,服务执行的路径为 /system/bin/servicemanager

  4. Options

    Options 是 Service 的可选项,与 service 配合使用:

    • disabled:不随 class 自动启动,只有根据 service 名才启动。
    • oneshot:service 退出后不再重启。
    • user/group:设置执行服务的用户/用户组,默认都是 root。
    • class:设置所属的类名,当所属类启动/退出时,服务也启动/停止,默认为 default。
    • onrestart:当服务重启时执行相应命令。
    • socket:创建名为 /dev/socket/<name> 的 socket。
    • critical:在规定时间内该 service 不断重启,则系统会重启并进入恢复模式。

    default:意味着 disabled=false,oneshot=false,critical=false。

  5. Import

    用来导入其他的 rc 文件。

    命令:import <filename>

  6. 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 函数的目的是尽可能地加载和解析所有可用的启动脚本。

  7. 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对象。

  8. 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();...}...
    }...
    
  9. 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.rcinit.zygote64.rcinit.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取经之路]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/767283.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Machine Learning - Logistic Regression

目录 一、Activation Function Why introduce activation functions? There are several commonly used activation functions: 二、Sigmoid&#xff1a; 三、Logistic Regression Model&#xff1a; 四、Implementation of logistic regression&#xff1a; 五、Decis…

unity 多屏幕操作

想了解基础操作请移步&#xff1a;&#xff08;重点是大佬写的好&#xff0c;这里就不再赘述&#xff09; Unity 基础 之 使用 Display 简单的实现 多屏幕显示的效果_unity display-CSDN博客 在panel上也可以通过获取 Canvas&#xff0c;来达到切换多屏幕的操作&#xff0c; …

Pear-rec:一键开启多功能捕捉分享,告别繁琐操作!

Pear-rec&#xff1a;一键捕捉每一刻&#xff0c;让每一次分享变得简单高效 - 精选真开源&#xff0c;释放新价值。 概览 Pear-rec是一款采用先进的Electron框架构建&#xff0c;并以ReactJS为前端技术基础的跨平台桌面应用&#xff0c;专注于提供全方位的屏幕捕捉与媒体处理功…

【C++】类与对象(下篇)

在本篇博客中&#xff0c;作者将会讲解类与对象的最后一篇。 一.再谈构造函数 在类与对象&#xff08;上篇&#xff09;中&#xff0c;我们讲到了构造函数&#xff0c;其实构造函数就是给每个成员变量进行赋值&#xff01;&#xff01;&#xff01; 仅仅只是赋值而已&#xf…

阿里云2核4G服务器支持多少人在线?2C4G多少钱一年?

2核4G服务器支持多少人在线&#xff1f;阿里云服务器网账号下的2核4G服务器支持20人同时在线访问&#xff0c;然而应用不同、类型不同、程序效率不同实际并发数也不同&#xff0c;2核4G服务器的在线访问人数取决于多个变量因素。 阿里云2核4G服务器多少钱一年&#xff1f;2核4…

地宫取宝dfs

分析&#xff1a; 矩阵里的每一个位置都有标记&#xff0c;要求的问题是&#xff1a;有几种方法能完成这个规定。 那么&#xff0c;我们只需要计算从开始(1,1)到最后(n,m)的深度优先搜索中&#xff0c;有几个是满足要求的即为正确答案。 有个要求是&#xff0c;如果一个格子中…

Docker-镜像仓库

Docker ⛅Docker-Registry&#x1f320;分类&#x1f320;镜像仓库工作机制&#x1f320;常用的镜像仓库&#x1f320;镜像仓库命令☃️docker login☃️docker pull☃️docker push☃️docker search☃️docker logout &#x1f320;镜像命令[部分]☃️docker images☃️docke…

Git工具的详细使用

一、环境说明 [rootgit ~]# getenforce Disabled [rootgit ~]# systemctl status firewalld ● firewalld.service - firewalld - dynamic firewall daemonLoaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; vendor preset: enabled)Active: inactive (d…

网络安全实训Day9

写在前面 访问控制和防火墙桌面端安全检测与防御 网络安全实训-网络安全技术 网络安全概述 访问控制 定义&#xff1a;通过定义策略和规则来限制哪些流量能经过防火墙&#xff0c;哪些流量不能通过。本质是包过滤 可以匹配的元素 IP协议版本 源区域和目的区域 源IP地址和目…

【赠书活动】Python编程 从入门到实践 第3版(图灵出品)(文末送书-进行中)

编辑推荐 适读人群 &#xff1a;本书适合对Python感兴趣的所有读者阅读。 编程入门就选蟒蛇书&#xff01; 【经典】Python入门经典&#xff0c;常居Amazon等编程类图书TOP榜 【畅销】热销全球&#xff0c;以12个语种发行&#xff0c;影响超过 250 万读者 【口碑】好评如潮…

2-dubbo源码 : 源码环境搭建

好的开始是成功的一半&#xff0c;阅读源码也是一样。 很多同学在下定决心阅读一个开源框架之后&#xff0c;就一头扎进去&#xff0c;迷失在代码“迷宫”中。此时&#xff0c;有同学意识到&#xff0c;需要一边 Debug 一边看&#xff1b;然后又有一批同学在搭建源码环境的时候…

学习C++是否有必要学习Boost库?

C作为一门强大且灵活的编程语言&#xff0c;在软件开发领域有着广泛的应用。而在C的学习过程中&#xff0c;Boost库是一个经常被提及的重要资源。那么&#xff0c;对于C的学习者而言&#xff0c;是否有必要投入精力去学习Boost库呢&#xff1f;本文将就此问题展开详尽讨论。 一…

论文:Zero-Shot Entity Linking by Reading Entity Descriptions翻译笔记(阅读实体描述、实体链接)

文章目录 论文题目&#xff1a;通过阅读实体描述实现零样本实体链接摘要1 介绍2 零点实体链接2.1 审查&#xff1a; 实体链接2.2 任务定义2.3 与其他 EL 任务的关系 3 数据集构建4 实体链接模型4.1 生成候选4.2 候选排序 5 适应目标世界6 实验6.1 基线6.2 对未知实体和新世界6.…

C++例子

#include<iostream> using namespace std;//抽象类 //抽象cpu类 class CPU { public:virtual void calcuate()0; }; //抽象显卡类 class VideoCard { public:virtual void display()0; }; //抽象内存条类 class Memory { public:virtual void storage()0;};//电脑类 clas…

【计算机网络】物理层

文章目录 第二章 物理层一、 物理层的基本概念1. 物理层接口特性 二、数据通信基础1. 典型的数据通信模型2. 数据通信相关术语3. 设计数据通信系统要考虑的3个问题4. 三种通信方式5. 串行传输&并行传输6. 同步传输&异步传输7. 码元8. 数字通信系统数据传输速率的两种表…

rpc详解rpc框架

文章目录 概述rpc的优点组件工作流程&RPC的底层原理RPC的底层原理 RPC框架rpc框架优点RPC 的实现基础RPC的应用场景RPC使用了哪些关键技术rpc 调用异常一般怎么处理rpc和http的区别为什么RPC要比HTTP更快一些Dubbo和openfeign 区别远程调用RPC框架传输协议传输速度 概述 在…

MySQL:存储过程

1. 概念 MySQL中的存储过程指的是存储在数据库中的SQL语句集合。当创建好存储过程后&#xff0c;在运行时提供所需参数&#xff0c;存储过程就可以以代码指定的方式使用参数执行并返回值。 存储过程的特点包括&#xff1a; 封装与复用&#xff1a;可以把某一业务SQL封装在存储过…

2024年三分钟教你激活CleanMyMac v4.15.1破解版下载图文激活教程

软件介绍 CleanMyMac 系列最新X测试版本&#xff0c;CleanMyMac应该是世界上最容易使用且最强大的Mac实用系统清理工具&#xff0c;CleanMyMac X是一款集所有功能于一身的先进程序卸载清理器&#xff0c;只需两个简单步骤就可以把系统里那些乱七八糟的无用文件统统清理掉&…

使用JavaScript控制<video>视频播放

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 HTML 元素 用于在 …

每日一题 --- 移除链表元素[力扣][Go]

移除链表元素 题目&#xff1a;203. 移除链表元素 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,6,3,4,5,6], val 6 输出&#xf…