Skip to content

Action解析

追踪 on 段从触发器解析、命令绑定到 ActionManager 消费的完整路径。

基于android-17.0.0_r1
AndroidinitActionParserActionManager

Action解析 ​

本文面向已经读过 rc语法 和 Init主循环 的读者。前文解释了 tokenizer 如何把一行切成参数;本文把范围收窄到 on 段:ActionParser 如何建立事件和属性条件,Action 如何保存命令,ActionManager 如何在事件队列中找到消费者。

本文不重复 tokenizer,也不展开每个 builtin 的实现。重点是一个可追踪问题:给定 on boot && property:ro.hardware=*,哪些字段在解析时被拥有,什么时候才匹配,命令失败后 Action 是否消失,以及新增的 rc 文件为什么可能错过当前事件。

1. 对象分工 ​

1.1 三个所有者 ​

对象解析阶段职责运行阶段职责
ActionParser读取 on 首行和 body,创建/提交 Action不执行命令
Action保存 event、property、命令和源位置判断事件、逐条调用 Command
ActionManager接收已完成的 Action保存 Action 列表、消费事件队列

ActionParser 自己只持有当前段的 action_;ActionManager 才拥有提交后的长期列表。这个边界解释了为什么 EndSection() 是关键生效点:在它执行前,后续事件看不到这条 Action。

1.2 数据形状 ​

源码文件:system/core/init/action.h

相关函数/类型:Action

cpp
class Action {
  private:
    std::map<std::string, std::string> property_triggers_;
    std::string event_trigger_;
    std::vector<Command> commands_;
    bool oneshot_;
    Subcontext* subcontext_;
    std::string filename_;
    int line_;
};

一个 Action 最多有一个 event trigger,可以有多个不同名称的 property trigger;命令顺序由 commands_ 保留。filename_ 和 line_ 不是装饰信息,执行日志和故障定位依赖它们。

2. 首行解析 ​

2.1 触发列表 ​

ActionParser::ParseSection() 收到的 args 包含首 token on,因此先从下标 1 开始复制 trigger。空 trigger 直接失败;解析成功后才创建 Action。

源码文件:system/core/init/action_parser.cpp

cpp
Result<void> ActionParser::ParseSection(
        std::vector<std::string>&& args,
        const std::string& filename, int line) {
    std::vector<std::string> triggers(args.begin() + 1, args.end());
    if (triggers.size() < 1) {
        return Error() << "Actions must have a trigger";
    }

    std::string event_trigger;
    std::map<std::string, std::string> property_triggers;
    if (auto result = ParseTriggers(
                triggers, action_subcontext, &event_trigger, &property_triggers);
        !result.ok()) {
        return Error() << "ParseTriggers() failed: " << result.error();
    }

    auto action = std::make_unique<Action>(
            false, action_subcontext, filename, line,
            event_trigger, property_triggers);
    action_ = std::move(action);
    return {};
}

旧稿把 Action 先构造、再“设置 trigger”描述成可变对象;Android 17 的构造函数直接接收解析结果,避免出现一个已经暴露但触发条件还未完成的中间 Action。

2.2 事件名 ​

ParseTriggers() 将不以 property: 开头的 token 视为 event trigger。event trigger 只能出现一个,并在 Android R 及以后通过 ValidateEventTrigger() 限制为字母数字、下划线和连字符。

源码文件:system/core/init/action_parser.cpp

cpp
if (!event_trigger->empty()) {
    return Error() << "multiple event triggers are not allowed";
}
if (auto result = ValidateEventTrigger(args[i]); !result.ok()) {
    return result;
}
*event_trigger = args[i];

因此 on boot && early-init 不是两个事件的“同时触发”,而是解析错误。on boot: 也会因冒号不符合字符规则而增加 parse error;init_test.cpp 的 WrongEventTrigger 对此有反向断言。

2.3 连接符 ​

触发器 token 按奇偶位置解析:偶数位置必须是 trigger,奇数位置必须是 &&。其他连接符不会被当作逻辑运算符,也没有 OR 语义。

源码文件:system/core/init/action_parser.cpp

