Android 11系统启动---init进程运行

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

在Android 11系统中,init进程作为用户空间的第一个进程,扮演着至关重要的角色。它不仅负责初始化系统环境,还承担着启动关键服务和后续进程的重要任务,是整个Android系统启动和运行的核心驱动力之一。

平台环境:
系统:Android 11
开发板:正点原子RK3568
系统版本:userdebug (可以通过cat /proc/cmdline查看buildvariant)


一、init进程的启动

当Android设备开机时,Linux内核首先被加载。内核启动完成后,会寻找并执行/system/bin/init文件,从而启动init进程。这一步是Android系统启动的起点,也是整个系统运行的基石

二、init进程源码分析

(一) 入口函数与参数解析

文件路径:system/core/init/main.cpp
函数原型:int main(int argc, char** argv)

int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)
    __asan_set_error_report_callback(AsanReportCallback);
#endif

    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }

    if (argc > 1) {
        if (!strcmp(argv[1], "subcontext")) {
            android::base::InitLogging(argv, &android::base::KernelLogger);
            const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();

            return SubcontextMain(argc, argv, &function_map);
        }

        if (!strcmp(argv[1], "selinux_setup")) {
            return SetupSelinux(argv);
        }

        if (!strcmp(argv[1], "second_stage")) {
            return SecondStageMain(argc, argv);
        }
    }

    return FirstStageMain(argc, argv);
}

函数结构和关键步骤

1. 设置ASan错误报告回调(可选)

#if __has_feature(address_sanitizer)
    __asan_set_error_report_callback(AsanReportCallback);
#endif
  • 使用#if __has_feature(address_sanitizer)判断是否启用了地址 sanitizer功能。地址 Sanitizer(ASan)是一种基于编译器的快速检测工具,用于检测原生代码中的内存错误。
  • 如果启用,则通过__asan_set_error_report_callback(AsanReportCallback);设置错误报告回调函数AsanReportCallback,用于在发生内存错误时进行处理和报告。

2. 判断是否以ueventd模式运行

if (!strcmp(basename(argv[0]), "ueventd")) {
    return ueventd_main(argc, argv);
}
  • 使用if (!strcmp(basename(argv[0]), “ueventd”))判断程序名是否为ueventd。
    如果是,则调用ueventd_main(argc, argv)进入ueventd模式,处理内核事件。
  • ueventd是Android系统中一个非常重要的守护进程,它负责接收内核的uevent消息,并根据这些消息来管理设备节点文件。通过ueventd,Android系统能够动态地响应硬件设备的变化,为应用程序提供访问硬件设备的接口

3. 处理命令行参数

 if (argc > 1) {
     if (!strcmp(argv[1], "subcontext")) {
         android::base::InitLogging(argv, &android::base::KernelLogger);
         const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();

         return SubcontextMain(argc, argv, &function_map);
     }

     if (!strcmp(argv[1], "selinux_setup")) {
         return SetupSelinux(argv);
     }

     if (!strcmp(argv[1], "second_stage")) {
         return SecondStageMain(argc, argv);
     }
 }
  • 检查argc是否大于1,即是否存在命令行参数。
  • 根据不同的参数值,执行不同的操作:
    • 子上下文模式
      • 如果参数为subcontext,则初始化日志记录,使用android::base::InitLogging(argv, &android::base::KernelLogger);设置日志记录到内核日志。
      • 获取内置函数映射const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
      • 调用SubcontextMain(argc, argv, &function_map),进入子上下文模式,处理特定的子上下文功能。
    • SELinux设置模式
      • 如果参数为selinux_setup,则调用SetupSelinux(argv),进行SELinux相关的设置,加载SELinux策略等。
    • 第二阶段初始化模式
      • 如果参数为second_stage,则调用SecondStageMain(argc, argv),进入第二阶段初始化,执行一些在第一阶段之后需要进行的初始化操作。

4. 第一阶段初始化(默认)

return FirstStageMain(argc, argv);
  • 如果以上条件都不满足,则调用FirstStageMain(argc, argv);进行第一阶段的初始化,这是init进程的主要初始化流程。

小结

init进程的入口函数是main(),位于main.cpp文件中。在main()函数中,解析传入的参数。根据不同的参数,init进程会进入不同的执行阶段:

  • “ueventd”:调用ueventd_main()方法,用于设备事件处理。
  • “subcontext”:初始化日志记录,调用SubcontextMain()方法,进入子上下文模式,处理特定的子上下文功能
  • “selinux_setup”:调用SetupSelinux()方法,进行SELinux的初始化。
  • “second_stage”:调用SecondStageMain()方法,进入init进程的第二初始化阶段。
  • 无参数:调用FirstStageMain()方法,进入init进程的第一初始化阶段。

在Android 11系统启动过程中,从内核启动,到执行init进程,并没有带参数,所以init进程会进入无参数模式的执行阶段,即从FirstStageMain()开始执行

(二) 第一阶段初始化操作

简述

FirstStageMain 是 Android 系统启动过程中的一个关键函数,属于 init 进程的第一阶段初始化流程。它的主要任务是为系统的进一步初始化和启动做好准备工作,包括设置运行环境、挂载必要的文件系统、创建设备节点等。

源码展示

文件路径:system/core/init/main.cpp、system/core/init/first_stage_init.cpp
函数原型:int FirstStageMain(int argc, char** argv);

int FirstStageMain(int argc, char** argv) {
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }

    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);

    // Clear the umask.
    umask(0);

    CHECKCALL(clearenv());
    CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1)); // _PATH_DEFPATH定义在bionic/libc/include/paths.h
    // Get the basic filesystem setup we need put together in the initramdisk
    // on / and then we'll let the rc file figure out the rest.
    CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
    CHECKCALL(mkdir("/dev/pts", 0755));
    CHECKCALL(mkdir("/dev/socket", 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
    // Don't expose the raw commandline to unprivileged processes.
    CHECKCALL(chmod("/proc/cmdline", 0440));
    std::string cmdline;
    android::base::ReadFileToString("/proc/cmdline", &cmdline);
    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));

    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)));

    // This is needed for log wrapper, which gets called before ueventd runs.
    CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));
    CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));

    // These below mounts are done in first stage init so that first stage mount can mount
    // subdirectories of /mnt/{vendor,product}/.  Other mounts, not required by first stage mount,
    // should be done in rc files.
    // Mount staging areas for devices managed by vold
    // See storage config details at http://source.android.com/devices/storage/
    CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=1000"));
    // /mnt/vendor is used to mount vendor-specific partitions that can not be
    // part of the vendor partition, e.g. because they are mounted read-write.
    CHECKCALL(mkdir("/mnt/vendor", 0755));
    // /mnt/product is used to mount product-specific partitions that can not be
    // part of the product partition, e.g. because they are mounted read-write.
    CHECKCALL(mkdir("/mnt/product", 0755));

    // /debug_ramdisk is used to preserve additional files from the debug ramdisk
    CHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=0"));
#undef CHECKCALL

    SetStdioToDevNull(argv);
    // Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
    // talk to the outside world...
    InitKernelLogging(argv);

    if (!errors.empty()) {
        for (const auto& [error_string, error_errno] : errors) {
            LOG(ERROR) << error_string << " " << strerror(error_errno);
        }
        LOG(FATAL) << "Init encountered errors starting first stage, aborting";
    }

    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();
    }

    auto want_console = ALLOW_FIRST_STAGE_CONSOLE ? FirstStageConsole(cmdline) : 0;

    if (!LoadKernelModules(IsRecoveryMode() && !ForceNormalBoot(cmdline), want_console)) {
        if (want_console != FirstStageConsoleParam::DISABLED) {
            LOG(ERROR) << "Failed to load kernel modules, starting console";
        } else {
            LOG(FATAL) << "Failed to load kernel modules";
        }
    }

    if (want_console == FirstStageConsoleParam::CONSOLE_ON_FAILURE) {
        StartConsole();
    }

    if (ForceNormalBoot(cmdline)) {
        mkdir("/first_stage_ramdisk", 0755);
        // SwitchRoot() must be called with a mount point as the target, so we bind mount the
        // target directory to itself here.
        if (mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr) != 0) {
            LOG(FATAL) << "Could not bind mount /first_stage_ramdisk to itself";
        }
        SwitchRoot("/first_stage_ramdisk");
    }

    // If this file is present, the second-stage init will use a userdebug sepolicy
    // and load adb_debug.prop to allow adb root, if the device is unlocked.
    if (access("/force_debuggable", F_OK) == 0) {
        std::error_code ec;  // to invoke the overloaded copy_file() that won't throw.
        if (!fs::copy_file("/adb_debug.prop", kDebugRamdiskProp, ec) ||
            !fs::copy_file("/userdebug_plat_sepolicy.cil", kDebugRamdiskSEPolicy, ec)) {
            LOG(ERROR) << "Failed to setup debug ramdisk";
        } else {
            // setenv for second-stage init to read above kDebugRamdisk* files.
            setenv("INIT_FORCE_DEBUGGABLE", "true", 1);
        }
    }

    if (!DoFirstStageMount()) {
        LOG(FATAL) << "Failed to mount required partitions early ...";
    }

    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);
    }

    SetInitAvbVersionInRecovery();

    setenv(kEnvFirstStageStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(),
           1);

    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));

    // execv() only returns if an error happened, in which case we
    // panic and never fall through this conditional.
    PLOG(FATAL) << "execv(\"" << path << "\") failed";

    return 1;
}

