【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文件或源码实现定制化需求(如新增开机服务)。

相关推荐
2501_916007472 小时前
手机使用过的痕迹能查到吗?完整查询指南与步骤
android·ios·智能手机·小程序·uni-app·iphone·webview
黄毛火烧雪下3 小时前
React Native (RN)项目在web、Android和IOS上运行
android·前端·react native
下位子3 小时前
『OpenGL学习滤镜相机』- Day7: FBO(帧缓冲对象)
android·opengl
從南走到北3 小时前
JAVA国际版同城外卖跑腿团购到店跑腿多合一APP系统源码支持Android+IOS+H5
android·java·ios·微信小程序·小程序
空白格973 小时前
组件化攻略
android
岸芷漫步3 小时前
android框架层弹出对话框的分析
android
Android疑难杂症3 小时前
鸿蒙Media Kit媒体服务开发快速指南
android·harmonyos·音视频开发
马 孔 多 在下雨3 小时前
Android动画集大成之宗-MotionLayout基础指南
android
用户413079810613 小时前
Android动画集大成之宗-MotionLayout
android