cpp
for (std::size_t i = 0; i < args.size(); ++i) {
    if (args[i].empty()) {
        return Error() << "empty trigger is not valid";
    }
    if (i % 2) {
        if (args[i] != "&&") {
            return Error() << "&& is the only symbol allowed to concatenate actions";
        }
        continue;
    }
    // 处理 property 或 event
}

3. 属性条件 ​

3.1 键值分割 ​

ParsePropertyTrigger() 去掉 property: 前缀后寻找第一个 =。左侧是 property 名,右侧是完整 value;缺少 = 的 token 失败。value 中后续的 = 不会被当成新的分隔符。

源码文件:system/core/init/action_parser.cpp

cpp
std::string prop_name(trigger.substr(prop_str.length()));
size_t equal_pos = prop_name.find('=');
if (equal_pos == std::string::npos) {
    return Error() << "property trigger found without matching '='";
}
std::string prop_value(prop_name.substr(equal_pos + 1));
prop_name.erase(equal_pos);

3.2 重复键 ​

property 条件保存于 std::map。使用 emplace() 的插入结果拒绝同一个 property 的第二次出现;这比后写覆盖前写更严格,也避免 on property:x=1 && property:x=2 产生无法解释的条件。

源码文件:system/core/init/action_parser.cpp

cpp
if (auto [it, inserted] = property_triggers->emplace(prop_name, prop_value);
    !inserted) {
    return Error() << "multiple property triggers found for same property";
}

3.3 读取边界 ​

当 actionable-compatible property 开关启用且 Action 运行在 subcontext 中时,IsActionableProperty() 允许一组 partner 前缀,其他 property 必须通过 CanReadProperty()。这是 parse-time 的安全检查,不是事件发生时才检查的权限。

源码文件:system/core/init/action_parser.cpp

相关函数/类型:IsActionableProperty

cpp
static constexpr const char* kPartnerPrefixes[] = {
    "init.svc.vendor.", "ro.vendor.", "persist.vendor.",
    "vendor.", "init.svc.odm.", "ro.odm.",
    "persist.odm.", "odm.", "ro.boot.",
};

4. 段上下文 ​

4.1 Subcontext选择 ​

ActionParser 收到的 Subcontext* 并不会无条件挂到所有 Action。只有源文件路径匹配该 subcontext 时,才把它保存为 action_subcontext;否则使用 null,命令在 init context 运行。

源码文件:system/core/init/action_parser.cpp

cpp
Subcontext* action_subcontext = nullptr;
if (subcontext_ && subcontext_->PathMatchesSubcontext(filename)) {
    action_subcontext = subcontext_;
}

4.2 APEX限制 ​

源码还限制 /apex/ 文件的 on 段:如果文件不属于可识别的 Vendor APEX subcontext,就直接拒绝。原因不是路径格式,而是 mainline APEX 不应依赖不稳定的 init 事件和 property。

源码文件:system/core/init/action_parser.cpp

cpp
if (StartsWith(filename, "/apex/") && !action_subcontext) {
    return Error() << "ParseSection() failed: 'on' is supported for only Vendor APEXes.";
}

4.3 命令执行者 ​

subcontext 存在时,Command::InvokeFunc() 根据 run_in_subcontext 决定两条路径:需要隔离执行的 builtin 交给 Subcontext::Execute();普通 builtin 先由 subcontext 展开参数,再在对应 context 调用函数。

源码文件:system/core/init/action.cpp

cpp
if (subcontext) {
    if (execute_in_subcontext_) {
        return subcontext->Execute(args_);
    }
    auto expanded_args = subcontext->ExpandArgs(args_);
    if (!expanded_args.ok()) return expanded_args.error();
    return RunBuiltinFunction(func_, *expanded_args, subcontext->context());
}
return RunBuiltinFunction(func_, args_, kInitContext);

这一步发生在 Action 已匹配并开始执行之后,不是 ActionParser 的解析动作。把 subcontext 的执行隔离误写成“解析器创建了一个新进程”会混淆两个生命周期。

5. 命令绑定 ​

5.1 关键字查找 ​

ParseLineSection() 将 body 行交给 Action::AddCommand()。Action 的静态 function_map_ 必须已经由 init 设置;未设置时返回 no function map available。设置后,BuiltinFunctionMap::Find() 同时验证命令名和参数数量。

源码文件:system/core/init/action.cpp

