Android 源码学习之init进程 - FirstStageMain()

103 阅读20分钟

Android Init 进程第一阶段 FirstStageMain 详细分析

FirstStageMain 是 Android 启动过程中从内核空间过渡到用户空间的第一个进程,在最小的 initramdisk 环境中建立基础系统设施,为后续启动阶段奠定基础。

大致流程如下:

Untitled diagram-2025-11-25-023017.png

完整执行流程详细分析

1. 紧急重启信号处理

if (REBOOT_BOOTLOADER_ON_PANIC) {
    InstallRebootSignalHandlers();
}

分析

  • 目的:建立安全防护网,防止系统卡死在崩溃状态
  • 信号覆盖:SIGABRT、SIGBUS、SIGSEGV 等关键信号
  • 生产/开发差异
    • 生产版本:启用,确保设备可恢复性
    • 开发版本:可能禁用,便于调试崩溃现场
  • 实现机制
    void InstallRebootSignalHandlers() {
        struct sigaction action;
        memset(&action, 0, sizeof(action));
        action.sa_handler = [](int signal) {
            LOG(ERROR) << "Received fatal signal " << signal;
            sync();  // 确保数据刷入存储
            RebootSystem(ANDROID_RB_RESTART2, "bootloader");
        };
        sigaction(SIGABRT, &action, nullptr);
        sigaction(SIGSEGV, &action, nullptr);
        sigaction(SIGBUS, &action, nullptr);
    }
    

2. 启动时间记录和错误收集框架

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

分析

  • 精确计时:使用 boot_clock 记录启动耗时,用于性能监控和优化
  • 集中错误管理:统一收集所有初始化错误,避免分散的错误处理
  • 宏技巧
    • #x 字符串化操作符,将代码转换为可读的错误消息
    • 保存 errno 提供详细的系统错误信息
  • 延迟报告:不立即处理错误,而是收集后统一报告,便于问题诊断

3. 基础环境初始化

umask(0);
CHECKCALL(clearenv());
CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));

解析

umask(0)
  • 作用机理:清除文件创建掩码,设置为 0000
  • 影响范围:确保新创建的文件具有精确的权限位(0777 & ~umask)
  • 必要性:避免从内核继承不可预测的 umask 设置
  • 安全考虑:后续会针对具体文件设置适当权限
clearenv()
  • 安全哲学:从绝对干净的状态开始,避免环境变量污染
  • 内核继承:清除从内核通过 execve 传递的所有环境变量
  • 确定性:确保启动环境的一致性,不受外部因素影响
setenv("PATH", _PATH_DEFPATH, 1)
  • 最小化原则:PATH 只包含 /sbin:/system/bin:/system/xbin
  • 安全限制:防止执行未经验证位置的可执行文件
  • 渐进式扩展:后续阶段会根据需要扩展 PATH 环境变量

4. 虚拟文件系统建立

4.1 挂载 tmpfs 到 /dev
CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));

技术分析

tmpfs 特性利用

  • 内存驻留:数据存储在 RAM 中,访问速度快
  • 动态大小:按需分配内存,不预占固定空间
  • 易失性:重启后数据丢失,符合临时文件的特性

挂载必要性

  • initramfs 通常是只读的,无法创建设备节点
  • 需要可写空间来动态管理设备文件
  • 为 udev 或 mdev 类似的设备管理提供基础设施

安全标志解析

  • MS_NOSUID
    • 防止在 /dev 目录中通过 SUID/SGID 程序进行权限提升
    • 阻断潜在的攻击向量
  • mode=0755
    • root 用户:读、写、执行权限
    • 其他用户:读、执行权限,防止普通用户创建设备文件
4.2 创建设备目录结构
CHECKCALL(mkdir("/dev/pts", 0755));      // 伪终端从设备
CHECKCALL(mkdir("/dev/socket", 0755));   // 进程间通信套接字
CHECKCALL(mkdir("/dev/dm-user", 0755));  // 设备映射器用户空间接口

目录作用详解

/dev/pts - 伪终端从设备系统
  • 架构设计: 应用程序 → /dev/ptmx (主设备) → 内核PTY驱动 → /dev/pts/N (从设备) ← Shell进程
  • ADB集成:每个 adb shell 会话创建一个新的 /dev/pts/N 设备
  • 终端仿真:终端应用、SSH 服务器等依赖此系统
  • 权限管理:从设备通常权限为 0620,确保只有相应会话用户可访问