函数结构和关键步骤

1. 设置信号处理

 if (REBOOT_BOOTLOADER_ON_PANIC) {
     InstallRebootSignalHandlers();
 }
  • 如果 REBOOT_BOOTLOADER_ON_PANIC 宏被定义,则调用 InstallRebootSignalHandlers() 安装重启信号处理函数,以便在系统出现严重错误时能够安全地重启 bootloader。
  • REBOOT_BOOTLOADER_ON_PANIC 宏定义在当前目录的Android.mk,只有工程和调试版本,这个宏才会打开;
  • 这里实际情况:开发板烧录的是userdebug版本,所以REBOOT_BOOTLOADER_ON_PANIC 宏已打开

2. 记录启动时间

boot_clock::time_point start_time = boot_clock::now();
  • 使用 boot_clock::now() 获取当前时间,并记录为启动时间 start_time,用于后续的性能分析和日志记录。

3. 清除环境变量和设置 PATH

 CHECKCALL(clearenv());
 CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
  • 调用 clearenv() 清除当前进程的环境变量,以确保后续操作在一个干净的环境中进行。
  • 使用 setenv(“PATH”, _PATH_DEFPATH, 1) 设置 PATH 环境变量,指定系统默认的可执行文件搜索路径。_PATH_DEFPATH定义在bionic/libc/include/paths.h
#define _PATH_DEFPATH "/product/bin:/apex/com.android.runtime/bin:/apex/com.android.art/bin:/system_ext/bin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbin"

4. 挂载必要的文件系统、创建设备节点和必要的文件目录

   // Get the basic filesystem setup we need put together in the initramdisk
    // on / and then we'll let the rc file figure out the rest.
    // 1. 挂载必要的文件系统
    CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
    CHECKCALL(mkdir("/dev/pts", 0755));
    CHECKCALL(mkdir("/dev/socket", 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
    // Don't expose the raw commandline to unprivileged processes.
    CHECKCALL(chmod("/proc/cmdline", 0440));
    std::string cmdline;
    android::base::ReadFileToString("/proc/cmdline", &cmdline);
    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));

	// 2. 创建设备节点
    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)));

    // This is needed for log wrapper, which gets called before ueventd runs.
    CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));
    CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));

    // These below mounts are done in first stage init so that first stage mount can mount
    // subdirectories of /mnt/{vendor,product}/.  Other mounts, not required by first stage mount,
    // should be done in rc files.
    // Mount staging areas for devices managed by vold
    // See storage config details at http://source.android.com/devices/storage/
    // 3. 挂载和创建其他必要的目录
    CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=1000"));
    // /mnt/vendor is used to mount vendor-specific partitions that can not be
    // part of the vendor partition, e.g. because they are mounted read-write.
    CHECKCALL(mkdir("/mnt/vendor", 0755));
    // /mnt/product is used to mount product-specific partitions that can not be
    // part of the product partition, e.g. because they are mounted read-write.
    CHECKCALL(mkdir("/mnt/product", 0755));

    // /debug_ramdisk is used to preserve additional files from the debug ramdisk
    CHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=0"));
#undef CHECKCALL
  • 挂载必要的文件系统

    • 挂载 tmpfs 到 /dev,并设置权限为 0755,为设备节点的创建提供临时文件系统。
    • 创建 /dev/pts 和 /dev/socket 目录,并挂载 devpts 到 /dev/pts,用于伪终端和 socket 通信。
    • 挂载 proc 文件系统到 /proc,并设置隐藏进程 ID 的选项,以增强系统安全性。
    • 调整 /proc/cmdline 的权限,限制未授权访问。
    • 挂载 sysfs 到 /sys,用于访问内核的 sysfs 文件系统。
    • 挂载 selinuxfs 到 /sys/fs/selinux,用于 SELinux 的相关操作。
  • 创建设备节点

    • 使用 mknod 创建 /dev/kmsg、/dev/random、/dev/urandom、/dev/ptmx、/dev/null 等设备节点,为系统提供基本的设备访问接口。
      • /dev/kmsg:Linux 系统中的一个特殊字符设备文件,用于读取内核日志消息,相关命令有cat /dev/kmsgdemsg
      • /dev/random 和 /dev/urandom:Linux 系统中的两个设备文件,用于生成随机数。
      • /dev/ptmx:伪终端主设备文件(pseudo-terminal master),用于创建伪终端(pseudo-terminal)。伪终端是一种用于在不同进程之间进行双向通信的机制,常用于远程登录(如 SSH)、终端模拟器等场景。
      • /dev/null:一个特殊的设备文件,被称为“黑洞”或“空设备”。它用于丢弃写入其中的所有数据,同时读取时会立即返回 EOF(文件结束符)。
  • 挂载和创建其他必要的目录

    • 挂载 tmpfs 到 /mnt、/debug_ramdisk 等目录,为后续的文件系统挂载和调试操作提供临时空间。
    • 创建/mnt/vendor和/mnt/product,这两个目录被用于挂载供应商和产品相关的分区

5. 设置标准输入输出到 /dev/null

 SetStdioToDevNull(argv);
  • 将标准输入、输出、错误重定向到 /dev/null,避免不必要的输出干扰,确保系统的输出更加干净。

6. 初始化内核日志记录

InitKernelLogging(argv);
  • 设置在发生致命错误时系统的重启目标。初始化日志记录功能,配置日志的输出方式和格式。

7. 打开根目录并获取其信息

    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();
    }
  • 打开根目录 /,获取其文件系统信息,并存储在 old_root_info 结构体中。为了后续的根文件系统切换做准备。

8. 处理控制台和内核模块加载

auto want_console = ALLOW_FIRST_STAGE_CONSOLE ? FirstStageConsole(cmdline) : 0;

if (!LoadKernelModules(IsRecoveryMode() && !ForceNormalBoot(cmdline), want_console)) {
    if (want_console != FirstStageConsoleParam::DISABLED) {
        LOG(ERROR) << "Failed to load kernel modules, starting console";
    } else {
        LOG(FATAL) << "Failed to load kernel modules";
    }
}

if (want_console == FirstStageConsoleParam::CONSOLE_ON_FAILURE) {
    StartConsole();
}
  • 根据命令行参数(参数中是否含有“androidboot.first_stage_console=”)和系统配置(ALLOW_FIRST_STAGE_CONSOLE宏是否打开),决定是否启用第一阶段控制台。开发板系统实际情况:参数中不含有“androidboot.first_stage_console=”,ALLOW_FIRST_STAGE_CONSOLE宏已打开,所以并不能启动第一阶段控制台,即want_console = 0。命令行参数如下图
    在这里插入图片描述
  • 调用 LoadKernelModules 函数加载必要的内核模块,如果加载失败且启用了控制台,则启动控制台进行调试。内核模块是内核功能的扩展,通过加载内核模块,可以为系统添加额外的功能和支持。在启动阶段加载必要的内核模块,可以确保系统具备基本的运行能力。内核模块存放目录一般在/lib/modules目录。开发板系统实际情况:并不存在/lib/modules目录,所以跳过加载模块

