Android 系统启动之 Init 进程启动分析二

本文基于 AOSP android-10.0.0_r41 版本讲解,内核版本 android-goldfish-4.14-gchips

1. SetupSelinux 初始化 SeLinux

上一节我们分析了 init 进程的第一阶段------ FirstStageMain 函数的执行过程,FirstStageMain 函数的最后阶段会执行到 SetupSelinux 函数:

cpp 复制代码
// system/core/init/selinux.cpp
// This function initializes SELinux then execs init to run in the init SELinux context.
int SetupSelinux(char** argv) {
    //初始化本阶段内核日志
    InitKernelLogging(argv);

    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }

     //  初始化 SELinux,加载 SELinux 策略
    // Set up SELinux, loading the SELinux policy.
    SelinuxSetupKernelLogging(); // selinux 日志重定向到内核
    // LoadPolicy 加载 selinux_policy 策略文件
    // security_setenforce() 设置默认 selinux 模式
    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.

    //  再次调用 main 函数,并传入 second_stage 进入第二阶段
    //  而且此次启动就已经在 SELinux 上下文中运行

    // 加载可执行文件 `/system/bin/init` 的安全上下文
    if (selinux_android_restorecon("/system/bin/init", 0) == -1) {
        PLOG(FATAL) << "restorecon failed of /system/bin/init failed";
    }

    // 进入下一步
    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;
}
  • 先调用 InitKernelLogging(argv) 初始化内核日志,再调用 InstallRebootSignalHandlers() 注册需要处理的信号,这个和第一阶段是一样的
  • 接着调用 SelinuxSetupKernelLogging() 将 selinux 日志重定向到内核
  • 接着调用 SelinuxInitialize 加载 selinux_policy 策略文件,设置默认 selinux 模式
  • 最后调用 selinux_android_restorecon 来设置 init 的安全上下问,接着通过 execv 跳转到第二阶段

接下来逐步分析

2. SelinuxSetupKernelLogging selinux 日志重定向

SelinuxSetupKernelLogging 将 selinux 日志重定向到内核,其具体实现如下:

cpp 复制代码
// system/core/init/selinux.cpp

//selinux 日志重定向到内核
// This function sets up SELinux logging to be written to kmsg, to match init's logging.
void SelinuxSetupKernelLogging() {
    selinux_callback cb;
    cb.func_log = SelinuxKlogCallback;
    // 设置 selinux 日志的回调
    selinux_set_callback(SELINUX_CB_LOG, cb);
}

这里调用了 selinux_set_callback 函数,第一个参数 SELINUX_CB_LOG 表示要设置 Selinux 日志的回调,第二个参数 cb 的成员 func_log 就是具体的回调函数:

cpp 复制代码
// 将 selinux 日志重定向到 /dev/kmsg
int SelinuxKlogCallback(int type, const char* fmt, ...) {
    android::base::LogSeverity severity = android::base::ERROR;
    if (type == SELINUX_WARNING) {
        severity = android::base::WARNING;
    } else if (type == SELINUX_INFO) {
        severity = android::base::INFO;
    }
    char buf[1024];
    va_list ap;
    va_start(ap, fmt);
    vsnprintf(buf, sizeof(buf), fmt, ap);
    va_end(ap);
    android::base::KernelLogger(android::base::MAIN, severity, "selinux", nullptr, 0, buf);
    return 0;
}

这里会调用 KernelLogger 将 Selinux 日志写入 /dev/kmsg 中。和上文分析的 InitKernelLogging(argv) 类似。

3. SelinuxInitialize 初始化 Selinux 环境

SelinuxInitialize 用于执行 Selinux 的初始化:

cpp 复制代码
void SelinuxInitialize() {
    Timer t;

    LOG(INFO) << "Loading SELinux policy";
    // 加载 selinux_policy 策略文件
    if (!LoadPolicy()) {
        LOG(FATAL) << "Unable to load SELinux policy";
    }

    bool kernel_enforcing = (security_getenforce() == 1);
    bool is_enforcing = IsEnforcing();
    if (kernel_enforcing != is_enforcing) {
        // 设置默认 selinux 模式
        if (security_setenforce(is_enforcing)) { 
            PLOG(FATAL) << "security_setenforce(%s) failed" << (is_enforcing ? "true" : "false");
        }
    }

    if (auto result = WriteFile("/sys/fs/selinux/checkreqprot", "0"); !result) {
        LOG(FATAL) << "Unable to write to /sys/fs/selinux/checkreqprot: " << result.error();
    }

    // init's first stage can't set properties, so pass the time to the second stage.
    setenv("INIT_SELINUX_TOOK", std::to_string(t.duration().count()).c_str(), 1);
}

核心的调用有两个:

  • LoadPolicy:加载 selinux_policy 策略文件
  • security_setenforce:设置 selinux 的工作模式

selinux 配置文件的加载过程我们放到 权限系统 专题给大家讲解,这里了解流程即可

4. 跳转第二阶段

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

这里跳转 init 时,带了一个参数 "second_stage",程序进入第二阶段。

参考资料

相关推荐
阿巴斯甜14 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker15 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952716 小时前
Andorid Google 登录接入文档
android
黄林晴17 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android