/dev/socket - Unix Domain Sockets
  • 进程间通信:提供基于文件的进程间通信机制
  • 系统服务使用
    • Zygote:应用进程孵化器
    • SurfaceFlinger:图形合成服务
    • netd:网络管理守护进程
  • 安全模型:通过文件系统权限控制访问权限
/dev/dm-user - 设备映射器用户空间接口
  • Android 11+ 新特性:用户空间与内核设备映射器的通信通道
  • F2FS 透明压缩: 文件系统 → dm-user设备 → 用户空间压缩守护进程 → 压缩数据存储
  • 增量文件系统:支持动态系统更新的基座+增量合并
  • 性能优化:在用户空间实现复杂的存储功能,避免内核复杂度
4.3 挂载核心虚拟文件系统
CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));
CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));
CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));

各文件系统分析

procfs (/proc) - 进程信息接口
  • 进程信息暴露
    • /proc/pid/status:进程状态信息
    • /proc/pid/fd/:打开的文件描述符
    • /proc/pid/maps:内存映射信息
  • 系统信息接口
    • /proc/cpuinfo:CPU 架构和信息
    • /proc/meminfo:内存使用情况
    • /proc/version:内核版本信息
  • 安全配置解析
    • hidepid=2
      • 级别 0:传统模式,所有进程可见
      • 级别 1:用户只能看到自己的进程
      • 级别 2:用户只能看到自己的进程,且隐藏其他进程的统计信息
    • gid=AID_READPROC
      • AID_READPROC = 1009,特殊系统组
      • 系统监控工具(ps, top)属于此组,可以查看所有进程
      • 防止恶意软件扫描系统进程信息进行攻击
sysfs (/sys) - 系统设备管理
  • 设备发现机制: /sys/ ├── class/ # 设备类别目录 │ ├── net/ # 网络设备 (eth0, wlan0) │ ├── input/ # 输入设备 (event0, mouse0) │ ├── backlight/ # 背光设备 │ └── leds/ # LED 设备 ├── bus/ # 总线设备 │ ├── usb/ # USB 设备 │ ├── pci/ # PCI 设备 │ └── platform/ # 平台设备 └── devices/ # 物理设备树
  • 运行时配置:通过读写文件调整内核参数和设备行为
  • 电源管理:控制设备电源状态和性能参数
selinuxfs (/sys/fs/selinux) - 安全策略接口
  • 策略管理:SELinux 策略加载和查询接口
  • 状态信息:提供 SELinux 当前状态和强制模式信息
  • 为第二阶段准备:第二阶段的 selinux_setup 会使用此接口

5. 创建设备节点

CHECKCALL(mknod("/dev/kmsg", S_IFCHR | 0600, 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)));

设备节点详解

/dev/kmsg - 内核消息接口
  • 设备号makedev(1, 11) - 主设备号 1(内存设备),次设备号 11
  • 权限 0600:只有 root 可以访问,保护内核日志安全
  • 使用场景
    • init 进程日志输出
    • 内核 printk 消息读取
    • 系统调试信息收集
/dev/random & /dev/urandom - 随机数源
  • 密码学安全:为系统加密操作提供熵源
  • 权限 0666:所有用户可访问,许多库和应用依赖随机数
  • 技术区别
    • random:阻塞型,熵池不足时阻塞,适合高安全性场景
    • urandom:非阻塞型,始终返回数据,适合性能敏感场景
  • 设备号:主设备号 1,次设备号 8 和 9
/dev/ptmx - 伪终端主设备
  • 设备号makedev(5, 2) - 主设备号 5(tty 设备),次设备号 2
  • 终端会话工厂:打开此设备会自动分配新的 pts 从设备
  • ADB Shell 基础adb shell 命令的核心依赖
/dev/null - 空设备
  • 数据黑洞:丢弃所有写入数据,读取立即返回 EOF
  • 标准流重定向:用于丢弃不需要的输出流
  • 设备号makedev(1, 3) - 主设备号 1,次设备号 3

6. 准备挂载点结构

6.1 为 vold 准备挂载区域
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));

/mnt 目录架构分析