cpp
Result<void> Action::AddCommand(
        std::vector<std::string>&& args, int line) {
    if (!function_map_) {
        return Error() << "no function map available";
    }
    auto map_result = function_map_->Find(args);
    if (!map_result.ok()) {
        return Error() << map_result.error();
    }
    commands_.emplace_back(
            map_result->function, map_result->run_in_subcontext,
            std::move(args), line);
    return {};
}

5.2 参数延迟 ​

解析时命令参数仍保存在 Command::args_;${property} 的普通 builtin 参数在 RunBuiltinFunction() 执行时展开。这样同一条 Action 可以在 property 改变后使用新值,而不是把解析瞬间的 property 快照写死。

源码文件:system/core/init/action.cpp

cpp
for (std::size_t i = 1; i < args.size(); ++i) {
    auto expanded_arg = ExpandProps(args[i]);
    if (!expanded_arg.ok()) return expanded_arg.error();
    builtin_arguments.args[i] = std::move(*expanded_arg);
}
return function(builtin_arguments);

5.3 无效命令 ​

命令关键字未知、参数个数不符或属性展开失败,都会让该命令返回错误。解析阶段已加入的其他 Action 不会因为这条命令失败而回滚;执行阶段的日志包含命令文本、Action trigger、文件和行号,消费者可以据此定位源头。

源码文件:system/core/init/action.cpp

cpp
LOG(INFO) << "Command '" << cmd_str << "' action=" << trigger_name
          << " (" << filename_ << ":" << command.line() << ") took "
          << duration.count() << "ms and "
          << (result.ok() ? "succeeded" : "failed: " + result.error().message());

6. 段提交 ​

6.1 EndSection ​

只有当前 Action 有至少一条命令时,EndSection() 才调用 ActionManager::AddAction()。空的 on boot 段不会进入 actions_,因此不会在事件匹配中留下一个“什么也不做”的对象。

源码文件:system/core/init/action_parser.cpp

cpp
Result<void> ActionParser::EndSection() {
    if (action_ && action_->NumCommands() > 0) {
        action_manager_->AddAction(std::move(action_));
    }
    return {};
}

6.2 EOF提交 ​

解析器在 EOF 也会调用 EndSection(),所以文件最后一个 on 段不需要额外空行才能提交;这是 Parser::ParseData() 追加换行的直接结果。若 body 命令全部无效,NumCommands() 仍为零,该 Action 不会提交。

6.3 顺序保留 ​

ActionManager::AddAction() 只是将 unique pointer 追加到 actions_。同一事件匹配多个 Action 时,ExecuteOneCommand() 按 actions_ 的顺序把它们放入 current_executing_actions_,因此解析文件/导入顺序最终会影响同一 trigger 的执行顺序。

源码文件:system/core/init/action_manager.cpp

cpp
void ActionManager::AddAction(std::unique_ptr<Action> action) {
    actions_.emplace_back(std::move(action));
}

下面的时序图把“段内暂存”和“事件消费”分成两个阶段。EndSection 是二者之间的所有权转移点;事件入队时,Parser 已经不再参与。

7. 匹配执行 ​

7.1 事件匹配 ​

事件触发调用 Action::CheckEvent(const EventTrigger&)。它要求 event 字符串相等,并且所有 property 条件都满足当前属性值。没有 event 的纯 property Action 不会被事件分支匹配。

源码文件:system/core/init/action.cpp

cpp
bool Action::CheckEvent(const EventTrigger& event_trigger) const {
    return event_trigger == event_trigger_ && CheckPropertyTriggers();
}

7.2 属性匹配 ​

属性变化调用另一重载,要求 Action 没有 event trigger,并把刚变化的 (name, value) 作为快速检查项;其他 property 仍从 property service 读取。* 只对非空属性值匹配,空值不会满足通配条件。

源码文件:system/core/init/action.cpp

cpp
bool Action::CheckEvent(const PropertyChange& property_change) const {
    const auto& [name, value] = property_change;
    return event_trigger_.empty() && CheckPropertyTriggers(name, value);
}

重载只负责限定候选 Action,真正的多属性合取发生在 CheckPropertyTriggers()。当前变化的键值直接使用事件携带值,其余键则读取当前 property 状态。