9. 切换根文件系统

    if (ForceNormalBoot(cmdline)) {
        mkdir("/first_stage_ramdisk", 0755);
        // SwitchRoot() must be called with a mount point as the target, so we bind mount the
        // target directory to itself here.
        if (mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr) != 0) {
            LOG(FATAL) << "Could not bind mount /first_stage_ramdisk to itself";
        }
        SwitchRoot("/first_stage_ramdisk");
    }
  • 如果强制正常启动,则创建 /first_stage_ramdisk 目录,并将其绑定挂载到自身,然后调用 SwitchRoot 切换根文件系统到该目录。ForceNormalBoot(cmdline)通过查找命令行中androidboot.force_normal_boot=1来确定是否强制正常启动。开发板系统实际情况:命令行中不存在androidboot.force_normal_boot,所以跳过

10. 设置调试相关的环境

    if (access("/force_debuggable", F_OK) == 0) {
        std::error_code ec;  // to invoke the overloaded copy_file() that won't throw.
        if (!fs::copy_file("/adb_debug.prop", kDebugRamdiskProp, ec) ||
            !fs::copy_file("/userdebug_plat_sepolicy.cil", kDebugRamdiskSEPolicy, ec)) {
            LOG(ERROR) << "Failed to setup debug ramdisk";
        } else {
            // setenv for second-stage init to read above kDebugRamdisk* files.
            setenv("INIT_FORCE_DEBUGGABLE", "true", 1);
        }
    }
  • 如果存在 /force_debuggable 文件,则复制相关的调试配置文件,并设置环境变量 INIT_FORCE_DEBUGGABLE,以便第二阶段的 init 进程能够使用调试相关的策略和配置。开发板系统实际情况:不存在/force_debuggable 文件,所以跳过

11. 执行第一阶段挂载操作

    // DoFirstStageMount函数在system/core/init/first_stage_mount.cpp
    if (!DoFirstStageMount()) {
        LOG(FATAL) << "Failed to mount required partitions early ...";
    }
  • 调用 DoFirstStageMount 进行第一阶段的挂载操作,如果挂载失败,则记录错误并终止进程。第一阶段的挂载操作是为了挂载系统启动所需的基本分区和文件系统,如 /system、/vendor 等。如果这些挂载操作失败,系统将无法正常启动,因此需要在第一阶段就完成这些关键的挂载操作。
  • 此函数大概流程:先从设备树中寻找挂载文件系统的文件系统表fstab,没找到就通过尝试不同的路径组合来找到系统存在的fstab文件;然后初始化必要的设备;挂载文件系统表fstab中的分区
  • 开发板系统实际情况:找到了/vendor/etc/fstab.rk30board文件系统表,内容如下
# Android fstab file.
#<src>                                          <mnt_point>         <type>    <mnt_flags and options>                       <fs_mgr_flags>
# The filesystem that contains the filesystem checker binary (typically /system) cannot
# specify MF_CHECK, and must come before any filesystems that do specify MF_CHECK
system  /system   ext4 ro,barrier=1 wait,logical,first_stage_mount
vendor  /vendor   ext4 ro,barrier=1 wait,logical,first_stage_mount
odm     /odm      ext4 ro,barrier=1 wait,logical,first_stage_mount
product /product  ext4 ro,barrier=1 wait,logical,first_stage_mount
system_ext /system_ext  ext4 ro,barrier=1 wait,logical,first_stage_mount
/dev/block/by-name/metadata /metadata ext4 nodev,noatime,nosuid,discard,sync wait,formattable,first_stage_mount,check
/dev/block/by-name/misc         /misc               emmc      defaults     defaults
/dev/block/by-name/cache          /cache              ext4      noatime,nodiratime,nosuid,nodev,noauto_da_alloc,discard                wait,check

