【Android FrameWork】第五天:init加载RC文件

Init加载RC文件

在Android系统启动流程中,Init进程是用户空间的第一个进程(PID=1),而RC文件(初始化脚本文件)则是Init进程的"操作手册"。

理解Init线程加载RC文件的过程,需结合源码逻辑才能掌握其核心机制。

本文基于Android 12+源码 (路径:system/core/init/),从概念、流程到关键机制,补充源码片段与注释,帮你建立"代码-功能"的对应认知。

Init进程与RC文件的角色定位

1. Init进程:用户空间的"启动管家"

Init进程由Linux内核通过/init可执行文件启动,其核心逻辑在init.cppmain()函数中实现,主要职责通过源码可直观体现:

cpp 复制代码
// system/core/init/main.cpp
int main(int argc, char** argv) {
    // 1. 初始化日志系统(klog+main_log)
    log_init();
    // 2. 挂载核心文件系统(/sys、/dev、/proc等)
    mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
    // 3. 初始化RC文件解析器
    Parser parser;
    // 4. 加载并解析RC文件(核心步骤)
    parser.ParseConfig("/init.rc");
    // 5. 执行RC文件中定义的动作与服务
    ActionManager& am = ActionManager::GetInstance();
    am.QueueEventTrigger("early-init");
    // 6. 进入事件循环,处理服务监控与事件响应
    return am.EventLoop();
}

2. RC文件:Init进程的"配置说明书"

RC文件通过特定语法定义"动作(Action)"和"服务(Service)",常见文件及作用如下表,其加载顺序由源码中ParseConfig()的调用逻辑决定。

