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.cppsystem/core/init/action_manager.cppsystem/core/init/action_manager.h
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
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
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
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
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
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
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
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
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
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
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 可复现实验
在设备上可用只读方式建立三条时间线:
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. 循环追踪
按下面顺序阅读可以复现本文主线:
- 在
system/core/init/init.cpp找SecondStageMain()的 trigger 入队和while (true)。 - 转到
system/core/init/action_manager.cpp,跟踪事件匹配、current_command_和 oneshot 清理。 - 回到
init.cpp阅读PropWaiterState、PropertyChanged()和HandleProcessActions()。 - 最后阅读
init_test.cpp的时间戳测试,检查“入队”和“首条命令执行”是否被正确区分。
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 的完整实现。后续专题会在各自边界内展开,本文保留的是“主循环如何把已建立的对象组织成可继续运行的调度闭环”。