/devices/platform/*usb*   auto vfat defaults      voldmanaged=usb:auto

# For sata
/devices/platform/*.sata* auto vfat defaults voldmanaged=sata:auto

# For pcie ssd
/devices/platform/*.pcie* auto vfat defaults voldmanaged=pcie:auto

/dev/block/zram0                                none                swap      defaults                                              zramsize=50%
# For sdmmc
/devices/platform/fe2b0000.dwmmc/mmc_host*        auto  auto    defaults        voldmanaged=sdcard1:auto
#  Full disk encryption has less effect on rk3326, so default to enable this.
/dev/block/by-name/userdata /data f2fs noatime,nosuid,nodev,dirsync,discard,reserve_root=32768,resgid=1065 latemount,wait,check,fileencryption=aes-256-xts:aes-256-cts:v2+inlinecrypt_optimized,keydirectory=/metadata/vold/metadata_encryption,quota,formattable,reservedsize=128M,checkpoint=fs
# for ext4
#/dev/block/by-name/userdata    /data      ext4    discard,noatime,nosuid,nodev,dirsync,noauto_da_alloc,data=ordered,user_xattr,barrier=1    latemount,wait,formattable,check,fileencryption=software,quota,reservedsize=128M,checkpoint=block

12. 检查根文件系统变化并释放 ramdisk

    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。在切换根文件系统后,旧的 ramdisk 已经不再使用,释放它可以释放占用的内存资源,优化系统的内存使用。

13. 设置 AVB 版本信息

SetInitAvbVersionInRecovery();
  • 调用 SetInitAvbVersionInRecovery 设置 init 进程的 AVB 版本信息。AVB(Android Verified Boot)是 Android 的验证启动机制,用于确保设备启动的软件未被篡改。设置 init 进程的 AVB 版本信息可以在恢复模式下使用,帮助验证启动过程的完整性和正确性。

14. 记录第一阶段启动时间

setenv(kEnvFirstStageStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(),
       1);
  • 将第一阶段启动时间记录到环境变量 kEnvFirstStageStartedAt 中。记录启动时间有助于后续的性能分析和日志记录,方便开发人员了解系统启动过程的耗时情况,优化启动性能。

15. SELinux设置初始化

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));
  • 设置标准输出和错误输出到 /dev/kmsg,然后使用 execv 执行 /system/bin/init 程序,并传递参数 selinux_setup,进入第二阶段的selinux设置流程。

(三) SELinux设置操作

简述

SetupSelinux 函数是 Android 系统中 init 进程的一个重要组成部分,主要负责在系统启动过程中进行 SELinux 的初始化和相关设置。SELinux(Security-Enhanced Linux)是一种强制访问控制(MAC)安全机制,用于提供更细粒度的权限管理和安全策略 enforcement。

源码展示

文件路径:system/core/init/main.cpp、system/core/init/selinux.cpp
函数原型:int SetupSelinux(char** argv);

int SetupSelinux(char** argv) {
    SetStdioToDevNull(argv);
    InitKernelLogging(argv);

    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }

    boot_clock::time_point start_time = boot_clock::now();

    MountMissingSystemPartitions();

    // Set up SELinux, loading the SELinux policy.
    SelinuxSetupKernelLogging();
    SelinuxInitialize();

    // We're in the kernel domain and want to transition to the init domain.  File systems that
    // store SELabels in their xattrs, such as ext4 do not need an explicit restorecon here,
    // but other file systems do.  In particular, this is needed for ramdisks such as the
    // recovery image for A/B devices.
    if (selinux_android_restorecon("/system/bin/init", 0) == -1) {
        PLOG(FATAL) << "restorecon failed of /system/bin/init failed";
    }

    setenv(kEnvSelinuxStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(), 1);

    const char* path = "/system/bin/init";
    const char* args[] = {path, "second_stage", nullptr};
    execv(path, const_cast<char**>(args));

    // execv() only returns if an error happened, in which case we
    // panic and never return from this function.
    PLOG(FATAL) << "execv(\"" << path << "\") failed";

    return 1;
}

函数结构和关键步骤

1. 设置标准输入输出到 /dev/null

SetStdioToDevNull(argv);
  • 将标准输入、输出、错误重定向到 /dev/null。这样做可以避免不必要的输出干扰,确保系统的输出更加干净。特别是在嵌入式设备和服务器环境中,减少不必要的输出可以提高系统的效率和稳定性。

2. 初始化内核日志记录

InitKernelLogging(argv);
  • 设置在发生致命错误时系统的重启目标。初始化日志记录功能,配置日志的输出方式和格式。

3. 设置安装重启信号处理

if (REBOOT_BOOTLOADER_ON_PANIC) {
    InstallRebootSignalHandlers();
}
  • 如果定义了 REBOOT_BOOTLOADER_ON_PANIC,则安装重启信号处理函数。这样做的目的是为了在系统出现严重错误(panic)时,能够安全地重启 bootloader,避免系统陷入不可用状态,提高系统的稳定性和可靠性

4. 记录启动时间

boot_clock::time_point start_time = boot_clock::now();
  • 获取当前时间并记录为启动时间 start_time。

5. 挂载缺失的系统分区

// 这是为了让R system.img/system_ext.img在旧的vendor.img上工作,因为 system_ext.img是在R中引入的。我们在第二阶段init中挂载system_ext,因为boot.img中的第一阶段init在纯系统OTA场景中不会更新。
MountMissingSystemPartitions();
  • 通过读取系统的分区配置信息和当前已挂载的分区状态,动态地挂载那些在第一阶段可能未被挂载的系统分区。这确保了系统在各种启动场景下(如系统更新、分区配置变化等)能够正确地访问所有必要的分区,从而提高系统的兼容性和稳定性。

6. 设置 SELinux 并加载策略

   SelinuxSetupKernelLogging();
   SelinuxInitialize();
  • SelinuxSetupKernelLogging():设置 SELinux 的内核日志记录。
  • SelinuxInitialize():初始化 SELinux,加载 SELinux 策略。
    • 判断设备是不是分策略设备
      • android8之后将独立策略文件拆分为平台策略和供应商策略,即分策略;代码中通过系统是否存在/system/etc/selinux/plat_sepolicy.cil这个文件判断设备是不是分策略设备;开发板实际情况:存在/system/etc/selinux/plat_sepolicy.cil这个文件,所以是分策略
        在这里插入图片描述
    • 两种加载策略的方式
      • 临时编译
        • 根据不同的分区(如/system、/system_ext、/product、/vendor、/odm等)和策略文件的存在情况,设置secilc工具的编译参数。参数包括平台策略文件、映射文件、兼容文件以及其他分区的策略文件等。
        • 使用ForkExecveAndWaitForCompletion函数执行secilc工具,将所有CIL格式的策略文件编译成一个二进制策略文件。
      • 预编译
        • 从系统/vendor/etc/selinux/和/odm/etc/selinux/目录寻找预编译策略文件 。开发板实际情况是用的这种方式。 在这里插入图片描述
    • 调用selinux_android_load_policy_from_fd函数,将临时编译的文件或者预编译的文件映射到内存中,再调用security_load_policy函数将策略数据写入到内核的/sys/fs/selinux/load节点,从而完成策略的加载中的策略加载到内核。selinux_android_load_policy_from_fd函数位于Android源码的external/selinux/libselinux/src/android/android_platform.c文件中。security_load_policy函数位于external/selinux/libselinux/src/load_policy.c
    • 设置selinux启动模式(默认为enforcing)

7. 恢复文件的安全上下文

if (selinux_android_restorecon("/system/bin/init", 0) == -1) {
    PLOG(FATAL) << "restorecon failed of /system/bin/init failed";
}
  • 对 /system/bin/init 文件执行 restorecon 操作,恢复其安全上下文。在系统启动过程中,内核首先运行在内核空间(内核域)。当内核完成初始化后,它会启动用户空间的第一个进程,通常是/system/bin/init(init域)。这个转换过程中,需要确保/system/bin/init文件的安全上下文正确,以便SELinux策略能够正确应用。在某些文件系统(如ext4)上,由于SELabels已经存储在文件的xattrs中,因此不需要显式调用restorecon来恢复安全上下文。然而,对于其他文件系统(特别是A/B设备的恢复映像和ramdisk),由于这些文件系统可能不支持xattrs或其上的文件在生成时没有正确设置SELabels,因此需要显式调用restorecon来确保文件的安全上下文正确设置

8. 记录 SELinux 初始化时间

setenv(kEnvSelinuxStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(), 1);
  • 将 SELinux 初始化的启动时间记录到环境变量 kEnvSelinuxStartedAt 中

9. 执行第二阶段的 init 进程

const char* path = "/system/bin/init";
const char* args[] = {path, "second_stage", nullptr};
execv(path, const_cast<char**>(args));
  • 使用 execv 执行 /system/bin/init 程序,并传递参数 second_stage,进入第二阶段的初始化流程。

(四) 第二阶段初始化操作

简述

SecondStageMain 函数在 Android 系统的启动过程中起着至关重要的作用。它通过一系列复杂的初始化操作,如信号处理、属性初始化、SELinux 设置、服务启动等,确保系统能够顺利进入正常运行状态。

源码展示

文件路径:system/core/init/main.cpp、system/core/init/init.cpp
函数原型:int SecondStageMain(int argc, char** argv);

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!";

    // Init should not crash because of a dependence on any other process, therefore we ignore
    // SIGPIPE and handle EPIPE at the call site directly.  Note that setting a signal to SIG_IGN
    // is inherited across exec, but custom signal handlers are not.  Since we do not want to
    // ignore SIGPIPE for child processes, we set a no-op function for the signal handler instead.
    {
        struct sigaction action = {.sa_flags = SA_RESTART};
        action.sa_handler = [](int) {};
        sigaction(SIGPIPE, &action, nullptr);
    }

    // Set init and its forked children's oom_adj.
    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();
    }

    // Set up a session keyring that all processes will have access to. It
    // will hold things like FBE encryption keys. No process should override
    // its session keyring.
    keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);

    // Indicate that booting is in progress to background fw loaders, etc.
    close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));

    // See if need to load debug props to allow adb root, when the device is unlocked.
    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");

    // Umount the debug ramdisk so property service doesn't read .prop files from there, when it
    // is not meant to.
    if (!load_debug_prop) {
        UmountDebugRamdisk();
    }

    PropertyInit();

    // Umount the debug ramdisk after property service has read the .prop files when it means to.
    if (load_debug_prop) {
        UmountDebugRamdisk();
    }

    // Mount extra filesystems required during second stage init
    MountExtraFilesystems();

    // Now set up SELinux for second stage.
    SelinuxSetupKernelLogging();
    SelabelInitialize();
    SelinuxRestoreContext();

    Epoll epoll;
    if (auto result = epoll.Open(); !result.ok()) {
        PLOG(FATAL) << result.error();
    }

    InstallSignalFdHandler(&epoll);
    InstallInitNotifier(&epoll);
    StartPropertyService(&property_fd);

    // Make the time that init stages started available for bootstat to log.
    RecordStageBoottimes(start_time);

    // Set libavb version for Framework-only OTA match in Treble build.
    if (const char* avb_version = getenv("INIT_AVB_VERSION"); avb_version != nullptr) {
        SetProperty("ro.boot.avb_version", avb_version);
    }
    unsetenv("INIT_AVB_VERSION");

    fs_mgr_vendor_overlay_mount_all();
    export_oem_lock_status();
    MountHandler mount_handler(&epoll);
    SetUsbController();

    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);

    // Turning this on and letting the INFO logging be discarded adds 0.2s to
    // Nexus 9 boot time, so it's disabled by default.
    if (false) DumpState();

    // Make the GSI status available before scripts start running.
    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);

    am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
    am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
    am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
    am.QueueEventTrigger("early-init");

    // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
    am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
    // ... so that we can start queuing up actions that require stuff from /dev.
    am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
    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");

    // Trigger all the boot actions to get us started.
    am.QueueEventTrigger("init");

    // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
    // wasn't ready immediately after wait_for_coldboot_done
    am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");

    // Don't mount filesystems or start core system services in charger mode.
    std::string bootmode = GetProperty("ro.bootmode", "");
    if (bootmode == "charger") {
        am.QueueEventTrigger("charger");
    } else {
        am.QueueEventTrigger("late-init");
    }

    // Run all property triggers based on current state of the properties.
    am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");

    while (true) {
        // By default, sleep until something happens.
        auto epoll_timeout = std::optional<std::chrono::milliseconds>{};

        auto shutdown_command = shutdown_state.CheckShutdown();
        if (shutdown_command) {
            HandlePowerctlMessage(*shutdown_command);
        }

        if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
            am.ExecuteOneCommand();
        }
        if (!IsShuttingDown()) {
            auto next_process_action_time = HandleProcessActions();

            // If there's a process that needs restarting, wake up in time for that.
            if (next_process_action_time) {
                epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(
                        *next_process_action_time - boot_clock::now());
                if (*epoll_timeout < 0ms) epoll_timeout = 0ms;
            }
        }

        if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
            // If there's more work to do, wake up again immediately.
            if (am.HasMoreCommands()) epoll_timeout = 0ms;
        }

        auto pending_functions = epoll.Wait(epoll_timeout);
        if (!pending_functions.ok()) {
            LOG(ERROR) << pending_functions.error();
        } else if (!pending_functions->empty()) {
            // We always reap children before responding to the other pending functions. This is to
            // prevent a race where other daemons see that a service has exited and ask init to
            // start it again via ctl.start before init has reaped it.
            ReapAnyOutstandingChildren();
            for (const auto& function : *pending_functions) {
                (*function)();
            }
        }
        if (!IsShuttingDown()) {
            HandleControlMessages();
            SetUsbController();
        }
    }

    return 0;
}

函数结构和关键步骤

1. 设置安装重启信号处理

if (REBOOT_BOOTLOADER_ON_PANIC) {
    InstallRebootSignalHandlers();
}
  • 如果定义了 REBOOT_BOOTLOADER_ON_PANIC,则安装重启信号处理函数。这样做的目的是为了在系统出现严重错误(panic)时,能够安全地重启 bootloader,避免系统陷入不可用状态,提高系统的稳定性和可靠性

2. 记录启动时间

boot_clock::time_point start_time = boot_clock::now();
  • 获取当前时间并记录为启动时间 start_time。

3. 设置一个关机触发器

trigger_shutdown = [](const std::string& command) { shutdown_state.TriggerShutdown(command); };
  • 设置一个关机触发器。当系统需要关机时,可以通过调用 trigger_shutdown 并传入相应的命令来触发关机操作。

4. 设置标准输入输出到 /dev/null

SetStdioToDevNull(argv);
  • 将标准输入、输出、错误重定向到 /dev/null。这样做可以避免不必要的输出干扰,确保系统的输出更加干净。特别是在嵌入式设备和服务器环境中,减少不必要的输出可以提高系统的效率和稳定性。

5. 初始化内核日志记录

InitKernelLogging(argv);
  • 设置在发生致命错误时系统的重启目标。初始化日志记录功能,配置日志的输出方式和格式。

6. SIGPIPE 信号处理

/* Init进程不应该因为依赖于任何其他进程而崩溃,因此我们忽略SIGPIPE,直接在调用站点处理EPIPE。请注意,将信号设置为SIG_IGN是跨exec继承的,但自定义信号处理程序不是。由于我们不想忽略子进程的SIGPIPE,因此我们为信号处理程序设置了一个空操作函数。*/
struct sigaction action = {.sa_flags = SA_RESTART};
action.sa_handler = [](int) {};
sigaction(SIGPIPE, &action, nullptr);
  • 忽略 SIGPIPE 信号,避免程序在发生管道错误时意外终止,提高程序的健壮性和稳定性。
    • .sa_flags = SA_RESTART 表示在处理信号时,如果系统调用被中断,会自动重新启动该系统调用。这有助于避免因信号中断而导致的程序逻辑错误。
  • 为什么要忽略 SIGPIPE 信号?
    • 进程接收到 SIGPIPE 信号后,默认处理方式是终止当前程序;所以为了不让init进程因为管道问题意外挂掉,要忽略此信号
  • 为什么不采用signal(SIGPIPE, SIG_IGN)方式而是自定义信号处理空函数的方式?
    • 采用signal(SIGPIPE, SIG_IGN)这种简单方式,如果进程通过execl 函数执行新的程序,它将会继承这个signal(SIGPIPE, SIG_IGN)方式,即忽略SIGPIPE,而自定义信号处理空函数是不会被继承的;如果我们不想忽略子进程的SIGPIPE,就采用自定义信号处理空函数方式

