Skip to content

Init主循环

追踪 init 主循环如何协调 rc 动作、属性等待、服务超时、重启和 epoll 事件。

基于android-17.0.0_r1
AndroidinitActionManagerEpollService

Init主循环 ​

本文面向已经读过 SecondStageInit 的读者。前文说明了 SecondStageMain() 如何建立 Epoll、ActionManager、属性服务和服务表;本文只回答一个更窄的问题:这些对象建立之后,PID 1 每一轮到底先做什么、何时睡眠、什么事件能把它唤醒,以及一个动作或服务失败后为什么不会把整个循环等同于失败。

本文不展开 rc 语法、Service::Start() 的 fork/exec 细节、SIGCHLD 的完整回收状态机或 property socket 协议。它们分别属于 rc、服务和信号专题。读完后,读者应能从 init.cpp 的 while (true) 追踪到 ActionManager::ExecuteOneCommand()、HandleProcessActions() 和 Epoll::Wait(),解释等待条件与唤醒来源,并用源码测试和设备只读日志判断“动作未执行”“服务未重启”和“主循环没有醒来”分别落在哪一层。

1. 循环边界 ​

1.1 入口 ​

SecondStageMain() 在完成脚本加载后先入队 init、charger 或 late-init,再入队属性触发器,最后进入循环。下面代码来自 system/core/init/init.cpp 的固定版本;它说明事件只是进入队列,并不表示对应命令已经执行。

相关源码:

  • system/core/init/init.cpp
  • system/core/init/action_manager.cpp
  • system/core/init/action_manager.h
cpp
am.QueueEventTrigger("init");
am.QueueBuiltinAction(SetCopyRollbackLogsAction, "CopyRollbackLogs");

if (bootmode == BootMode::CHARGER_MODE) {
    am.QueueEventTrigger("charger");
} else {
    am.QueueEventTrigger("late-init");
}

am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");

ActionManager 拥有事件队列和已解析的 Action 列表;主线程拥有消费节奏。这个所有权分工解释了一个常见误判:QueueEventTrigger("late-init") 返回成功,只能证明事件进入队列,不能证明 on late-init 下的 class_start 已经运行。

1.2 一轮 ​

主循环的顺序可以压缩为四个阶段:处理待决 shutdown,尝试执行一个动作命令,计算服务时间点,等待 fd 或 timeout。等待返回后,再处理一条控制消息和 USB controller 状态。

注意 Epoll::Wait() 不是循环的起点,而是本轮把已经可执行的工作检查完之后的阻塞点。若本轮发现仍有动作,next_action_time 被设为当前时间,下一次 epoll_wait 的 timeout 为零或极短;若没有动作且没有服务截止时间,才可能无限等待 fd。

2. 动作队列 ​

2.1 事件匹配 ​

ActionManager::ExecuteOneCommand() 先持有 event_queue_lock_,从队首事件开始匹配所有 Action。Android 17 的队列元素不是单一字符串,而是 EventTrigger、PropertyChange 和 BuiltinAction 的 variant。匹配成功的 action 指针进入 current_executing_actions_,事件随后出队。

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

cpp
void ActionManager::ExecuteOneCommand() {
    std::vector<EventTrigger> triggered_events;
    {
        auto lock = std::lock_guard{event_queue_lock_};
        triggered_events.reserve(event_queue_.size());
        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());
                }
            }
            if (EventTrigger* trigger = std::get_if<EventTrigger>(&event_queue_.front())) {
                triggered_events.emplace_back(std::move(*trigger));
            }
            event_queue_.pop();
        }
    }

锁保护的是队列和匹配过程,不保护真正的 builtin 执行。源码随后在锁外调用 action->ExecuteOneCommand(current_command_)。这是必要的不变量:某个 builtin 可能改属性并再次入队,如果把执行包在 event_queue_lock_ 内,属性线程会等待锁,而主线程又等待 builtin 返回,形成死锁风险。

2.2 单命令 ​

匹配到 action 后,每轮只推进 current_command_ 指向的一条命令。命令完成后递增索引;最后一条命令完成时,才从 current_executing_actions_ 移除 action。oneshot action 还会从永久的 actions_ 列表删除。

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

cpp
auto action = current_executing_actions_.front();