源码文件:system/core/init/action.cpp

cpp
for (const auto& [trigger_name, trigger_value] : property_triggers_) {
    if (trigger_name != name) {
        std::string prop_value = android::base::GetProperty(trigger_name, "");
        if (trigger_value == "*" && !prop_value.empty()) continue;
        if (trigger_value != prop_value) return false;
    }
}
return true;

7.3 运行循环 ​

ActionManager::ExecuteOneCommand() 先在锁内消费事件队列,按 actions_ 顺序将匹配 Action 放入执行队列;真正的 InvokeFunc() 在释放锁后发生。这样 builtin 可以设置 property 或导入 rc,而不会在持有 event_queue_lock_ 时递归制造死锁。

源码文件:system/core/init/action_manager.cpp

cpp
while (current_executing_actions_.empty() && !event_queue_.empty()) {
    for (const auto& action : actions_) {
        if (std::visit([&action](const auto& event) {
                return action->CheckEvent(event);
            }, event_queue_.front())) {
            current_executing_actions_.emplace(action.get());
        }
    }
    event_queue_.pop();
}

8. 失败与恢复 ​

8.1 命令失败 ​

命令失败由 Command::InvokeFunc() 返回 Result<void>,Action::ExecuteCommand() 记录失败并继续由 ActionManager 推进下一个命令。源码没有因为一条 builtin 失败就自动撤销之前命令的事务语义;是否停止或重试取决于具体 builtin。

8.2 动态导入 ​

如果一个 Action 执行 import,新 Action 会在执行过程中加入 ActionManager,但不会 retroactively 匹配已经从 event queue 取出的当前事件。LazilyLoadedActionsCantBeTriggeredByTheSameTrigger 测试输入一个 on boot 加载另一个 on boot,断言后者本轮不执行;下一次显式 trigger 才能消费它。

8.3 oneshot清理 ​

普通 rc Action 的 oneshot_ 为 false,会留在 actions_ 供未来事件再次匹配。内置 Action 由 QueueBuiltinAction() 创建为 oneshot;最后一条命令执行完成后,ActionManager 才从 actions_ 移除它。清理发生在消费完成之后,不是在入队时。

源码文件:system/core/init/action_manager.cpp

cpp
if (current_command_ == action->NumCommands()) {
    current_executing_actions_.pop();
    current_command_ = 0;
    if (action->oneshot()) {
        actions_.erase(std::remove_if(actions_.begin(), actions_.end(), eraser),
                       actions_.end());
    }
}

9. 触发测试 ​

9.1 触发测试 ​

SimpleEventTrigger 的输入是一个 on boot 和 pass_test builtin,测试动作是向 ActionManager 入队 boot,断言 callback 被执行。它证明了“解析 → AddAction → 事件匹配 → 命令调用”这条最小正向路径,不证明属性权限或 subcontext。

EventTriggerOrder 再加入两个同名 on boot,其中一个附带 property:ro.hardware=*,断言三条命令按源码注册顺序执行。这证明 property 条件满足时不会改变 actions_ 的相对顺序。

9.2 失败测试 ​

WrongEventTrigger 使用 on boot:,随后断言 parse_error_count() 为 1,证明非法 event 字符在解析阶段被拒绝。这个断言不涉及设备上的 SELinux policy,也不证明所有厂商扩展都会用相同 vendor API level。

9.3 可执行检查 ​

读者可以从一个真实 init.rc 的 on 段开始,依次写出:

text
首行 token
  -> event_trigger / property_triggers
  -> Action::commands_
  -> EndSection / ActionManager::actions_
  -> CheckEvent
  -> Command::InvokeFunc

然后选择一个带 property: 的 Action,分别改变匹配 property 和无关 property,预测哪次 QueuePropertyChange 会进入执行队列。最后检查 processing action 日志中的文件与行号,确认运行消费者仍指向源 rc。

9.4 边界 ​

本文没有把 trigger 解析等同于命令执行,也没有覆盖 ActionManager 全部时间戳、shutdown 清队列和所有 property service 细节;这些是相邻机制的边界。结论只适用于 Android 17 通用 init 源码,设备厂商的 subcontext、APEX 清单和 rc 加载集合仍需回到对应源码核验。