文件名 路径 核心作用 加载触发逻辑(源码)
init.rc / 系统主脚本 parser.ParseConfig("/init.rc")(main函数直接调用)
init.{ro.hardware}.rc / 硬件适配脚本 读取ro.hardware属性后拼接路径调用ParseConfig()
init.vendor.rc /vendor/etc/init/ 厂商定制脚本 init.rc中通过import /vendor/etc/init/*.rc触发

完整流程

Init对RC文件的处理分为解析阶段执行阶段,每个阶段的核心逻辑均对应明确的源码实现,以下按步骤拆解。

阶段1:启动初期------初始化解析环境

Init进程启动后,先完成日志、文件系统、解析器的初始化,为RC文件解析做准备,关键源码如下:

cpp 复制代码
// system/core/init/main.cpp(初始化关键步骤)
void InitializeEnvironment() {
    // 1. 初始化日志:开启内核日志(klog)和Init进程日志(main_log)
    log_init(); 
    // 2. 挂载必要文件系统:/sys(设备信息)、/dev(设备节点)、/proc(进程信息)
    if (mount("sysfs", "/sys", "sysfs", 0, NULL) == -1) {
        PLOG(ERROR) << "mount sysfs failed";
    }
    // 3. 初始化SELinux(安全策略),后续RC文件执行需权限校验
    selinux_init_all();
}

// 初始化解析器:加载RC文件语法规则(识别service、on等关键字)
Parser::Parser() {
    // 注册"service"关键字的解析函数:ParseService
    AddSectionParser("service", std::make_unique<ServiceParser>());
    // 注册"on"关键字的解析函数:ParseAction
    AddSectionParser("on", std::make_unique<ActionParser>());
    // 注册"import"关键字的处理函数:ParseImport
    AddLineParser(std::make_unique<ImportParser>());
}

源码注释

  • log_init():日志输出到/dev/kmsg(内核日志)和logcat,后续解析错误可通过logcat -s init查看;
  • Parser类:通过"关键字-解析函数"映射,确保不同RC指令被正确处理(如service对应ServiceParser)。

阶段2:解析阶段------加载并解析RC文件

解析器初始化后,Init按"主文件→硬件文件→厂商文件"的顺序加载RC文件,核心源码在parser.cpp中:

2.1 加载主文件与import指令
cpp 复制代码
// system/core/init/parser.cpp
bool Parser::ParseConfig(const std::string& path) {
    // 1. 打开RC文件,若不存在直接返回错误(如厂商文件可选)
    std::unique_ptr<File> file = OpenFile(path);
    if (!file) {
        PLOG(ERROR) << "Could not open " << path;
        return false;
    }
    // 2. 逐行解析文件内容
    std::string line;
    while (file->ReadLine(&line)) {
        // 跳过注释行(#开头)和空行
        if (line.empty() || line[0] == '#') continue;
        // 3. 处理import指令:加载其他RC文件(如厂商脚本)
        if (ParseImport(line, path)) continue;
        // 4. 处理section指令(service/on)或普通指令(setprop/chmod)
        ParseLine(line, path, file->LineNumber());
    }
    return true;
}

// 处理import指令:递归加载指定路径的RC文件
bool Parser::ParseImport(const std::string& line, const std::string& path) {
    std::vector<std::string> args = Split(line, ' ');
    if (args[0] != "import") return false;
    // 拼接import路径(如import /vendor/etc/init/*.rc)
    std::string import_path = ExpandArgs(args[1], path);
    // 加载该路径下所有RC文件
    for (const auto& file : Glob(import_path)) {
        ParseConfig(file);
    }
    return true;
}
2.2 解析Service与Action指令

当解析到serviceon指令时,会调用对应的解析函数,将配置信息存入全局管理类(ServiceList/ActionManager):

cpp 复制代码
// 解析service指令:如"service zygote /system/bin/app_process64 ..."
// system/core/init/service_parser.cpp
bool ServiceParser::ParseSection(const std::vector<std::string>& args, const std::string& filename, int line) {
    // 1. 提取service核心参数:服务名(args[1])、可执行路径(args[2])
    std::string name = args[1];
    std::string path = args[2];
    // 2. 创建Service对象,设置启动参数(args[3...])
    Service* service = new Service(name, path, args.slice(3));
    // 3. 解析service属性(如oneshot/respawn)
    for (size_t i = 3; i < args.size(); ++i) {
        if (args[i] == "oneshot") {
            service->flags |= SVC_ONESHOT; // 仅启动一次
        } else if (args[i] == "respawn") {
            service->flags |= SVC_RESPAWN; // 崩溃后自动重启
        }
    }
    // 4. 将service存入全局ServiceList(供后续启动与监控)
    ServiceList::GetInstance().AddService(service);
    return true;
}

// 解析on指令:如"on early-init mkdir /data 0755 root root"
// system/core/init/action_parser.cpp
bool ActionParser::ParseSection(const std::vector<std::string>& args, const std::string& filename, int line) {
    // 1. 提取触发条件(如"early-init")
    std::string trigger = args[1];
    // 2. 创建Action对象,绑定触发条件
    std::unique_ptr<Action> action = std::make_unique<Action>(trigger);
    // 3. 后续行的命令(如mkdir)加入Action的命令队列
    action->AddCommand(std::make_unique<Command>(...));
    // 4. 将Action存入全局ActionManager(供后续触发执行)
    ActionManager::GetInstance().AddAction(std::move(action));
    return true;
}

源码注释

  • ServiceList:全局单例,存储所有服务,后续通过StartService()启动服务;
  • ActionManager:全局单例,存储所有动作,通过QueueEventTrigger()触发指定动作(如"early-init")。

阶段3:执行阶段------按"触发条件"执行动作与服务

解析完成后,Init通过ActionManager触发不同阶段的动作,核心源码在action_manager.cpp中:

cpp 复制代码
// system/core/init/action_manager.cpp
void ActionManager::QueueEventTrigger(const std::string& trigger) {
    // 1. 查找所有触发条件为trigger的Action(如"early-init"对应的所有动作)
    auto actions = actions_by_trigger_[trigger];
    // 2. 将Action加入执行队列
    for (auto& action : actions) {
        if (!action->HasExecuted()) {
            action_queue_.push_back(action);
        }
    }
    // 3. 执行队列中的Action(同步执行)
    ExecuteAllActions();
}

// 执行Action中的所有命令(如mkdir、start service)
void ActionManager::ExecuteAllActions() {
    while (!action_queue_.empty()) {
        auto action = action_queue_.front();
        action_queue_.pop_front();
        // 执行Action中的每个Command
        for (const auto& command : action->Commands()) {
            // 命令执行逻辑:如"start zygote"对应启动zygote服务
            command->Execute();
        }
        // 标记Action已执行,避免重复触发
        action->SetExecuted(true);
    }
}

关键阶段触发逻辑(对应源码调用):

  1. early-init阶段main()函数中调用am.QueueEventTrigger("early-init"),执行目录创建、权限设置;
  2. init阶段early-init执行完成后,通过queue_event_trigger("init")触发,启动ueventdservicemanager
  3. late-init阶段init阶段完成且/data挂载后,触发late-init,启动zygotesurfaceflinger

阶段4:监控阶段------服务重启与RC重载

系统启动后,Init通过事件循环监控服务状态,核心源码在main()函数的EventLoop()中:

cpp 复制代码
// system/core/init/main.cpp
int ActionManager::EventLoop() {
    while (true) {
        // 1. 监控子进程(服务)状态:若服务崩溃(exit code非0),检查是否需重启
        CheckChildProcesses();
        // 2. 处理外部指令(如initctl reload,触发RC文件重载)
        HandleSignal();
        // 3. 等待事件(如服务退出信号、外部指令),避免空循环占用CPU
        epoll_wait(epoll_fd_, events, EPOLL_MAX_EVENTS, -1);
    }
}

// 检查服务状态,重启配置了respawn的服务
void CheckChildProcesses() {
    pid_t pid = waitpid(-1, &status, WNOHANG);
    if (pid <= 0) return;
    // 查找PID对应的服务
    Service* service = ServiceList::GetInstance().FindServiceByPid(pid);
    if (service && (service->flags & SVC_RESPAWN)) {
        // 重启服务(如zygote崩溃后自动重启)
        service->Start();
    }
}

源码层面的可靠性设计

Init通过以下源码逻辑解决"配置冲突""服务依赖"等问题,确保RC文件加载的灵活性与稳定性。

1. 配置优先级:后加载文件覆盖前序文件

源码中,ServiceList::AddService()会先删除同名服务,再添加新服务,实现"后加载覆盖":

cpp 复制代码
// system/core/init/service_list.cpp
void ServiceList::AddService(Service* service) {
    // 1. 若已存在同名服务,先删除(确保后加载的服务生效)
    auto it = services_.find(service->name());
    if (it != services_.end()) {
        delete it->second;
        services_.erase(it);
    }
    // 2. 添加新服务
    services_[service->name()] = service;
}

2. 服务依赖:after/require关键字的实现

解析service时,会将依赖信息存入service->after_list(),启动服务前检查依赖:

cpp 复制代码
// system/core/init/service.cpp
bool Service::Start() {
    // 1. 检查after依赖:确保所有"after"的服务已启动
    for (const auto& after_name : after_list_) {
        Service* after_service = ServiceList::GetInstance().FindService(after_name);
        if (!after_service || !after_service->IsRunning()) {
            LOG(ERROR) << "Service " << name() << " depends on " << after_name << " which is not running";
            return false;
        }
    }
    // 2. 启动服务(fork子进程执行服务可执行文件)
    pid_t pid = fork();
    if (pid == 0) {
        execv(path_.c_str(), args_.data()); // 执行服务二进制文件
    }
    return true;
}

总结

Init加载RC文件的过程,本质是"源码调用链驱动配置解析与执行"的过程,核心对应关系如下:

  1. main() → 初始化环境 → 调用ParseConfig()加载RC文件;
  2. ParseConfig() → 逐行解析 → 调用ServiceParser/ActionParser存储配置;
  3. QueueEventTrigger() → 触发阶段动作 → 调用ExecuteAllActions()执行命令;
  4. EventLoop() → 监控服务状态 → 调用CheckChildProcesses()重启服务。

掌握这一调用链,即可通过日志定位RC文件加载问题(如服务未启动可查Service::Start()的错误日志),也能通过修改RC文件或源码实现定制化需求(如新增开机服务)。

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