if (current_command_ == 0) {
    LOG(INFO) << "processing action (" << action->BuildTriggersString()
              << ") from (" << action->filename() << ":" << action->line() << ")";
}

action->ExecuteOneCommand(current_command_);

++current_command_;
if (current_command_ == action->NumCommands()) {
    current_executing_actions_.pop();
    current_command_ = 0;
    if (action->oneshot()) {
        auto eraser = [&action](std::unique_ptr<Action>& a) { return a.get() == action; };
        actions_.erase(std::remove_if(actions_.begin(), actions_.end(), eraser), actions_.end());
    }
}

因此“一个 trigger 对应一个 action”并不成立:同一个事件可以匹配多个 action;而一个 action 也可能跨越多轮才执行完。HasMoreCommands() 在锁内同时检查当前 action 队列和事件队列,主循环用它决定是否立即再次运行。

2.3 时间戳 ​

当测试开关 enables_init_event_timestamp_ 打开时,触发事件的 ro.boottime.event.* 属性在第一条命令执行前写入。测试代码明确验证了嵌套 trigger event2 的时间点晚于触发它的命令,而不是事件刚入队的时间点。

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

cpp
if (enables_init_event_timestamp_) {
    for (auto& event : triggered_events) {
        std::string prop = property_prefix_for_testing_ + "ro.boottime.event." + event;
        if (IsLegalPropertyName(prop) && base::GetProperty(prop, "").empty()) {
            base::SetProperty(prop, std::to_string(
                    base::boot_clock::now().time_since_epoch().count()));
        }
    }
}

这给出一个可验证的生效时机:日志中的 action 入队时间不能替代第一条命令的执行时间。排查启动变慢时,应把 ro.boottime.event.<name> 与 action 日志和服务启动时间放在同一时间线上。

3. 等待条件 ​

3.1 属性等待 ​

start wait_for_property 通过 PropWaiterState::StartWaiting() 设置目标名称、值和计时器。如果目标属性已经是期望值,不进入等待;否则主循环的 MightBeWaiting() 返回真,普通 action 被暂缓。

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

cpp
bool StartWaiting(const char* name, const char* value) {
    auto lock = std::lock_guard{lock_};
    if (waiting_for_prop_) return false;
    if (GetProperty(name, "") != value) {
        wait_prop_name_ = name;
        wait_prop_value_ = value;
        waiting_for_prop_.reset(new Timer());
    }
    return true;
}

属性线程收到匹配值后调用 CheckAndResetWait(),清除等待状态并 WakeMainInitThread()。这里的消费者不是 property callback 本身,而是被 eventfd 唤醒后的 init 主线程;callback 只修改等待状态并发出通知。

3.2 临时服务 ​

Service::ExecStart() 启动 exec 类型的一次性服务后,把静态标志 is_exec_service_running_ 置为真。主循环因此跳过普通 action,直到子进程回收路径清除该标志。这样做是为了让 rc 的 exec_start 语义保持同步:调用方依赖的副作用完成前,不让后续普通 action 越过它。

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

cpp
flags_ |= SVC_EXEC;
is_exec_service_running_ = true;
LOG(INFO) << "SVC_EXEC service '" << name_ << "' pid " << pid_ << " ... waiting...";

它与 property waiter 的共同点是“抑制普通 action”,不同点是等待对象:前者等待属性值,后者等待子进程结束。不要把 SVC_EXEC 当成所有服务都同步;普通 service 启动返回后并不会设置这个全局抑制标志。

3.3 关闭状态 ​

sys.powerctl 的处理有特殊路径。PropertyChanged() 不把关机命令普通排队,而是调用 trigger_shutdown() 设置状态;主循环下一轮最先检查 shutdown_state.CheckShutdown(),然后调用 HandlePowerctlMessage()。这样即使 action 队列或 exec service 正在等待,关机仍不会无限排在队尾。

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

cpp
if (name == "sys.powerctl") {
    trigger_shutdown(value);
}

这是一条取消/中断边界:关机不是撤销当前 builtin,而是让下一轮在动作消费前进入 shutdown 处理。正在运行的 C++ builtin 是否可被中断,取决于它自己的实现;主循环不会强行抢占它。

4. 服务时间 ​