7. 设置 OOM 调整值

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();
}
  • 设置 init 进程及其派生子进程的 OOM(Out Of Memory)调整值。确保关键进程在内存紧张时不会被系统杀死,提高系统的稳定性。通过cat /proc/[pid号]/oom_score_adj可以查看指定进程的OOM调整值。进程1的OOM调整值为-1000,代表其完全不会被OOM杀手考虑。
  • 在Linux系统中,OOM(Out-Of-Memory)分数是一个用于决定在系统内存不足时,哪些进程应该被优先终止的指标。每个进程都有一个OOM分数,范围通常在0到1000之间。分数越高,进程在内存不足时被杀死的优先级越高。
    • OOM分数的作用
      • 当系统内存耗尽时,Linux内核的OOM杀手(OOM Killer)会根据进程的OOM分数选择一个或多个进程终止,以释放内存,确保系统能够继续运行。
    • OOM分数的调整
      • 用户或程序可以通过写入/proc/[pid]/oom_score_adj文件来调整特定进程的OOM分数。oom_score_adj的取值范围是-1000到1000,值越小,进程被OOM杀手选中的可能性越小。
        -1000:表示进程完全不会被OOM杀手考虑。
        0:表示使用内核默认的OOM分数计算。
        1000:表示进程最容易被OOM杀手选中。

8. 设置会话密钥环

keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);
  • 获取会话密钥环的 ID,并确保所有进程都能访问该密钥环。会话密钥环用于存储加密密钥等敏感信息,确保系统中各进程能够安全地访问这些信息。

9. 创建 .booting 文件

close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
  • 创建 /dev/.booting 文件,表示系统正在启动。为后台固件加载器等组件提供系统启动状态的指示。当固件加载完成,就删除此文件(这个动作在init.rc中)。
  • 解释:创建一个文件/dev/.booting,并且由于文件权限被设置为0000(所有用户都不可读、不可写、不可执行),所以这个文件只能被创建它的进程访问,其他进程无法读取或修改它。
    • 用法举例:
      • 在启动过程中,第一个程序执行时创建/dev/.booting文件。
      • 后续的程序可以通过检查/dev/.booting(access(“/dev/.booting”, F_OK) == 0)是否存在来判断系统是否正在启动。
      • 当启动过程完成时,最后一个程序删除/dev/.booting文件(rm /dev/.booting),表示系统已经启动完成。

10. 初始化属性系统

PropertyInit();
/*
void PropertyInit() {
    selinux_callback cb;
    cb.func_audit = PropertyAuditCallback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);

    mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);
    CreateSerializedPropertyInfo();
    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";
    }

    // If arguments are passed both on the command line and in DT,
    // properties set in DT always have priority over the command-line ones.
    ProcessKernelDt();
    ProcessKernelCmdline();

    // Propagate the kernel variables to internal variables
    // used by init as well as the current required properties.
    ExportKernelBootProps();

    PropertyLoadBootDefaults();
}
*/
  • 初始化属性系统,加载系统属性。系统属性在 Android 系统中广泛用于配置和管理各种组件,初始化属性系统是确保系统正常运行的关键步骤。函数流程如下:
    • 设置 SELinux 的审计回调函数,当 SELinux 需要审计属性相关操作时,会调用 PropertyAuditCallback 函数,用于生成包含属性名称和相关进程信息的审计日志条目
    • 使用mkdir创建目录 /dev/__properties__,用于存储系统属性相关的信息
      -CreateSerializedPropertyInfo: 创建序列化属性上下文信息
      • 检查多个分区下的属性上下文文件(如/system/etc/selinux/plat_property_contexts),将它们加载到属性信息列表中,再将属性信息列表构建为序列化数据,最后将序列化数据写入到指定文件/dev/__properties__/property_info,并恢复其 SELinux 上下文
    • __system_property_area_init:初始化系统属性的内存共享区域
      • 加载默认属性信息文件/dev/__properties__/property_info,并将其文件内容映射到内存
      • 获取/dev/__properties__/property_info文件中属性上下文的节点数,并根据ContextNode 对象大小和节点数分配一块内存(mmap方式),再使用 placement new 语法在已分配的内存中构造 ContextNode 对象,每个节点初始化时传入对应的上下文信息和文件名。
      • 调用ContextNode的Open方法创建上下文节点文件,并映射内存(每个节点文件大小128K)在这里插入图片描述
      • 创建/dev/__properties__/properties_serial文件,并映射内存
    • property_info_area.LoadDefaultPath:加载默认属性信息文件/dev/__properties__/property_info,并将其文件内容映射到内存;这里操作和上一步__system_property_area_init中第一步操作相同,但并不是重复操作;property_info_area是一个全局对象,其他进程可以通过其相关的封装函数获取属性上下文信息
    • ProcessKernelDt:处理设备树(Device Tree)中的某些文件,并将这些文件的内容设置为系统属性
    • ProcessKernelCmdline:处理内核命令行参数,并将某些参数设置为系统属性
    • ExportKernelBootProps:通过属性映射表,将内核启动属性导出到其他系统属性中,如ro.boot.serialno的值导入ro.serialno中在这里插入图片描述
    • PropertyLoadBootDefaults:加载多个属性文件,并将这些属性设置到系统中,确保系统启动时加载默认的属性配置,代码流程如下
      • load_properties_from_file:从系统文件中加载属性;
        • 系统的默认属性(文件:/system/etc/prop.default)、系统的build属性(文件:/system/build.prop、/system_ext/build.prop)
        • vendor的默认属性(文件:/vendor/default.prop)、vendor的build属性(文件:/vendor/build.prop)
        • odm的默认属性(文件:/odm/default.prop)、odm的build属性(文件:/odm/build.prop)
        • product的build属性(文件:/product/build.prop)、
        • factory的相关属性(文件:/factory/factory.prop)
        • 提示:开发时如果需要增加的属性,可以在上述文件中添加,或者自己编辑打包一个自定义的xxx.prop文件,利用load_properties_from_file函数加载
      • PropertySet:将上述从文件加载的属性设置到系统中
      • property_initialize_ro_product_props:通过属性来源顺序,初始化只读产品属性,确保产品信息在系统中正确设置
      • property_derive_build_fingerprint:组合多个系统属性,生成并设置 ro.build.fingerprint 属性在这里插入图片描述
      • update_sys_usb_config:动态更新 USB 配置属性 persist.sys.usb.config,以确保在调试模式下启用 ADB(Android Debug Bridge)
        11. 挂载额外的文件系统
