【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.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 函数的执行顺序:

  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_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 文件系统并调整相关权限。
  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);
        }
    
        // 返回 PID
        return 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 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 文件来启动其他进程,并进入一个无限循环,进行子进程的实时监控。

参考

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吴同学是个程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值