/mnt/ (tmpfs, 安全隔离环境)
├── vendor/          # 供应商特定挂载点
│   ├── firmware/    # 硬件固件
│   ├── persist/     # 持久化配置
│   └── ...          # 其他供应商特定数据
├── product/         # 产品特定挂载点
│   ├── media/       # 产品媒体文件
│   ├── app/         # 产品预装应用
│   └── ...          # 其他产品定制内容
├── expand/          # 可扩展存储 (vold 运行时创建)
├── asec/            # 安全容器 (vold 运行时创建)
├── obb/             # 游戏数据包 (vold 运行时创建)
└── runtime/         # 运行时权限挂载 (vold 运行时创建)

安全标志解析

  • MS_NOEXEC:禁止执行其中的程序,防止代码注入攻击
  • MS_NOSUID:忽略 SUID/SGID 权限位,防止权限提升
  • MS_NODEV:禁止访问设备文件,限制设备访问能力
  • uid=0,gid=1000:root 用户,system 组(AID_SYSTEM)
6.2 调试和资源保留
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"));

用途分析

/debug_ramdisk
  • 调试信息保存:在根切换前保存调试数据
  • 崩溃分析:保留系统崩溃时的现场信息
  • 性能监控:存储启动性能指标数据
kSecondStageRes(通常是 /.backup)
  • 阶段间资源传递:保持 ramdisk 中的数据在根切换后仍可访问
  • 资源配置保存:存储需要在第二阶段使用的初始化配置
  • 回滚支持:在启动失败时提供恢复所需的数据

7. 日志系统初始化

SetStdioToDevNull(argv);
InitKernelLogging(argv);
SetStdioToDevNull() 实现
void SetStdioToDevNull() {
    // 打开 /dev/null
    int fd = open("/dev/null", O_RDWR);
    if (fd == -1) {
        // 在 /dev/null 不可用时,这是严重错误
        abort();
    }
  
    // 重定向标准输入、输出、错误
    dup2(fd, STDIN_FILENO);   // 标准输入 ← /dev/null
    dup2(fd, STDOUT_FILENO);  // 标准输出 → /dev/null  
    dup2(fd, STDERR_FILENO);  // 标准错误 → /dev/null
  
    // 关闭原始文件描述符
    if (fd > STDERR_FILENO) {
        close(fd);
    }
}
  • 目的:防止未初始化的标准流导致意外行为或数据竞争
  • 必要性:在控制台完全建立前,确保标准流有确定的行为
  • 安全考虑:避免敏感信息泄漏到未初始化的输出流
InitKernelLogging(argv) 分析
void InitKernelLogging(char** argv) {
    // 初始化日志标签
    log_id id = LOG_ID_MAIN;
    const char* tag = basename(argv[0]);
  
    // 打开内核日志设备
    int fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
    if (fd == -1) {
        return; // 日志初始化失败,但继续启动
    }
  
    // 设置日志输出到内核环状缓冲区
    android_set_log_frontend(LOGGER_KERNEL, fd);
  
    // 根据内核参数设置日志级别
    std::string loglevel;
    if (fs_mgr_get_boot_config("loglevel", &loglevel)) {
        SetLogLevelFromString(loglevel);
    }
}
  • 输出到 kmsg:所有 LOG() 宏输出到 /dev/kmsg
  • 日志等级控制:根据 loglevel= 内核参数设置详细程度
  • 标签标识:使用程序名作为日志标签,便于过滤和分析

8. 错误检查和处理

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

错误处理策略分析

统一错误报告机制
  • 批量处理:收集所有错误后统一输出,避免分散的错误信息
  • 上下文保留:同时记录错误描述和系统 errno,提供完整上下文
  • 人类可读:使用 strerror() 将 errno 转换为可读描述
严重性分级
// CHECKCALL 宏处理的错误类型
- EACCES: 权限不足
- ENOENT: 文件或目录不存在
- ENOTDIR: 路径不是目录
- EIO: I/O 错误
- ENOMEM: 内存不足
// 这些错误通常表示系统配置错误或硬件问题
终止策略
  • 立即终止:关键错误导致立即终止,防止后续未知行为
  • 明确信息:提供清晰的错误消息,指导问题诊断
  • FATAL 级别:确保错误消息被看到,即使日志系统有问题

9. 内核模块加载

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)) {
    // 模块加载失败处理
}
### 10. 设备创建和早期控制台