MountExtraFilesystems();
/*
static void MountExtraFilesystems() {
#define CHECKCALL(x) \
    if ((x) != 0) PLOG(FATAL) << #x " failed.";

    // /apex is used to mount APEXes
    CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=0"));

    // /linkerconfig is used to keep generated linker configuration
    CHECKCALL(mount("tmpfs", "/linkerconfig", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=0"));
#undef CHECKCALL
}
*/
  • 挂载两个tmpfs文件系统到/apex和/linkerconfig目录。这两个目录分别用于存储APEX文件和动态链接器的配置文件。通过使用tmpfs,这些文件系统中的数据在设备重启后不会保留,确保了系统的安全性和灵活性

12. 设置 SELinux

    SelinuxSetupKernelLogging();
    SelabelInitialize();
    /*
    // 通过 selinux_android_file_context_handle 创建一个文件上下文的处理句柄,然后通过 selinux_android_set_sehandle 将其设置为全局的文件上下文句柄,以便后续使用。
    void SelabelInitialize() {
    sehandle = selinux_android_file_context_handle(); // 创建 context 的处理函数
    selinux_android_set_sehandle(sehandle); // 将新建的处理函数赋值给 fc_sehandle
}
    */
    SelinuxRestoreContext();
  • 设置 SELinux 的内核日志记录,初始化 SELinux 标签,恢复安全上下文。确保系统在第二阶段初始化过程中遵循 SELinux 安全策略,提高系统的安全性。

13. 初始化 epoll 和事件处理

    Epoll epoll;
    if (auto result = epoll.Open(); !result.ok()) {
        PLOG(FATAL) << result.error();
    }

    InstallSignalFdHandler(&epoll);
    InstallInitNotifier(&epoll);
    StartPropertyService(&property_fd);
  • Epoll epoll:创建一个epoll实例(属于init进程)
  • InstallSignalFdHandler(&epoll):设置信号处理程序,使用 signalfd函数创建一个文件描述符,进而信号事件转换为了文件描述符事件,从而可以通过 epoll 机制来异步处理信号
  • InstallInitNotifier(&epoll):设置一个通知机制,用于在适当的时机唤醒主线程来处理事件。原理是使用 eventfd 和 epoll 实现线程之间的通知,具体可以去查看下InstallInitNotifier的源码
  • StartPropertyService(&property_fd):启动属性服务,代码逻辑如下
    • 设置属性服务版本为2
    • 创建socketpair(一对无名Unix域套接字),from_init_socket和init_socket,这两个文件描述符将用于在property_service和init进程之间进行通信
    • 将from_init_socket赋值给全局变量property_fd,即property_fd=from_init_socket,外部函数可以通过property_fd发送消息
    • 设置发送消息标志为true,表示可以发送消息了
    • 创建属性设置的监听socket(即property_set_fd),将 socket 绑定到指定的路径/dev/socket/property_service;设置监听队列长度为8
    • 启动属性服务线程,其线程函数负责创建一个epoll实例(属于属性服务线程),然后通过epoll机制监听和处理属性服务相关的I/O事件。它主要处理两个文件描述符上的事件:property_set_fd和init_socket
      • property_set_fd:用于处理其他客户端设置属性的事件
      • init_socket:from_init_socket(property_fd)和init_socket是套接字对,init进程主循环执行init.rc文件中on load_persist_props_action动作时,会调用do_load_persist_props函数,接着调用SendLoadPersistentPropertiesMessage函数,此函数会通过from_init_socket发送消息给属性服务线程加载持久性属性,服务线程通过监听init_socket得到消息,最后

14. 记录启动时间

RecordStageBoottimes(start_time);
  • 记录各个启动阶段(第一阶段和selinux)的时间,用于性能分析。
  • 通过getprop ro.boottime.init.first_stage获取第一阶段初始化的消耗时间,单位ns
  • 通过getprop ro.boottime.init.selinux获取selinux初始化的消耗时间,单位ns在这里插入图片描述
    15. 挂载vendor_overlay和设置OEM锁状态属性
    fs_mgr_vendor_overlay_mount_all();
    export_oem_lock_status();
  • fs_mgr_vendor_overlay_mount_all():挂载vendor_overlay(存放一些特定的ui资源)目录;由于需要获取vndk版本号属性,且第一阶段属性服务未开启,所以在第二阶段挂载。
    • VNDK(Vendor Native Development Kit)版本号用于确定vendor overlay的路径,确保挂载正确的overlay目录,目录格式:“/system/vendor_overlay/[vndk版本号]” 或"/product/vendor_overlay/[vndk版本号]"
  • export_oem_lock_status():根据设备的内核命令行参数,导出OEM锁状态到系统属性中。如果设备支持OEM解锁(ro.oem_unlock_supported = 1),则根据"androidboot.verifiedbootstate"的值设置"ro.boot.flash.locked"属性,表示设备的引导分区是否被锁定。

16. 实时监控和处理文件系统的挂载信息(/proc/mounts)

MountHandler mount_handler(&epoll);
  • 创建一个MountHandler对象,并将其与epoll(属于init进程)事件循环关联起来,以便实时监控和处理文件系统的挂载信息变化;具体处理过程如下:
    • 重置文件读取位置:使用rewind函数将文件位置指示器重置到文件开头,以便重新读取/proc/mounts文件。
    • 读取和解析挂载信息:逐行读取/proc/mounts文件的内容,跳过包含/emulated的行(虚拟文件系统),解析每一行的有效挂载信息。
    • 比较挂载项:将解析出的挂载项与当前已知的挂载项进行比较,找出新增的挂载项和已经不存在的挂载项。
    • 更新挂载状态:对于已经不存在的挂载项,更新其挂载属性为未挂载并从已知挂载项中移除;对于新增的挂载项,更新其挂载属性为已挂载并添加到已知挂载项中。

17. 设置 USB 控制器属性

SetUsbController();
  • 设置系统属性sys.usb.controller,将其值设置为/sys/class/udc目录下的某个控制器名称。这个目录通常包含系统中可用的USB设备控制器(UDC)的标识符
    在这里插入图片描述

19. 设置系统的挂载命名空间

    if (!SetupMountNamespaces()) {
        PLOG(FATAL) << "SetupMountNamespaces failed";
    }
  • 设置系统的挂载命名空间和挂载传播类型,以满足Android系统中不同进程对文件系统挂载的不同需求。具体来说,它确保了某些目录的挂载行为在不同的挂载命名空间中能够正确传播或隔离。

20. 初始化子上下文(/system/bin/init subcontext)