4.1 超时 ​

HandleProcessActions() 遍历 ServiceList。对带 timeout 的运行中服务,它比较 time_started() + timeout_period();超时则调用 Service::Timeout(),尚未到期则把时间点返回给主循环。

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

cpp
for (const auto& s : ServiceList::GetInstance()) {
    if ((s->flags() & SVC_RUNNING) && s->timeout_period()) {
        auto timeout_time = s->time_started() + *s->timeout_period();
        if (boot_clock::now() > timeout_time) {
            s->Timeout();
        } else if (!next_process_action_time || timeout_time < *next_process_action_time) {
            next_process_action_time = timeout_time;
        }
    }

返回时间点的意义是让 epoll 睡到最早截止时间,而不是每毫秒轮询。服务超时处理和 action 执行由同一个 init 主线程串行完成,因此超时日志出现的时刻还受当前 builtin 执行时间影响。

4.2 重启 ​

带 SVC_RESTARTING 的服务使用 time_started() + restart_period() 计算下一次启动时间。到期时调用 s->Start();失败只记录错误并继续主循环,不把一次重启失败升级成 init 进程退出。

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

cpp
if (!(s->flags() & SVC_RESTARTING)) continue;
auto restart_time = s->time_started() + s->restart_period();
if (boot_clock::now() > restart_time) {
    if (auto result = s->Start(); !result.ok()) {
        LOG(ERROR) << "Could not restart process '" << s->name()
                   << "': " << result.error();
    }
} else if (!next_process_action_time || restart_time < *next_process_action_time) {
    next_process_action_time = restart_time;
}

重启策略的所有者是 Service,时间调度的所有者是主循环。后者不保存服务状态副本,只读取 ServiceList 当前状态并计算下一次唤醒点。

5. 唤醒边界 ​

主循环只把计算出的 deadline 交给 Epoll::Wait(),并在返回后继续处理 control message。Epoll 如何保存 fd-to-handler 映射、为什么子进程回收回调优先、handler 自注销为何延迟清理,属于下一篇 Epoll事件循环 的对象生命周期主线。

本文只保留调度器所依赖的契约:fd 就绪或 deadline 到期会让 Wait() 返回;返回并不等于某个业务动作成功,只表示主线程获得了再次检查动作队列、服务时间点和控制消息的机会。

6. 主循环图 ​

下面把四类状态放在同一张图中:动作队列决定“有没有立即工作”,服务时间决定“何时必须醒来”,属性/exec 决定“是否暂缓普通动作”,epoll fd 决定“外部事件如何唤醒”。

状态图不是说 init 有一个名为 Ready 的枚举;它是把源码中的条件组合成阅读模型。真实状态由 ActionManager 队列、PropWaiterState、Service::is_exec_service_running_ 和 ShutdownState 分散持有。

7. 控制消息 ​

7.1 一次一条 ​

属性服务收到 ctl.start、ctl.stop 或 ctl.restart 后,把消息放进 pending_control_messages 并通过 WakeMainInitThread() 写 eventfd。主循环在 Epoll::Wait() 返回后调用 HandleControlMessages(),每轮只弹出一条。

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

cpp
static void HandleControlMessages() {
    auto lock = std::unique_lock{pending_control_messages_lock};
    if (!pending_control_messages.empty()) {
        auto control_message = pending_control_messages.front();
        pending_control_messages.pop();
        lock.unlock();
        bool success = HandleControlMessage(control_message.message,
                                            control_message.name,
                                            control_message.pid);
        if (control_message.fd != -1) {
            uint32_t response = success ? PROP_SUCCESS : PROP_ERROR_HANDLE_CONTROL_MESSAGE;
            TEMP_FAILURE_RETRY(send(control_message.fd, &response, sizeof(response), 0));
            close(control_message.fd);
        }
        lock.lock();
    }
    if (!pending_control_messages.empty()) WakeMainInitThread();
}

一次只处理一条是防饥饿策略:大量 ctl.* 请求不能在一个循环尾部占满主线程,让动作队列和其他 fd 长时间得不到机会。队列超过 100 条时,QueueControlMessage() 直接丢弃新消息并记录错误;这不是服务不存在,而是控制消息入口的容量保护。

7.2 属性唤醒 ​

PropertyChanged() 先处理 sys.powerctl 和 sys.shutdown.requested,再在启用 property trigger 时调用 QueuePropertyChange(),最后检查 property waiter。若队列发生变化或等待条件满足,都会唤醒主线程。

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

cpp
if (property_triggers_enabled) {
    ActionManager::GetInstance().QueuePropertyChange(name, value);
    WakeMainInitThread();
}
prop_waiter_state.CheckAndResetWait(name, value);

因此 property 事件有两个消费者:ActionManager 消费匹配的 on property: action,PropWaiterState 消费精确的名称和值匹配。二者同时存在时,前者进入动作队列,后者只负责解除普通 action 的抑制。

8. 异常边界 ​

8.1 启动失败 ​

Epoll::Open()、RegisterHandler() 等第二阶段设施初始化失败会在进入主循环前 LOG(FATAL);这类错误意味着 init 尚未具备可靠的事件分发能力,继续运行没有安全语义。

8.2 动作失败 ​

命令执行返回错误由 Action::ExecuteCommand() 记录,通常只影响该命令和其所在 action;主循环仍可在下一轮继续消费其他 action。不要把日志中的 Command ... failed 直接等同为 PID 1 退出。

8.3 事件失败 ​

Epoll::Wait() 返回错误时主循环记录错误并继续;但如果 fd 被错误注销、handler 查找不到或 eventfd 唤醒丢失,后果表现为某一类事件不再被消费。应分别检查注册表、内核 fd 和 producer 的 WakeMainInitThread(),而不是只看 action 日志。

8.4 恢复清理 ​

服务超时调用 Service::Timeout(),重启调用 Service::Start();exec 服务的结束由子进程回收路径清除运行标志;注销 fd 延迟到当前 handler 返回后清理。它们都属于“状态恢复后继续循环”,不是重新创建整个 Epoll 或 ActionManager。

9. 调度测试 ​

9.1 时间断言 ​

init_test.cpp 的 EventTimeStampIsRecordedOnExecution 构造三个 action:event1 触发 event2、event3,并记录命令执行时间。断言要求 event2 的 boottime 属性晚于 event1 中的 trigger done,event3 又晚于 event2 完成。这证明时间戳在第一条命令执行边界设置,而不是在队列入队时设置;它不证明真实设备上所有 action 都开启该测试 flag。

9.2 可复现实验 ​

在设备上可用只读方式建立三条时间线:

sh
adb shell getprop ro.boottime.init.cold_boot_wait
adb shell getprop | grep '^\[ro.boottime.event\.'
adb shell logcat -b all -d -s init:I

把 processing action、ro.boottime.event.* 和 service ... started 按时间排序,可以区分“事件已入队但尚未消费”“动作执行后服务启动失败”和“主循环没有被唤醒”。这些命令只能观察公开属性和日志,不能单独证明 epoll_wait 的内核返回值或所有 handler 注册成功。

10. 循环追踪 ​

按下面顺序阅读可以复现本文主线:

  1. 在 system/core/init/init.cpp 找 SecondStageMain() 的 trigger 入队和 while (true)。
  2. 转到 system/core/init/action_manager.cpp,跟踪事件匹配、current_command_ 和 oneshot 清理。
  3. 回到 init.cpp 阅读 PropWaiterState、PropertyChanged() 和 HandleProcessActions()。
  4. 最后阅读 init_test.cpp 的时间戳测试,检查“入队”和“首条命令执行”是否被正确区分。
sh
rg -n "while \(true\)|ExecuteOneCommand|HandleProcessActions|epoll\.Wait" \
  system/core/init/init.cpp system/core/init/action_manager.cpp

当看到某个 on property: 没有执行时,先判断 property trigger 是否已启用、事件是否唤醒了 eventfd、动作是否被 prop_waiter_state 或 SVC_EXEC 暂缓,再检查 builtin 本身;这条顺序比从最后一条错误日志倒推更接近真实控制流。

本文没有覆盖 Parser 如何生成 Action、ReapOneProcess() 如何更新服务状态、exec_start 的 fork/exec 和 shutdown 的完整实现。后续专题会在各自边界内展开,本文保留的是“主循环如何把已建立的对象组织成可继续运行的调度闭环”。