```cpp
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);
}
DoCreateDevices() 分析
bool DoCreateDevices() {
    // 1. 初始化基础设备
    if (!InitDevices()) {
        LOG(ERROR) << "Failed to initialize base devices";
        return false;
    }
  
    // 2. 挂载 metadata 分区(如果存在)
    auto metadata_partition = std::find_if(fstab_.begin(), fstab_.end(), 
        [](const auto& entry) { 
            return entry.mount_point == "/metadata"; 
        });
  
    if (metadata_partition != fstab_.end()) {
        if (MountPartition(metadata_partition, true /* erase_same_mounts */)) {
            // 必须在切换根目录前完成 DSU 密钥复制
            CopyDsuAvbKeys();
        } else {
            LOG(WARNING) << "Failed to mount metadata partition";
        }
    }
  
    // 3. 创建逻辑分区
    if (!CreateLogicalPartitions()) {
        LOG(ERROR) << "Failed to create logical partitions";
        return false;
    }
  
    return true;
}
InitDevices() 设备初始化
bool FirstStageMount::InitDevices() {
    std::set<std::string> devices;
    GetSuperDeviceName(&devices);
	// 获取所有的dm verity设备
    if (!GetDmVerityDevices(&devices)) {
        return false;
    }
	// 初始化设备映射器
    if (!InitRequiredDevices(std::move(devices))) {
        return false;
    }

    if (IsDmLinearEnabled()) {
        auto super_symlink = "/dev/block/by-name/"s + super_partition_name_;
        if (!android::base::Realpath(super_symlink, &super_path_)) {
            PLOG(ERROR) << "realpath failed: " << super_symlink;
            return false;
        }
    }
    return true;
}

// InitRequiredDevices(std::move(devices))会调用到此方法初始化一个DeviceMapper设备
bool BlockDevInitializer::InitDeviceMapper() {
    return InitMiscDevice("device-mapper");
}

bool BlockDevInitializer::InitMiscDevice(const std::string& name) {
    const std::string dm_path = "/devices/virtual/misc/" + name;
    bool found = false;
    auto dm_callback = [this, &dm_path, &found](const Uevent& uevent) {
        if (uevent.path == dm_path) {
            device_handler_->HandleUevent(uevent);
            found = true;
            return ListenerAction::kStop;
        }
        return ListenerAction::kContinue;
    };
    uevent_listener_.RegenerateUeventsForPath("/sys" + dm_path, dm_callback);
   
    return true;
}

CreateLogicalPartitions() 逻辑分区创建
bool CreateLogicalPartitions() {
    // 获取超级分区信息
    std::string super_device = fs_mgr_get_super_partition_name();
    if (super_device.empty()) {
        LOG(INFO) << "No super partition found, skipping logical partitions";
        return true; // 不是错误,传统分区系统
    }
  
    // 创建逻辑分区表
    LpMetadata metadata = ReadPartitionMetadata(super_device);
    if (!metadata) {
        LOG(ERROR) << "Failed to read partition metadata";
        return false;
    }
  
    // 为每个分区创建设备映射器设备
    for (const auto& partition : metadata.partitions) {
        if (!CreateLogicalPartition(partition, super_device)) {
            LOG(ERROR) << "Failed to create logical partition: " << partition.name;
            return false;
        }
    }
  
    return true;
}
StartConsole() 控制台启动
void StartConsole(const std::string& cmdline) {
    // 解析控制台参数
    std::string console_device = GetConsoleDeviceFromCmdline(cmdline);
    if (console_device.empty()) {
        console_device = "/dev/console"; // 默认控制台
    }
  
    // 设置控制台
    if (!SetupConsole(console_device)) {
        LOG(ERROR) << "Failed to setup console on " << console_device;
        return;
    }
  
    // 启动 shell(在控制台上)
    StartConsoleShell();
  
    LOG(INFO) << "Console started on " << console_device;
}

11. 配置和策略准备

11.1 复制 ramdisk 属性
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;
}

分析

  • boot image 属性:存储在 boot image ramdisk 中的构建属性
  • 第二阶段访问:复制到第二阶段可以访问的持久化位置
  • 构建信息传递:确保构建信息在根切换后仍然可用
11.2 调试模式支持
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;
  
    // 复制 ADB 调试属性
    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();
    }
  
    // 复制 userdebug SELinux 策略
    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);
}

调试功能分析

ADB 调试支持
  • adb_debug.prop:包含 ro.debuggable=1ro.adb.secure=0
  • adb root:在 unlocked 设备上允许 adb root 命令
  • 开发工具:启用完整的开发工具链支持
SELinux 策略调整
  • userdebug 策略:更宽松的 SELinux 策略,便于调试
  • 权限放宽:允许调试操作所需的额外权限
  • 审计日志:更详细的 SELinux 拒绝日志
环境变量传递
  • INIT_FORCE_DEBUGGABLE:通知第二阶段 init 使用调试配置
  • 策略选择:影响 SELinux 策略加载和服务启动

12. 根目录切换准备和执行

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");
}
ForceNormalBoot() 强制正常启动
bool ForceNormalBoot(const std::string& cmdline, const std::string& bootconfig) {
    // 检查 bootconfig 参数
    if (bootconfig.find("androidboot.force_normal_boot = \"1\"") != std::string::npos) {
        return true;
    }
  
    // 检查内核命令行参数
    if (cmdline.find("androidboot.force_normal_boot=1") != std::string::npos) {
        return true;
    }
  
    return false;
}

使用场景

  • 系统更新后:确保更新后启动到新系统而不是恢复模式
  • 启动循环恢复:打破可能的启动循环
  • 工厂测试:在测试环境中强制正常启动
PrepareSwitchRoot() 根切换准备
void PrepareSwitchRoot() {
    // 创建新根目录结构
    const std::vector<std::string> new_root_dirs = {
        "/first_stage_ramdisk/system",
        "/first_stage_ramdisk/vendor",
        "/first_stage_ramdisk/product",
        "/first_stage_ramdisk/data",
        "/first_stage_ramdisk/dev",
        "/first_stage_ramdisk/proc",
        "/first_stage_ramdisk/sys"
    };
  
    for (const auto& dir : new_root_dirs) {
        if (mkdir(dir.c_str(), 0755) != 0 && errno != EEXIST) {
            PLOG(FATAL) << "Failed to create directory: " << dir;
        }
    }
  
    // 设置挂载传播类型
    if (mount(nullptr, "/", nullptr, MS_SLAVE | MS_REC, nullptr) != 0) {
        PLOG(WARNING) << "Failed to set mount propagation to slave";
    }
}
SwitchRoot() 根切换执行
void SwitchRoot(const std::string& new_root) {
    // 切换到新根目录
    if (chdir(new_root.c_str()) != 0) {
        PLOG(FATAL) << "Could not chdir to new root: " << new_root;
    }
  
    // 执行 pivot_root
    if (pivot_root(".", "old_root") != 0) {
        PLOG(FATAL) << "pivot_root failed";
    }
  
    // 切换到新根目录
    if (chdir("/") != 0) {
        PLOG(FATAL) << "chdir to new root failed";
    }
  
    // 卸载旧根目录
    if (umount2("/old_root", MNT_DETACH) != 0) {
        PLOG(ERROR) << "Failed to unmount old root";
    }
  
    // 移除旧根目录
    if (rmdir("/old_root") != 0) {
        PLOG(WARNING) << "Failed to remove old root directory";
    }
  
    LOG(INFO) << "Successfully switched root to " << new_root;
}

根切换技术分析

pivot_root vs chroot
  • pivot_root

    • 系统级根切换,影响整个挂载命名空间
    • 可以完全卸载旧根文件系统
    • 现代 Linux 推荐方式
  • chroot

    • 进程级根切换,只影响当前进程
    • 旧根文件系统仍然保持挂载
    • 传统方式,逐渐被淘汰
绑定挂载的重要性
mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr);
  • 自引用挂载:确保目录在自身内部可见
  • pivot_root 要求pivot_root 要求目标是一个挂载点
  • 一致性保证:避免根切换后的路径解析问题

13. 第一阶段挂载执行

if (!DoFirstStageMount(!created_devices)) {
    LOG(FATAL) << "Failed to mount required partitions early ...";
}
DoFirstStageMount() 分析
bool DoFirstStageMount(bool create_devices) {
    // 1. 读取第一阶段 fstab
    Fstab fstab;
    if (!ReadFirstStageFstab(&fstab)) {
        LOG(ERROR) << "Failed to read first stage fstab";
        return false;
    }
  
    // 2. 条件性创建设备
    if (create_devices && !DoCreateDevices()) {
        LOG(ERROR) << "Failed to create devices for first stage mount";
        return false;
    }
  
    // 3. 创建适当的挂载器实例
    auto mount_helper = FirstStageMount::Create();
    if (!mount_helper) {
        LOG(ERROR) << "Failed to create first stage mount helper";
        return false;
    }
  
    // 4. 执行挂载
    return mount_helper->DoFirstStageMount(create_devices);
}
FirstStageMount::Create() 工厂方法
std::unique_ptr<FirstStageMount> FirstStageMount::Create() {
    Fstab fstab;
    if (!ReadFirstStageFstab(&fstab)) {
        return nullptr;
    }

    // 根据系统特性选择挂载策略
    if (IsDmVerityEnabled() && !IsRecoveryMode()) {
        if (IsAvbEnabled()) {
            // Android 验证启动 v2.0
            return std::make_unique<FirstStageMountVBootV2>(std::move(fstab));
        } else {
            // 传统 dm-verity
            return std::make_unique<FirstStageMountVerity>(std::move(fstab));
        }
    } else {
        // 基础版本,无验证
        return std::make_unique<FirstStageMount>(std::move(fstab));
    }
}
挂载策略实现差异
FirstStageMountVBootV2 (AVB 2.0)
bool FirstStageMountVBootV2::DoFirstStageMount(bool create_devices) {
    // 1. 初始化 AVB 处理
    if (!InitAvbHandle()) {
        return false;
    }
  
    // 2. 验证分区
    if (!VerifyPartitions()) {
        return false;
    }
  
    // 3. 设置 dm-verity
    if (!SetUpDmVerity()) {
        return false;
    }
  
    // 4. 挂载分区
    return MountPartitions();
}
FirstStageMount (基础版本)
bool FirstStageMount::DoFirstStageMount(bool create_devices) {
    // 直接挂载分区,无验证
    return MountPartitions();
}

14. 系统根目录切换的关键步骤

FirstStageMount::MountPartitions() 中,首先调用 TrySwitchSystemAsRoot()

bool FirstStageMount::MountPartitions() {
    // 1. 首先尝试系统根目录切换
    if (!TrySwitchSystemAsRoot()) {
        LOG(ERROR) << "Failed to switch to system-as-root";
        return false;
    }
  
    // 2. 然后挂载其他分区
    return MountRemainingPartitions();
}
TrySwitchSystemAsRoot() 详细实现
bool FirstStageMount::TrySwitchSystemAsRoot() {
    // 检查是否支持 System-as-Root
    if (!IsSystemAsRootSupported()) {
        LOG(INFO) << "System-as-root not supported, using traditional layout";
        return true;  // 不支持也不算失败
    }
  
    // 查找系统分区条目
    auto system_entry = GetSystemRootEntry();
    if (system_entry == fstab_.end()) {
        LOG(ERROR) << "No system partition found for system-as-root";
        return false;
    }
  
    // 挂载系统分区到临时位置
    if (!MountSystemForRootSwitch(system_entry)) {
        return false;
    }
  
    // 执行根切换
    return ExecuteSystemRootSwitch();
}
System-as-Root 架构优势

传统布局

/ (rootfs, initramfs)
├── system/     # 系统分区挂载点
├── vendor/     # 供应商分区挂载点
├── data/       # 数据分区挂载点
└── ...

System-as-Root 布局

/ (system 分区)
├── system/     # 系统子目录(绑定挂载或兼容性链接)
├── vendor/     # 供应商分区挂载点
├── data/       # 数据分区挂载点
└── ...

优势

  • 安全性:系统分区只读,防止篡改
  • 一致性:统一的分区布局
  • 简化性:减少 initramfs 的复杂性
  • 验证:更好的验证启动支持

15. 资源清理和状态设置

15.1 释放 ramdisk 资源
struct stat old_root_info;
if (stat("/", &old_root_info) != 0) {
    PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk";
    old_root_dir.reset();
}

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

资源释放分析

设备号比较
  • st_dev:文件所在设备的设备号
  • 根切换前:initramfs 的设备号
  • 根切换后:系统分区的设备号
  • 设备号不同:说明成功执行了根切换
FreeRamdisk 实现
void FreeRamdisk(DIR* dir, dev_t ramdisk_dev) {
    struct dirent* entry;
    while ((entry = readdir(dir)) != nullptr) {
        // 跳过 . 和 ..
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
            continue;
        }
    
        std::string path = std::string("/") + entry->d_name;
        struct stat st;
        if (stat(path.c_str(), &st) == 0 && st.st_dev == ramdisk_dev) {
            // 删除 ramdisk 中的文件
            if (S_ISDIR(st.st_mode)) {
                DeleteRecursive(path);
            } else {
                unlink(path.c_str());
            }
        }
    }
  
    LOG(INFO) << "Freed ramdisk resources";
}
15.2 设置启动时间戳
setenv(kEnvFirstStageStartedAt, 
       std::to_string(start_time.time_since_epoch().count()).c_str(), 1);

性能监控分析

时间戳格式
  • boot_clock::time_point:基于启动单调时钟
  • time_since_epoch().count():从启动开始的纳秒数
  • 精度:纳秒级精度,用于精确性能分析
环境变量传递
  • 第二阶段使用:第二阶段 init 可以读取此变量计算总启动时间
  • 性能分析:用于启动时间优化和性能监控
  • 调试支持:在启动问题时提供时间参考点

16. 移交控制权到第二阶段

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() 只有出错时才会返回
PLOG(FATAL) << "execv(\"" << path << "\") failed";

控制权移交分析

标准流重定向
// 打开内核日志设备
int fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);

// 重定向标准输出和错误
dup2(fd, STDOUT_FILENO);  // fd → 1 (stdout)
dup2(fd, STDERR_FILENO);  // fd → 2 (stderr)

// 关闭原始文件描述符
close(fd);
  • 确保日志:所有后续输出都进入内核日志系统
  • 调试支持:即使在控制台不可用时也能获取日志
  • 一致性:保持整个启动过程的日志输出一致性
execv() 系统调用
execv("/system/bin/init", {"init", "selinux_setup", nullptr});
  • 进程替换:用新的程序映像完全替换当前进程
  • 参数传递
    • "init":argv[0],通常是程序名
    • "selinux_setup":argv[1],指示进入启动的第二阶段
  • 环境继承:当前环境变量(包括第一阶段设置的时间戳)被继承
  • 不会返回:成功时当前进程被完全替换,控制流不会返回
错误处理
// execv() 只有失败时才会返回
if (errno == ENOENT) {
    LOG(ERROR) << "Second stage init not found: " << path;
} else if (errno == EACCES) {
    LOG(ERROR) << "Permission denied executing: " << path;
} else {
    PLOG(ERROR) << "Failed to execute second stage init";
}
// 此处会触发 FATAL 错误,系统无法继续启动

第一阶段的核心

建立的基础设施:

  1. 完整的设备管理系统

    • 动态设备节点创建和管理
    • 设备映射器框架初始化
    • 逻辑分区设备支持
  2. 虚拟文件系统体系

    • procfs:进程和系统信息接口
    • sysfs:硬件设备管理接口
    • devpts:伪终端系统
    • selinuxfs:安全策略接口
  3. 日志和调试系统

    • 内核日志系统初始化
    • 控制台设备建立
    • 分级错误报告机制
  4. 存储和文件系统

    • 核心分区挂载(system、vendor 等)
    • tmpfs 临时文件系统
    • 挂载点命名空间准备
  5. 安全基础

    • 设备权限设置
    • 进程隔离准备
    • SELinux 接口建立
    • 验证启动支持

启动时间线总结

Linux 内核启动
    ↓
执行 /init (FirstStageMain)
    ├── 紧急处理设置 (信号处理器)
    ├── 基础环境初始化 (umask, PATH, 环境变量)
    ├── 虚拟文件系统建立
    │   ├── /dev (tmpfs, 设备节点)
    │   ├── /proc (进程信息, hidepid=2安全)
    │   ├── /sys (设备信息)
    │   └── /sys/fs/selinux (安全策略)
    ├── 设备节点创建 (kmsg, null, random, ptmx等)
    ├── 挂载点准备 (/mnt, /debug_ramdisk)
    ├── 日志系统初始化 (重定向到 kmsg)
    ├── 内核模块加载 (并行优化)
    ├── 设备创建和验证设置 (Device-mapper, 逻辑分区)
    ├── 配置和策略准备 (ramdisk属性, 调试支持)
    ├── 根目录切换准备 (pivot_root)
    ├── 第一阶段分区挂载 (多策略选择)
    ├── 系统根目录切换 (TrySwitchSystemAsRoot)
    ├── 资源清理和状态设置 (时间戳, 环境变量)
    └── 执行第二阶段 init (selinux_setup)
        ↓
    SELinux 初始化阶段...