InitializeSubcontext();
  • 根据Android API的等级,初始化vendor相关的子上下文(subcontext)。这个子上下文将用于vendor init进程的创建和管理,确保其具有适当的SELinux上下文和权限。具体流程如下:
    • 判断andorid api等级是否大于28(Android P),大于则执行后续动作
    • 创建Subcontext对象,执行Fork();
    • 创建Socket对,用于subcontext进程与init进程之间的通信
    • 调用fork(),创建子进程,子进程中调用执行/system/bin/init(参数1:subcontext ,参数2:selinux上下文,参数3:subcontext_socket复制体),进而调用SubcontextMain函数,其selinux上下文为u:r:vendor_init:s0,进程名init在这里插入图片描述
    • 通过poll机制监听父进程发来的消息,解析并执行相应的命令,然后将执行结果回复给父进程。这种设计确保了子进程能够灵活地响应父进程的指令,同时保持与父进程的通信和同步
  • 小知识点: subcontext和init之间的消息格式用的是protobuf(相关文件在/system/core/init/subcontext.proto),这是Google公司开发的一种高效的、灵活的、可扩展的数据序列化格式,用于在不同的系统和应用程序之间进行数据交换和存储。它类似于XML或JSON,但具有更高的效率和更小的体积。平时做项目的时候可以考虑下

21. 加载系统启动脚本

    ActionManager& am = ActionManager::GetInstance();
    ServiceList& sm = ServiceList::GetInstance();

    LoadBootScripts(am, sm);
  • 获取一个动作管理类的单例,用于管理动作(Action)的执行和事件的处理
  • 获取一个服务管理类的单例,用于添加、移除、查找服务以及管理服务状态等
  • 加载系统的启动脚本,解析其中定义的初始化动作和服务(关键步骤,后续有空详细介绍):启动脚本定义了系统启动过程中需要执行的各类任务和服务,是系统初始化的重要组成部分。代码流程如下:
    • 创建解析器对象,用于解析配置脚本
    • 获取系统属性ro.boot.init_rc的值,如果该属性不存在,则返回空字符串。
    • 判断是否使用默认启动脚本路径:如果ro.boot.init_rc属性值为空,则按照默认的路径顺序加载启动脚本文件;否则,直接解析该属性指定的脚本文件。开发板实际情况:ro.boot.init_rc属性值为空,所以按照默认的路径顺序加载启动脚本文件
    • 默认路径下的脚本解析:
      • 解析/system/etc/init/hw/init.rc文件:硬件相关的启动脚本文件,通常包含与硬件初始化相关的服务和动作。
      • 解析/system/etc/init目录下的所有.rc文件。如果解析失败(可能是因为目录不存在或没有可解析的文件),则将该路径添加到late_import_paths列表中,供后续处理。
      • 解析/system_ext/etc/init目录下的脚本文件
      • 解析/product/etc/init、/odm/etc/init、/vendor/etc/init目录下的所有.rc文件。如果解析失败(可能是因为目录不存在或没有可解析的文件),则将该路径添加到late_import_paths列表中,供后续处理。

22. 设置 GSI (Generic System Image)状态属性

// Make the GSI status available before scripts start running.
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);
  • 在启动脚本运行之前,将 GSI(Generic System Image)的运行状态和安装状态设置为系统属性。通过将 GSI 的状态设置为系统属性,其他系统组件和脚本可以在运行时动态获取这些状态信息,从而根据 GSI 的状态做出相应的决策。例如,某些服务或脚本可能只在 GSI 运行时才需要启动或执行。

23. 添加一些内置动作、事件触发、属性变化的动作到事件队列中

class ActionManager {
  public:
    static ActionManager& GetInstance();

    // Exposed for testing
    ActionManager();
    size_t CheckAllCommands();

    void AddAction(std::unique_ptr<Action> action);
    void QueueEventTrigger(const std::string& trigger);
    void QueuePropertyChange(const std::string& name, const std::string& value);
    void QueueAllPropertyActions();
    void QueueBuiltinAction(BuiltinFunction func, const std::string& name);
    void ExecuteOneCommand();
    bool HasMoreCommands() const;
    void DumpState() const;
    void ClearQueue();

  private:
    ActionManager(ActionManager const&) = delete;
    void operator=(ActionManager const&) = delete;

    std::vector<std::unique_ptr<Action>> actions_;
    std::queue<std::variant<EventTrigger, PropertyChange, BuiltinAction>> event_queue_
            GUARDED_BY(event_queue_lock_);
    mutable std::mutex event_queue_lock_;
    std::queue<const Action*> current_executing_actions_;
    std::size_t current_command_;
};
  • 根据动作管理类ActionManager的定义,可以看出事件队列event_queue_支持三种类型的动作事件:EventTrigger, PropertyChange, BuiltinAction
 	am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
    am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
    am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
    am.QueueEventTrigger("early-init");

    // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
    am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
    // ... so that we can start queuing up actions that require stuff from /dev.
    am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
    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");

    // Trigger all the boot actions to get us started.
    am.QueueEventTrigger("init");

    // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
    // wasn't ready immediately after wait_for_coldboot_done
    am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");

    // Don't mount filesystems or start core system services in charger mode.
    std::string bootmode = GetProperty("ro.bootmode", "");
    if (bootmode == "charger") {
        am.QueueEventTrigger("charger");
    } else {
        am.QueueEventTrigger("late-init");
    }

    // Run all property triggers based on current state of the properties.
    am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
  • 向动作管理器的事件队列中添加内置动作:SetupCgroups、SetKptrRestrict、TestPerfEventSelinux
  • 向动作管理器的事件队列中添加事件触发动作:early-init ,该动作用于执行与早期初始化相关的动作,比如 start ueventd
  • 向动作管理器的事件队列中添加内置动作:wait_for_coldboot_done,该动作用于等待冷启动完成,确保 ueventd 已设置好所有的 /dev 文件
  • 向动作管理器的事件队列中添加内置动作:MixHwrngIntoLinuxRng、SetMmapRndBits
  • 向动作管理器的事件队列中添加内置动作:KeychordInit,该动作用于初始化键码并注册键码处理程序;键码处理程序用于捕获和处理键盘快捷键(keychords),这些快捷键可以用于执行特定的系统操作。通过在启动过程中初始化键码处理程序,可以确保系统能够及时响应用户的键盘输入。
  • 向动作管理器的事件队列中添加事件触发动作:init ,该动作用于执行与系统初始化相关的动作,比如start logd、start servicemanager、start hwservicemanager、start vndservicemanager
  • 再次向动作管理器的事件队列中添加内置动作:MixHwrngIntoLinuxRng
  • 根据系统属性**启动模式(充电模式或其他模式)**向动作管理器的事件队列中添加事件触发动作:charger(充电模式)或late-init
  • 向动作管理器的事件队列中添加内置动作:queue_property_triggers,该动作用来处理所有属性触发器,确保属性的正确性和一致性。属性触发器用于在系统属性发生变化时执行相应的动作。通过运行这些触发器,可以确保系统在启动过程中根据当前的属性状态正确地配置和初始化各种组件。

24. init进程主事件循环

while (true) {
        // By default, sleep until something happens.
        auto epoll_timeout = std::optional<std::chrono::milliseconds>{};

        auto shutdown_command = shutdown_state.CheckShutdown();
        if (shutdown_command) {
            HandlePowerctlMessage(*shutdown_command);
        }

        if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
            am.ExecuteOneCommand();
        }
        if (!IsShuttingDown()) {
            auto next_process_action_time = HandleProcessActions();

            // If there's a process that needs restarting, wake up in time for that.
            if (next_process_action_time) {
                epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(
                        *next_process_action_time - boot_clock::now());
                if (*epoll_timeout < 0ms) epoll_timeout = 0ms;
            }
        }

        if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
            // If there's more work to do, wake up again immediately.
            if (am.HasMoreCommands()) epoll_timeout = 0ms;
        }

        auto pending_functions = epoll.Wait(epoll_timeout);
        if (!pending_functions.ok()) {
            LOG(ERROR) << pending_functions.error();
        } else if (!pending_functions->empty()) {
            // We always reap children before responding to the other pending functions. This is to
            // prevent a race where other daemons see that a service has exited and ask init to
            // start it again via ctl.start before init has reaped it.
            ReapAnyOutstandingChildren();
            for (const auto& function : *pending_functions) {
                (*function)();
            }
        }
        if (!IsShuttingDown()) {
            HandleControlMessages();
            SetUsbController();
        }
    }
  • 检查是否有关机命令,如果有关机命令,则调用 HandlePowerctlMessage 处理关机消息
  • 如果没有属性等待器在等待或没有服务在运行,则执行一个命令(解析rc文件保存的命令)
  • 通过使用 epoll高效地等待和处理多个文件描述符上的事件(信号事件、通知消息、挂载信息)

(五) ueventd 的启动

简述

ueventd 的启动由 init.rc 中的 start ueventd 命令触发(service ueventd /system/bin/ueventd),/system/bin/ueventd实际是指向init程序,最终执行 ueventd_main 函数。在这里插入图片描述

源码展示

文件路径:system/core/init/main.cpp、system/core/init/ueventd.cpp
函数原型:int ueventd_main(int argc, char** argv);

int ueventd_main(int argc, char** argv) {
    /*
     * init sets the umask to 077 for forked processes. We need to
     * create files with exact permissions, without modification by
     * the umask.
     */
    umask(000);

    android::base::InitLogging(argv, &android::base::KernelLogger);

    LOG(INFO) << "ueventd started!";

    SelinuxSetupKernelLogging();
    SelabelInitialize();

    std::vector<std::unique_ptr<UeventHandler>> uevent_handlers;

    // Keep the current product name base configuration so we remain backwards compatible and
    // allow it to override everything.
    // TODO: cleanup platform ueventd.rc to remove vendor specific device node entries (b/34968103)
    auto hardware = android::base::GetProperty("ro.hardware", "");

    auto ueventd_configuration = ParseConfig({"/system/etc/ueventd.rc", "/vendor/ueventd.rc",
                                              "/odm/ueventd.rc", "/ueventd." + hardware + ".rc"});

    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));
    }
    UeventListener uevent_listener(ueventd_configuration.uevent_socket_rcvbuf_size);

    if (!android::base::GetBoolProperty(kColdBootDoneProp, false)) {
        ColdBoot cold_boot(uevent_listener, uevent_handlers,
                           ueventd_configuration.enable_parallel_restorecon);
        cold_boot.Run();
    }

    for (auto& uevent_handler : uevent_handlers) {
        uevent_handler->ColdbootDone();
    }

    // We use waitpid() in ColdBoot, so we can't ignore SIGCHLD until now.
    signal(SIGCHLD, SIG_IGN);
    // Reap and pending children that exited between the last call to waitpid() and setting SIG_IGN
    // for SIGCHLD above.
    while (waitpid(-1, nullptr, WNOHANG) > 0) {
    }

    uevent_listener.Poll([&uevent_handlers](const Uevent& uevent) {
        for (auto& uevent_handler : uevent_handlers) {
            uevent_handler->HandleUevent(uevent);
        }
        return ListenerAction::kContinue;
    });

    return 0;
}

函数结构和关键步骤

1. 设置文件创建掩码

umask(000);
  • 将文件创建掩码设置为 000,确保后续创建的文件和目录具有完全的读写权限。ueventd 需要根据内核事件动态创建设备节点文件,这些文件的权限需要精确控制,不受 umask 影响。

2. 初始化日志记录和设置 SELinux

   android::base::InitLogging(argv, &android::base::KernelLogger);

   LOG(INFO) << "ueventd started!";

   SelinuxSetupKernelLogging();
   SelabelInitialize();
  • 初始化日志记录功能
  • 设置 SELinux 的内核日志记录
  • 初始化 SELinux 标签

3. 解析uevent相关配置文件

auto hardware = android::base::GetProperty("ro.hardware", "");
auto ueventd_configuration = ParseConfig({"/system/etc/ueventd.rc", "/vendor/ueventd.rc",
                                          "/odm/ueventd.rc", "/ueventd." + hardware + ".rc"});
  • 获取设备的硬件属性,并解析多个配置文件以获取 ueventd 的配置信息。配置文件中定义了设备节点的权限、SELinux 标签等信息,ueventd 需要根据这些配置来正确管理设备节点。

4. 初始化 uevent 处理器

 std::vector<std::unique_ptr<UeventHandler>> uevent_handlers;
 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 对象,用于处理不同类型的 uevent 事件。不同的 uevent 事件需要不同的处理逻辑,例如设备节点创建、固件处理、加载驱动ko模块等。

5. 初始化 uevent 监听器

UeventListener uevent_listener(ueventd_configuration.uevent_socket_rcvbuf_size);
  • 创建一个 UeventListener 对象(使用的Netlink Socket),用于监听内核发送的 uevent 事件。

6. 处理冷启动

    if (!android::base::GetBoolProperty(kColdBootDoneProp, false)) {
        ColdBoot cold_boot(uevent_listener, uevent_handlers,
                           ueventd_configuration.enable_parallel_restorecon);
        cold_boot.Run();
    }

    for (auto& uevent_handler : uevent_handlers) {
        uevent_handler->ColdbootDone();
    }

    // We use waitpid() in ColdBoot, so we can't ignore SIGCHLD until now.
    signal(SIGCHLD, SIG_IGN);
    // Reap and pending children that exited between the last call to waitpid() and setting SIG_IGN
    // for SIGCHLD above.
    while (waitpid(-1, nullptr, WNOHANG) > 0) {
    }
  • 检查系统属性ro.cold_boot_done是否为false。如果该属性为false,说明系统尚未完成冷启动,需要执行冷启动流程。
    • 创建一个ColdBoot对象cold_boot。该对象的构造函数接收三个参数:
      • uevent_listener:一个uevent监听器,用于监听内核发出的uevent事件。
      • uevent_handlers:一个包含uevent事件处理程序的容器,这些处理程序将被调用以处理不同的uevent事件。
      • ueventd_configuration.enable_parallel_restorecon:一个布尔值,表示是否启用并行的restorecon操作(restorecon用于设置文件的安全上下文,在SELinux环境中非常重要)。
    • 调用cold_boot.Run()方法执行冷启动流程。
      • 在指定目录/sys/devices及其子目录中重新生成uevent事件,并通过回调函数将uevent事件加入到uevent事件队列中。
      • fork子进程,对uevent队列中的事件进行划分,并根据不同的uevent 处理器将每个事件分发给相应的处理程序进行处理
      • 处理完事件等待子进程退出。
      • 最后设置系统属性ro.cold_boot_done为true
  • 遍历uevent_handlers容器中的每个uevent处理程序,并调用它们的ColdbootDone方法。通知所有 uevent 处理器冷启动已完成。
  • 忽略SIGCHLD信号,清理可能存在的僵尸进程(主要是冷启动过程中fork了很多子进程)

7. 监听并处理 uevent 事件

    uevent_listener.Poll([&uevent_handlers](const Uevent& uevent) {
        for (auto& uevent_handler : uevent_handlers) {
            uevent_handler->HandleUevent(uevent);
        }
        return ListenerAction::kContinue;
    });
  • 使用 UeventListener 的 Poll 方法监听内核发送的 uevent 事件,并将事件传递给所有uevent处理器进行处理。如动态创建或删除设备节点,确保系统设备的正确管理和访问权限

总结(简略图)

主函数执行流程简略图

  • 文件路径:system/core/init/main.cpp
    主函数执行简要流程图

init程序执行情况简略图

init程序执行情况

  • 整个初始化过程里,/system/bin/init程序共执行了五次,情况如下:
    • /system/bin/init(无参数): 第一阶段初始化
    • /system/bin/init selinux_setup:Selinux初始化
    • /system/bin/init second_stage:第二阶段初始化
    • /system/bin/init subcontext:子上下文初始化,即vendor域的init进程;属于第二阶段初始化中的一部分,产生过程:通过fork出的子进程里执行/system/bin/init subcontext启动
    • /system/bin/ueventd->init:ueventd初始化;属于第二阶段初始化中的一部分,产生过程:通过解析init.rc文件,然后执行start ueventd服务,最后执行/system/bin/ueventd启动(/system/bin/ueventd链接到/system/bin/init)

第一阶段初始化简略图

  • 文件路径:system/core/init/main.cpp、system/core/init/first_stage_init.cpp
    第一阶段初始化主要操作

Selinux初始化简略图

  • 文件路径:system/core/init/main.cpp、system/core/init/first_stage_init.cppSelinux初始化主要操作

第二阶段初始化简略图

  • 文件路径:system/core/init/main.cpp、system/core/init/init.cpp第二阶段初始化主要操作
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值