Android 重启流程源码解析:从 sys.powerctl 到内核 reboot
本文基于本地
init.cpp与reboot.cpp(Android R/11 前后的 system/core/init,含平台厂商定制),把sys.powerctl属性到达 init 之后 的完整流程逐一展开。这篇可以配合恢复出厂设置流程一起食用阅读本文你将了解:
PropertyChanged/ShutdownState如何安全地把关机命令从属性线程转交 init 主线程HandlePowerctlMessage()如何解析reboot,xxx命令、写 BCB、重排 action 队列DoReboot()从停服务到reboot()系统调用的完整关机序列(分阶段逐段分析)- 看门狗线程
RebootMonitorThread如何保证"系统一定关得掉"- umount /data 为什么这么难:FUSE、zram、APEX、NFS 一个个拆
- 一份从 logcat / lastkmsg 排查重启问题的关键日志时间线
目录
- 前言:回顾 framework 侧的第 1~7 步
- 命令如何安全地交给 init 主线程(init.cpp)
- HandlePowerctlMessage:解析与排队(reboot.cpp)
- DoReboot:真正的关机序列(reboot.cpp)
- 看门狗:RebootMonitorThread
- umount /data 的那些拦路虎
- 岔路:userspace reboot(可选阅读)
- 全流程时序总结
- 关键日志与调试技巧
- 总结
前言:回顾 framework 侧的第 1~7 步
先把整体链路串一遍(前 5 步在 framework 侧,只做简要回顾):
scss
① PowerManager.reboot() → PMS.reboot() Binder 调用,检查 REBOOT 权限
② ShutdownThread.reboot() 确认对话框 / WakeLock / 进度条
③ ShutdownThread.run() 发 ACTION_SHUTDOWN 广播、
AMS/PM shutdown、关 radio
④ rebootOrShutdown() → PMS.lowLevelReboot(reason)
⑤ SystemProperties.set("sys.powerctl", "reboot," + reason) ← framework 的最后一步!
⑥ init 属性服务 HandlePropertySet() SELinux 权限检查 + 记录
"Received sys.powerctl=..." 日志
⑦ PropertyChanged() → ShutdownState → init 主循环 本篇从第 6 步末尾开始展开
│
▼
HandlePowerctlMessage() → DoReboot() → RebootSystem() → 内核 reboot 系统调用
几个关键背景:
sys.powerctl是一个 "虚"属性 :init 的属性服务(property_service.cpp)收到对它的 set 请求后,并不会把它写进属性存储区,只会走NotifyPropertyChange()通知出去------它本质上是一条发给 init 的命令,而不是一个状态;- 能写这个属性的进程受 SELinux 约束:framework 关机走的是 system_server,
adb reboot走的是 adbd,shell 域是没有权限的; - 属性服务在 init 的属性线程 里处理请求,而关机动作必须由 init 的主线程执行------这就引出了第 6~7 步里最核心的线程交接问题。
一、命令如何安全地交给 init 主线程(init.cpp)
1.1 PropertyChanged:sys.powerctl 特判(init.cpp:397)
属性服务的 NotifyPropertyChange() 最终走到 PropertyChanged():
scss
void PropertyChanged(const std::string& name, const std::string& value) { // init.cpp:397
// If the property is sys.powerctl, we bypass the event queue and immediately handle it.
// This is to ensure that init will always and immediately shutdown/reboot, regardless of
// if there are other pending events to process or if init is waiting on an exec service or
// waiting on a property.
if (name == "sys.powerctl") { // :404
trigger_shutdown(value); // :405
}
// (厂商定制:cdm./csd./vendor.cdm./vendor.csd. 属性 fork 出 sdprop 处理,
// 见第十节,不影响本流程)
...
if (property_triggers_enabled) {
ActionManager::GetInstance().QueuePropertyChange(name, value); // 普通属性:走事件队列
WakeMainInitThread();
}
prop_waiter_state.CheckAndResetWait(name, value);
}
注意注释里的关键词:bypass the event queue(绕过事件队列) 。普通属性变化会被排进 action 队列慢慢处理,但 sys.powerctl 必须立即生效------哪怕 init 正在等待某个属性(wait_for_prop)、正在跑 exec 服务、队列里还堆着一大串 action,也必须优先关机。
trigger_shutdown 是一个函数指针,在 SecondStageMain() 启动时被赋值(init.cpp:964):
c
trigger_shutdown = [](const std::string& command) {
shutdown_state.TriggerShutdown(command); // init.cpp:964
};
1.2 为什么不能直接调 HandlePowerctlMessage?------ShutdownState(init.cpp:237)
c
static class ShutdownState {
public:
void TriggerShutdown(const std::string& command) { // init.cpp:239
// We can't call HandlePowerctlMessage() directly in this function,
// because it modifies the contents of the action queue, which can cause the action queue
// to get into a bad state if this function is called from a command being executed by the
// action queue. Instead we set this flag and ensure that shutdown happens before the next
// command is run in the main init loop.
auto lock = std::lock_guard{shutdown_command_lock_};
shutdown_command_ = command;
do_shutdown_ = true;
WakeMainInitThread();
}
std::optional<std::string> CheckShutdown() { // init.cpp:251
auto lock = std::lock_guard{shutdown_command_lock_};
if (do_shutdown_ && !IsShuttingDown()) {
do_shutdown_ = false;
return shutdown_command_;
}
return {};
}
...
} shutdown_state;
这段注释是整个交接机制的核心,翻译一下:
TriggerShutdown()是在属性线程里被调的;- 而
HandlePowerctlMessage()会清空并重排 action 队列 (ClearQueue/QueueEventTrigger/QueueBuiltinAction); - 如果此刻主线程正在通过 action 队列执行命令(遍历队列的过程中队列被人改了),队列状态会被破坏;
- 所以属性线程只做三件事 :存下命令、置
do_shutdown_标志、WakeMainInitThread()唤醒主线程。真正的关机由主循环在下一条命令执行之前处理。
另外注意 CheckShutdown() 里的 !IsShuttingDown() 防护:一旦进入关机状态(EnterShutdown() 置位 shutting_down,见 2.5 节),再来一条 sys.powerctl 也不会重入。
1.3 WakeMainInitThread:eventfd 唤醒(init.cpp:138)
scss
static int wake_main_thread_fd = -1; // init.cpp:138
static void InstallInitNotifier(Epoll* epoll) {
wake_main_thread_fd = eventfd(0, EFD_CLOEXEC); // :140
auto clear_eventfd = [] {
uint64_t counter;
TEMP_FAILURE_RETRY(read(wake_main_thread_fd, &counter, sizeof(counter)));
};
epoll->RegisterHandler(wake_main_thread_fd, clear_eventfd);
}
static void WakeMainInitThread() { // init.cpp:154
uint64_t counter = 1;
TEMP_FAILURE_RETRY(write(wake_main_thread_fd, &counter, sizeof(counter)));
}
init 主线程平时睡在 epoll 上;属性线程往 eventfd 写一下,主线程立刻被唤醒。注释里还讲了历史:早期用一个阻塞 socket 传属性变化消息,容易被填满导致死锁,后来才改成"锁 + eventfd 唤醒"的方式。
1.4 主循环取命令:CheckShutdown → HandlePowerctlMessage(init.cpp:1141)
init 主循环每轮第一件事就是检查有没有关机命令:
scss
while (true) { // init.cpp:1141
...
auto shutdown_command = shutdown_state.CheckShutdown(); // :1148
if (shutdown_command) {
LOG(INFO) << "Got shutdown_command '" << *shutdown_command
<< "' Calling HandlePowerctlMessage()"; // :1150 排查重启问题的重要日志!
HandlePowerctlMessage(*shutdown_command); // :1152 → reboot.cpp:1049
}
if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
am.ExecuteOneCommand(); // :1156 每轮只执行一条 action
if (am.HasMoreCommands()) {
next_action_time = boot_clock::now();
}
}
if (!IsShuttingDown()) { // :1165 关机期间不再调度服务重启
auto next_process_action_time = HandleProcessActions();
...
}
...
auto epoll_result = epoll.Wait(epoll_timeout); // :1179
if (!IsShuttingDown()) { // :1183 关机期间不再处理 ctl.* 消息
HandleControlMessages();
SetUsbController();
}
}
三个细节值得注意:
CheckShutdown()放在ExecuteOneCommand()之前------保证关机命令"插队"成功,先于队列里任何待执行命令被处理(这正是 1.2 节注释承诺的 "ensure that shutdown happens before the next command is run");HandlePowerctlMessage()本身并不执行关机 ,它只做解析和排队(见第二节),真正的动作在后续多轮循环的ExecuteOneCommand()里逐条执行,最后一条内建 actionshutdown_done才调DoReboot();IsShuttingDown()的两处防护:关机期间HandleProcessActions()(服务重启调度、服务超时处理)和HandleControlMessages()(ctl.start/ctl.stop等控制消息)都被跳过------防止"这边在杀服务、那边又把服务拉起来"的打架 。服务被杀后不重启,同样依赖shutting_down标志(IsShuttingDown(),reboot.cpp:1173)。
1.5 另一个入口:SIGTERM → shutdown,container(init.cpp:747)
arduino
static void HandleSigtermSignal(const signalfd_siginfo& siginfo) { // init.cpp:747
if (siginfo.ssi_pid != 0) {
// Drop any userspace SIGTERM requests. 用户态发来的 SIGTERM 直接忽略
return;
}
// 只有内核发来的 SIGTERM(容器场景下 init 没有 CAP_SYS_BOOT)才触发关机
HandlePowerctlMessage("shutdown,container"); // :754
}
当 init 跑在容器里(IsRebootCapable() 为 false,init.cpp:802),收到内核 SIGTERM 时等价于 sys.powerctl=shutdown,container。这是 HandlePowerctlMessage 唯一不经过 ShutdownState 的调用点(因为它本来就在主线程的 epoll 回调上下文里)。
二、HandlePowerctlMessage:解析与排队(reboot.cpp:1049)
HandlePowerctlMessage() 是 reboot.cpp 的入口函数,只做四件事:解析命令、写 BCB、重排 action 队列、进入关机状态。
2.1 命令格式与解析
c
void HandlePowerctlMessage(const std::string& command) { // reboot.cpp:1049
unsigned int cmd = 0;
std::vector<std::string> cmd_params = Split(command, ","); // 按','拆分
std::string reboot_target = "";
bool run_fsck = false;
bool command_invalid = false;
bool userspace_reboot = false;
命令格式与解析结果:
| 命令字符串 | cmd | reboot_target | 特殊处理 |
|---|---|---|---|
reboot / reboot,userrequested |
ANDROID_RB_RESTART2 |
空 / userrequested |
无 |
reboot,recovery |
ANDROID_RB_RESTART2 |
recovery |
写 BCB:boot-recovery |
reboot,bootloader |
ANDROID_RB_RESTART2 |
bootloader |
写 BCB:bootloader |
reboot,sideload |
ANDROID_RB_RESTART2 |
归一化为 recovery |
写 BCB:--sideload |
reboot,userspace |
- | - | 走 userspace reboot 分支(第七节) |
shutdown,userrequested |
ANDROID_RB_POWEROFF |
- | run_fsck = true |
shutdown,thermal |
ANDROID_RB_THERMOFF |
- | 立即关背光(降温优先) |
shutdown 分支(reboot.cpp:1057):
ini
if (cmd_params[0] == "shutdown") {
cmd = ANDROID_RB_POWEROFF;
if (cmd_params.size() >= 2) {
if (cmd_params[1] == "userrequested") {
// 用户主动关机:文件系统卸载成功后跑一次 fsck,保证下次开机干净
run_fsck = true;
} else if (cmd_params[1] == "thermal") {
TurnOffBacklight(); // 热关机:立刻把热源(背光)关掉
cmd = ANDROID_RB_THERMOFF; // 且不跑 fsck,避免拖时间
}
}
}
注意
run_fsck只在shutdown,userrequested时为 true:重启场景不跑 fsck(用户期望重启快),反正 ext4/f2fs 标脏了下次开机也能修。
2.2 特殊 reboot_target 与 BCB(misc 分区)
reboot 分支(reboot.cpp:1071)的核心是对 bootloader / recovery / sideload / quiescent 这些 target 写 BCB(Bootloader Control Block,misc 分区开头的 108 字节结构) :
ini
} else if (cmd_params[0] == "reboot") {
cmd = ANDROID_RB_RESTART2;
if (cmd_params.size() >= 2) {
reboot_target = cmd_params[1];
...
// 不支持动态分区的设备,adb reboot fastboot 落到 bootloader
if (reboot_target == "bootloader") {
std::string err;
write_reboot_bootloader(&err); // BCB.command = "bootloader"
} else if (reboot_target == "recovery") { // reboot.cpp:1094
bootloader_message boot = {};
read_bootloader_message(&boot, &err);
// command 字段为空才写"boot-recovery",已存在则保留(别覆盖 OTA 写的 BCB!)
if (!CommandIsPresent(&boot)) {
strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
write_bootloader_message(boot, &err);
}
} else if (reboot_target == "quiescent") { // 静默重启:BCB 写 "boot-quiescent"
...
} else if (reboot_target == "sideload" || ...) { // BCB 写 "--sideload" 等参数
write_bootloader_message(options, &err);
reboot_target = "recovery"; // target 归一化成 recovery
}
// 第 3 段之后的参数原样追加,如 "reboot,recovery,quiescent"
for (size_t i = 2; ...) reboot_target += "," + cmd_params[i];
}
}
两个易忽略的细节:
CommandIsPresent()(reboot.cpp:1034) :先读 BCB,只有 command 字段为空才写入,非空(比如 OTA 升级已经写好了命令)则保留不动;顺带校验 command 里不能有不可打印字符,坏了就清零;reboot_target最终会作为 command 字符串传给内核的reboot(RESTART2),所以像userrequested、recovery这些字符串既出现在内核日志里("Restarting system with command 'xxx'"),也会在下次开机时被拼进sys.boot.reason(配合PersistRebootReason,见 4.2 节)。
2.3 重排 action 队列:清空 → shutdown 触发器 → shutdown_done
scss
// We do not want to process any messages (queue'ing triggers, shutdown messages, control
// messages, etc) from properties during reboot.
StopSendingMessages(); // reboot.cpp:1152 属性线程停止投递
LOG(INFO) << "Clear action queue and start shutdown trigger";
ActionManager::GetInstance().ClearQueue(); // :1160 清空整个 action 队列
// Queue shutdown trigger first
ActionManager::GetInstance().QueueEventTrigger("shutdown"); // :1162 触发 on shutdown
// Queue built-in shutdown_done
auto shutdown_handler = [cmd, command, reboot_target, run_fsck](const BuiltinArguments&) {
DoReboot(cmd, command, reboot_target, run_fsck); // :1165 真正的关机序列
return Result<void>{};
};
ActionManager::GetInstance().QueueBuiltinAction(shutdown_handler, "shutdown_done"); // :1168
EnterShutdown(); // :1170
执行到这里的队列状态:
csharp
action 队列(已清空后重新填充):
[0] event trigger "shutdown" ← 各 rc 文件里 on shutdown 段的动作(OEM/厂商收尾)
[1] builtin "shutdown_done" ← 最后一条,回调 = DoReboot()
on shutdown:/system/etc/init/hw/init.rc、vendor/odm 的 rc 里都可以有on shutdown段,用来做设备级收尾(写某个 sysfs 节点、同步某块芯片状态等)。厂商在这里做的动作会先于DoReboot执行;shutdown_done:内建 action,排在最后。主循环ExecuteOneCommand()一条一条跑,跑到它时进入DoReboot()------从此不再返回。
2.4 EnterShutdown:进入关机状态(reboot.cpp:824)
scss
static void EnterShutdown() { // reboot.cpp:824
LOG(INFO) << "Entering shutdown mode";
shutting_down = true; // 全局标志(reboot.cpp:86)
// Skip wait for prop if it is in progress
ResetWaitForProp(); // 取消正在进行的 wait_for_prop(别让关机等属性)
// Clear EXEC flag if there is one pending
for (const auto& s : ServiceList::GetInstance()) {
s->UnSetExec(); // 清除 service 的 pending exec 标志
}
}
shutting_down 置位后的连锁反应(配合 1.4 节):
- 主循环跳过
HandleProcessActions():服务死了不再自动重启; - 主循环跳过
HandleControlMessages():ctl.start/ctl.stop失效; ShutdownState::CheckShutdown()拒绝重入;IsShuttingDown()供 service.cpp / sigchld_handler 使用,杀服务时不会触发重启逻辑。
三、DoReboot:真正的关机序列(reboot.cpp:634)
DoReboot(cmd, reason, reboot_target, run_fsck) 是整个 init 侧关机的主函数,跑完为止,绝不返回 (最后 RebootSystem() + abort())。先给全局图,再分阶段展开。
bash
DoReboot (reboot.cpp:634)
│
├─0 超时预算 + 起看门狗线程 RebootMonitorThread :636-669
├─1 PersistRebootReason 记录重启原因 :671-680
├─2 /data 未挂载?→ sync + 直接 RebootSystem(快速路径) :682-688
├─3 服务分类:调试服务/watchdogd/动画/critical/待停 :690-720
├─4 关背光(POWEROFF/thermal 且无关机动画) :717-720
├─5 关机动画 bootanim/surfaceflinger 处理 :722-751
├─6 两段式停服务:SIGTERM(半超时) → SIGKILL :753-762
├─7 vold 收尾:vdc abort_fuse / vdc shutdown → stop :764-774
├─8 杀调试服务(logd/tombstoned/adbd/console) :775-776
│ ★ "logcat stopped here" ------ 此后只能去内核日志找输出
├─9 sync() :778-783
├─10 KillZramBackingDevice :785
├─11 apexd --unmount-all :787-791
├─12 KillNfsBackingDevice(厂商定制) :792
├─13 TryUmountAndFsck:umount /data 全家 + 可选 fsck :793-794
├─14 再 sync() + LogShutdownTime :795-803
├─15 停看门狗线程 :805-807
├─16 F2FS_IOC_SHUTDOWN 让 /data 干净落地 :809-819
└─17 RebootSystem(cmd, reboot_target) → 内核 :820-821
3.1 阶段 0:超时预算与看门狗(reboot.cpp:636-669)
c
static void DoReboot(unsigned int cmd, const std::string& reason,
const std::string& reboot_target, bool run_fsck) { // reboot.cpp:634
Timer t;
LOG(INFO) << "Reboot start, reason: " << reason << ", reboot_target: " << reboot_target;
bool is_thermal_shutdown = cmd == ANDROID_RB_THERMOFF;
auto shutdown_timeout = 0ms;
if (!SHUTDOWN_ZERO_TIMEOUT) { // util.h 常量,默认 false
constexpr unsigned int shutdown_timeout_default = 6; // 默认 6 秒
constexpr unsigned int max_thermal_shutdown_timeout = 3; // 热关机最多 3 秒
auto shutdown_timeout_final = android::base::GetUintProperty(
"ro.build.shutdown_timeout", shutdown_timeout_default);
if (is_thermal_shutdown && shutdown_timeout_final > max_thermal_shutdown_timeout) {
shutdown_timeout_final = max_thermal_shutdown_timeout; // 热关机宁可不优雅也要快
}
shutdown_timeout = std::chrono::seconds(shutdown_timeout_final);
}
LOG(INFO) << "Shutdown timeout: " << shutdown_timeout.count() << " ms";
shutdown_timeout 是后面"优雅停服务 / umount 重试"的总预算(可用 ro.build.shutdown_timeout 调);SHUTDOWN_ZERO_TIMEOUT 为 true 时直接 0------一点不优雅,立刻硬来。
接着初始化信号量并起看门狗(详细分析见第五节):
scss
sem_t reboot_semaphore;
if (sem_init(&reboot_semaphore, false, 0) == -1) {
// 信号量创建失败(几乎不可能):放弃优雅流程,立即重启
RebootSystem(cmd, reboot_target, reason);
}
LOG(INFO) << "Create reboot monitor thread.";
bool reboot_monitor_run = true;
std::thread reboot_monitor_thread(&RebootMonitorThread, cmd, reboot_target,
&reboot_semaphore, shutdown_timeout, &reboot_monitor_run);
reboot_monitor_thread.detach();
sem_post(&reboot_semaphore); // 给看门狗第一拍"心跳"
3.2 阶段 1:PersistRebootReason 记录重启原因(reboot.cpp:671-680)
c
// Ensure last reboot reason is reduced to canonical
// alias reported in bootloader or system boot reason.
size_t skip = 0;
std::vector<std::string> reasons = Split(reason, ",");
if (reasons.size() >= 2 && reasons[0] == "reboot" &&
(reasons[1] == "recovery" || reasons[1] == "bootloader" || reasons[1] == "cold" ||
reasons[1] == "hard" || reasons[1] == "warm")) {
skip = strlen("reboot,"); // "reboot,recovery" → 只存 "recovery"
}
PersistRebootReason(reason.c_str() + skip, true);
PersistRebootReason()(reboot.cpp:100)把原因写两处:LAST_REBOOT_REASON_PROPERTY 属性和 LAST_REBOOT_REASON_FILE 文件(/data/misc/recovery/lastrebootreason,定义在 reboot_utils.h),并 fsync 落盘。下次开机时框架据此合成规范化的 sys.boot.reason(比如 reboot,userrequested、reboot,recovery)。
skip 的逻辑很讲究:bootloader 自己上报原因时会带上 reboot, 前缀,如果 init 这里存的是完整的 "reboot,recovery",拼出来就成 "reboot,reboot,recovery" 了------所以这些规范别名(recovery/bootloader/cold/hard/warm)要把前缀剥掉再存。
3.3 快速路径:/data 未挂载(reboot.cpp:682-688)
scss
// If /data isn't mounted then we can skip the extra reboot steps below,
// since we don't need to worry about unmounting it.
if (!IsDataMounted("*")) { // IsDataMounted:遍历 /proc/mounts 找 /data
sync();
RebootSystem(cmd, reboot_target, reason);
abort();
}
没挂 /data(比如 charger 模式、早期 first stage、recovery 边界场景)就没有"保护 /data"的负担:sync 一下直接重启,后面所有阶段全省。
3.4 阶段 2:服务分类(reboot.cpp:690-720)
c
bool do_shutdown_animation = GetBoolProperty("ro.init.shutdown_animation", false);
// watchdogd is a vendor specific component but should be alive to complete shutdown safely.
const std::set<std::string> to_starts{"watchdogd"}; // 确保看门狗服务在跑!
std::set<std::string> stop_first; // 其余服务:先停
for (const auto& s : ServiceList::GetInstance()) {
if (kDebuggingServices.count(s->name())) { // {tombstoned, logd, adbd, console}
// keep debugging tools until non critical ones are all gone.
s->SetShutdownCritical(); // 调试服务留到最后杀(日志保留最久)
} else if (to_starts.count(s->name())) {
if (auto result = s->Start(); !result.ok()) { ... }
s->SetShutdownCritical(); // watchdogd:没跑就拉起来,且不许停
} else if (do_shutdown_animation) {
continue; // 有关机动画:动画类服务先不动
} else if (s->IsShutdownCritical()) {
if (auto result = s->Start(); !result.ok()) { ... } // critical 服务没跑也拉起来
} else {
stop_first.insert(s->name()); // 其余统统进入"先停"名单
}
}
// POWEROFF / 热关机且没有关机动画:关背光(省电/降温)
if (!do_shutdown_animation && (cmd == ANDROID_RB_POWEROFF || is_thermal_shutdown)) {
TurnOffBacklight(); // 起 blank_screen 服务把屏幕抹黑
}
分类逻辑一张表说清:
| 类别 | 判定 | 处理 |
|---|---|---|
| 调试服务 | 名字在 kDebuggingServices(reboot.cpp:88):tombstoned/logd/adbd/console |
标 critical,最后杀 |
| watchdogd | to_starts |
没跑就 Start,标 critical------它是"安全完成关机"的兜底 |
| shutdown critical | rc 里声明 shutdown critical 的服务 |
没跑也拉起来;不会被 stop_first 杀 |
| 关机动画类 | ro.init.shutdown_animation=true |
先保留,走动画流程 |
| 其余 | 默认 | 进 stop_first,第一批被停 |
为什么 logd 留到最后? 关机全程 init 的 LOG 都要往外打,logd 活得越久,logcat 里留下的关机日志越多;所以先杀业务服务,最后才轮到日志/调试设施(杀完它们 init 还在跑,但输出只剩 LOG_KLOG 直写的 /dev/kmsg 了)。
为什么 watchdogd 反而要确保在跑? 如果平台用了硬件 watchdog 且它由 watchdogd 喂狗,那关机序列卡住时 watchdog 超时复位是最后一道保险------不能自己把保险拆了。
3.5 阶段 3:关机动画(reboot.cpp:722-751)
scss
Service* boot_anim = ServiceList::GetInstance().FindService("bootanim");
Service* surface_flinger = ServiceList::GetInstance().FindService("surfaceflinger");
if (boot_anim != nullptr && surface_flinger != nullptr && surface_flinger->IsRunning()) {
if (do_shutdown_animation) {
SetProperty("service.bootanim.exit", "0"); // 控制动画不退出
SetProperty("service.bootanim.progress", "0"); // 进度从 0 开始
boot_anim->Stop(); // 可能正在放开机动画,停掉重启以切换到关机动画模式
}
for (const auto& service : ServiceList::GetInstance()) {
if (service->classnames().count("animation") == 0) continue;
if (do_shutdown_animation) service->Start(); // animation class 服务全部拉起
service->SetShutdownCritical(); // 全部标 critical,不许 stop_first 杀
}
if (do_shutdown_animation) {
boot_anim->Start();
surface_flinger->SetShutdownCritical();
boot_anim->SetShutdownCritical();
}
}
思路:如果设备配了关机动画(ro.init.shutdown_animation),bootanim/surfaceflinger 和 animation class 的服务都要活到最后 ,通过 service.bootanim.exit/progress 属性向 bootanim 传递"关机模式 + 进度"信号(bootanim 轮询这两个属性绘制关机动画)。这套机制和开机动画共用一套代码,只是入口状态不同。
3.6 阶段 4:两段式停服务(reboot.cpp:753-762)
scss
// optional shutdown step
// 1. terminate all services except shutdown critical ones. wait for delay to finish
if (shutdown_timeout > 0ms) {
StopServicesAndLogViolations(stop_first, shutdown_timeout / 2, true /* SIGTERM */);
}
// Send SIGKILL to ones that didn't terminate cleanly.
StopServicesAndLogViolations(stop_first, 0ms, false /* SIGKILL */); // 不再等
SubcontextTerminate(); // 终止 subcontext 进程(vendor rc 命令的执行体)
ReapAnyOutstandingChildren(); // 收尸
两段式 :先用一半的预算(默认 3s)发 SIGTERM 优雅终止------服务可以存数据、关文件、断连接;超时还没退的,第二轮直接 SIGKILL,杀完就收尸,一分钟都不多等。
StopServices()(reboot.cpp:546)按 services_in_shutdown_order()(ServiceList 的逆序 ,即后定义/后启动的先停)逐个 Terminate()/Stop(),并用 WaitToBeReaped() 在超时内等待 pid 被 reap。StopServicesAndLogViolations()(reboot.cpp:575)在此基础上多干一件事------把没退的服务点名:
scss
LOG(ERROR) << "[service-misbehaving] : service '" << s->name() << "' is still running "
<< timeout.count() << "ms after receiving " << (terminate ? "SIGTERM" : "SIGKILL");
[service-misbehaving] 是关机慢问题最直接的线索:哪个服务上榜,哪个服务就要查为什么忽略 SIGTERM/赖着不死。
3.7 阶段 5:vold 收尾(reboot.cpp:764-774)
scss
// 3. send volume abort_fuse and volume shutdown to vold
Service* vold_service = ServiceList::GetInstance().FindService("vold");
if (vold_service != nullptr && vold_service->IsRunning()) {
// Manually abort FUSE connections, since the FUSE daemon is already dead
// at this point, and unmounting it might hang.
CallVdc("volume", "abort_fuse"); // fork /system/bin/vdc volume abort_fuse
CallVdc("volume", "shutdown"); // vold:卸载自己管理的所有卷
vold_service->Stop();
} else {
LOG(INFO) << "vold not running, skipping vold shutdown";
}
CallVdc()(reboot.cpp:204)fork 出 /system/bin/vdc(vold 的命令行客户端)执行命令。为什么 FUSE 要单独 abort_fuse?------vold 是 FUSE daemon,FUSE 挂载点(/storage/emulated 等)的请求全由它服务;此刻它马上要被停掉,如果直接 umount FUSE 挂载点,umount 可能在等一个永远没人应答的 FUSE 请求而挂死。所以先 abort 所有 FUSE 连接,再让 vold 正常 shutdown(它会把 emulated/adopted/private 卷都卸干净),最后 stop vold 本体。
这一步和 framework 侧 ShutdownThread "vold 由 init 负责" 的分工相呼应:ShutdownThread 只把用户态清干净,存储栈的收尾全部交给 init。
3.8 阶段 6:杀调试服务(reboot.cpp:775-776)
arduino
// logcat stopped here
StopServices(kDebuggingServices, 0ms, false /* SIGKILL */);
源码注释就一行 // logcat stopped here。logd/tombstoned/adbd/console 被一把 SIGKILL------从这行往后,logcat 里再也看不到新日志 。init 后续的 LOG 走 LOG_KLOG 直写 /dev/kmsg(内核日志缓冲区),要看重启前最后阶段的日志得去 lastkmsg / pstore 翻。这是调试重启问题时必须知道的分水岭。
3.9 阶段 7:sync + zram + apex + nfs(reboot.cpp:778-792)
scss
// 4. sync, try umount, and optionally run fsck for user shutdown
{
Timer sync_timer;
LOG(INFO) << "sync() before umount..."; // 全文件系统刷盘
sync();
}
// 5. drop caches and disable zram backing device, if exist
KillZramBackingDevice(); // 见第六节详解
LOG(INFO) << "Ready to unmount apexes. So far shutdown sequence took " << t;
// 6. unmount active apexes, otherwise they might prevent clean unmount of /data.
if (auto ret = UnmountAllApexes(); !ret.ok()) { // fork: apexd --unmount-all
LOG(ERROR) << ret.error();
}
KillNfsBackingDevice(); // 厂商定制:杀 nfsd/rpcbind/mountd
UmountStat stat =
TryUmountAndFsck(cmd, run_fsck, shutdown_timeout - t.duration(), &reboot_semaphore);
到这里 /data 的"层层包装"被逐层剥掉:
sync():先把手脏页全落盘;KillZramBackingDevice():拆掉压在 /data 上的 swap(zram backing 是 /data 上的文件经 loop 设备映射的);UnmountAllApexes():APEX 都是从 /data 或 super 动态挂载的,不卸掉会卡住 /data 的 umount;- (厂商定制)
KillNfsBackingDevice():NFS 相关进程与挂载点清掉; TryUmountAndFsck():终于轮到 umount /data 本体(第六节详解)。
3.10 阶段 8:收尾(reboot.cpp:795-819)
scss
// Follow what linux shutdown is doing: one more sync with little bit delay
{
Timer sync_timer;
LOG(INFO) << "sync() after umount...";
sync(); // umount 后再 sync 一次(学 Linux 原生 shutdown)
}
if (!is_thermal_shutdown) std::this_thread::sleep_for(100ms); // 给底层一点缓冲
LogShutdownTime(stat, &t); // "powerctl_shutdown_time_ms:<耗时>:<umount状态>"
// Send signal to terminate reboot monitor thread.
reboot_monitor_run = false; // 通知看门狗线程退出
sem_post(&reboot_semaphore);
// Reboot regardless of umount status. If umount fails, fsck after reboot will fix it.
if (IsDataMounted("f2fs")) { // /data 是 f2fs:让文件系统"优雅落地"
uint32_t flag = F2FS_GOING_DOWN_FULLSYNC;
unique_fd fd(TEMP_FAILURE_RETRY(open("/data", O_RDONLY)));
int ret = ioctl(fd.get(), F2FS_IOC_SHUTDOWN, &flag);
...
}
RebootSystem(cmd, reboot_target, reason); // ★ 最后一步,见下
abort(); // 正常情况下执行不到这里
LogShutdownTime()(reboot.cpp:218)输出powerctl_shutdown_time_ms:xxx:0,冒号后的数字是UmountStat(见第六节表格)------统计关机耗时的标准出口;F2FS_IOC_SHUTDOWN(FULLSYNC):通知 f2fs 把 journal/脏数据全部落盘并把文件系统置为干净状态,避免下次开机被标 "unclean shutdown" 触发修复;ext4 设备在这个版本里没有对应处理(后续 AOSP 版本补了EXT4_IOC_SHUTDOWN);- 注释再次强调兜底哲学:umount 失败也照样重启,脏盘交给下次开机的 fsck。
3.11 最后一步:RebootSystem() → 内核
RebootSystem() 定义在同目录的 reboot_utils.cpp:
c
void __attribute__((noreturn))
RebootSystem(unsigned int cmd, const std::string& rebootTarget) {
LOG(INFO) << "Reboot ending, jumping to kernel";
switch (cmd) {
case ANDROID_RB_POWEROFF:
reboot(RB_POWER_OFF); // 关机:内核走 power off 流程
break;
case ANDROID_RB_RESTART2:
syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
LINUX_REBOOT_CMD_RESTART2, rebootTarget.c_str()); // ★ 带命令串的重启
break;
case ANDROID_RB_THERMOFF:
reboot(RB_POWER_OFF);
break;
}
PLOG(ERROR) << "reboot call returned"; // 能走到这里说明 syscall 失败
abort(); // 主动 crash,让 watchdog/pstore 留现场
}
内核侧接着发生的事(简述):sys_reboot 校验 magic 后打印 Restarting system with command 'userrequested',依次走过 reboot notifier 链、syscore 关停,最后 machine_restart() ------ 在 arm64 上通常是 PSCI 的 SYSTEM_RESET 调用(SMC 进 EL3/固件),由芯片级复位逻辑把 SoC 复位;之后 bootloader 起来,读 BCB/复位原因寄存器决定去哪(主系统 / recovery / fastboot),加载 kernel,init 重新开始。
四、看门狗:RebootMonitorThread(reboot.cpp:323)
关机流程里藏着一个独立线程,它的唯一使命是:DoReboot 卡死时把机器强制重启/关掉。
c
void RebootMonitorThread(unsigned int cmd, const std::string& reboot_target,
sem_t* reboot_semaphore, std::chrono::milliseconds shutdown_timeout,
bool* reboot_monitor_run) { // reboot.cpp:323
unsigned int remaining_shutdown_time = 0;
// 300 seconds more than the timeout passed to the thread as there is a final Umount pass
// after the timeout is reached.
constexpr unsigned int shutdown_watchdog_timeout_default = 300; // 看门狗预算:300 秒
auto shutdown_watchdog_timeout = android::base::GetUintProperty(
"ro.build.shutdown.watchdog.timeout", shutdown_watchdog_timeout_default);
remaining_shutdown_time = shutdown_watchdog_timeout + shutdown_timeout.count() / 1000;
while (*reboot_monitor_run == true) {
if (TEMP_FAILURE_RETRY(sem_wait(reboot_semaphore)) == -1) return; // 等一次"心跳"
...
shutdown_timeout_timespec.tv_sec += remaining_shutdown_time; // 装填倒计时
int sem_return = 0;
while ((sem_return = sem_timedwait_monotonic_np(reboot_semaphore,
&shutdown_timeout_timespec)) == -1 &&
errno == EINTR) { } // 带超时地等下一次"心跳"
if (sem_return == -1) { // 超时:迟迟没有心跳 → 判定 init 卡死
LOG(ERROR) << "Reboot thread timed out";
if (android::base::GetBoolProperty("ro.debuggable", false) == true) {
LOG(INFO) << "Show stack for all active CPU:";
WriteStringToFile("l", PROC_SYSRQ); // sysrq l:全 CPU 栈回溯
LOG(INFO) << "Show tasks that are in disk sleep...";
WriteStringToFile("w", PROC_SYSRQ); // sysrq w:D 状态任务
}
// 关机场景:紧急 sync + 全部 remount 只读,然后直接掉电
if (cmd == ANDROID_RB_POWEROFF || cmd == ANDROID_RB_THERMOFF) {
WriteStringToFile("s", PROC_SYSRQ); // sysrq s:emergency sync
WriteStringToFile("u", PROC_SYSRQ); // sysrq u:remount read-only
RebootSystem(cmd, reboot_target);
}
LOG(ERROR) << "Trigger crash at last!";
WriteStringToFile("c", PROC_SYSRQ); // sysrq c:主动 panic
} else {
// 收到心跳:计算剩余时间,回到循环开头重新等
...
}
}
}
工作机制(信号量 = 心跳):
DoReboot在启动时 (:669)、fsck 前后 (:459/:468,因为 fsck 可能很久)、结束时 (:806-807)各sem_post一次;- 看门狗线程每次收到心跳就重新装填倒计时(默认 300s + shutdown_timeout ,可用
ro.build.shutdown.watchdog.timeout调整); - 若倒计时耗尽仍无心跳 → 说明 init 主流程卡死在某个阶段,走超时路径。
超时路径的层层递进非常有节奏感:
| 顺序 | 动作 | sysrq 键 | 目的 |
|---|---|---|---|
| 1 | dump 全 CPU 栈 | l |
debuggable 设备才做,留现场 |
| 2 | dump D 状态任务 | w |
找卡在不可中断 IO 的进程 |
| 3 | 紧急 sync | s |
仅关机场景:尽最后努力刷盘 |
| 4 | 全部 remount 只读 | u |
尽最后努力保护文件系统 |
| 5 | 直接 RebootSystem | - | 关机场景到这为止 |
| 6 | 主动 panic | c |
重启场景:kernel panic → ramoops 留现场 + 硬件 watchdog 兜底复位 |
(PROC_SYSRQ 即 /proc/sysrq-trigger,往里写一个字符等价于触发对应的 Magic SysRq 功能。)
结论:Android 关机是有"三重保险"的------优雅流程(DoReboot)→ 软件看门狗(RebootMonitorThread 300s)→ 硬件看门狗(watchdogd/SoC WD)。用户按重启键后机器理论上不可能永远卡着不关(除非 EL3/固件级别的 bug)。
五、umount /data 的那些拦路虎
TryUmountAndFsck()(reboot.cpp:417)与 UmountPartitions()(reboot.cpp:281)是整个关机序列里最容易出问题、也最能体现工程功力的部分。
5.1 谁压在 /data 上?
重启前的挂载关系(典型设备):
bash
/dev/block/by-name/userdata (块设备)
└── /data (f2fs/ext4)
├── /mnt/user/0/emulated/0 ← FUSE(由 vold 代理,fsname 是 /data/media)
├── zram backing 文件 → /dev/block/loopX(swap 用)
└── 各种 APEX 的 backing 数据 / libc 等
所以顺序必须是:杀进程 → abort FUSE → 拆 zram/loop → 卸 APEX → 才能卸 /data。少任何一步,umount /data 都会 EBUSY。
5.2 FindPartitionsToUmount:找出要卸的分区(reboot.cpp:240)
less
// 扫 /proc/mounts:
if (MountEntry::IsBlockDevice(*mentry) && hasmntopt(mentry, "rw")) {
// 块设备且 rw 的才需要处理;排除 "/" "/system" "/vendor" "/oem"
// ------ 这些是 adb remount 后变 rw 的只读分区,关机 critical 服务还依赖它们,不动
block_dev_partitions->emplace(block_dev_partitions->begin(), *mentry);
} else if (MountEntry::IsEmulatedDevice(*mentry)) { // fsname 以 /data/ 开头 = FUSE 挂载
emulated_partitions->emplace(emulated_partitions->begin(), *mentry);
}
5.3 UmountPartitions:带超时的重试循环(reboot.cpp:281)
c
static UmountStat UmountPartitions(std::chrono::milliseconds timeout) { // reboot.cpp:281
Timer t;
while (true) {
std::vector<MountEntry> block_devices, emulated_devices;
if (!FindPartitionsToUmount(&block_devices, &emulated_devices, false)) {
return UMOUNT_STAT_ERROR; // /proc/mounts 都打不开
}
if (block_devices.size() == 0) {
return UMOUNT_STAT_SUCCESS; // 没有可卸的了 → 成功!
}
bool unmount_done = true;
if (emulated_devices.size() > 0) { // 先卸 FUSE
for (auto& entry : emulated_devices) {
if (!entry.Umount(false)) unmount_done = false;
}
if (unmount_done) sync(); // FUSE 卸干净了就 sync 一次
}
for (auto& entry : block_devices) { // 再卸块设备(/data 等)
if (!entry.Umount(timeout == 0ms)) unmount_done = false; // 最后一轮用 MNT_FORCE
}
if (unmount_done) return UMOUNT_STAT_SUCCESS;
if ((timeout < t.duration())) {
return UMOUNT_STAT_TIMEOUT; // 超时了:先至少试过一轮
}
std::this_thread::sleep_for(100ms); // 100ms 后再试一轮
}
}
算法概括:循环"扫 /proc/mounts → 卸 FUSE → sync → 卸块设备",全部卸光才算成功;失败就 100ms 一轮地重试,直到预算耗尽 。Umount(force)(reboot.cpp:139)内部是 umount2(dir, force ? MNT_FORCE : 0),并打印 Unmounting <fsname>:<dir> opts <opts> / Umounted ... / Cannot umount ... 三种日志。
5.4 TryUmountAndFsck:最后的疯狂与可选 fsck(reboot.cpp:417)
scss
static UmountStat TryUmountAndFsck(unsigned int cmd, bool run_fsck,
std::chrono::milliseconds timeout, sem_t* reboot_semaphore) {
...
auto sm = snapshot::SnapshotManager::New();
bool ota_update_in_progress = false;
if (sm->IsUserspaceSnapshotUpdateInProgress()) { // OTA 虚拟 A/B 合并进行中?
LOG(INFO) << "OTA update in progress";
ota_update_in_progress = true;
}
UmountStat stat = UmountPartitions(timeout - t.duration());
if (stat != UMOUNT_STAT_SUCCESS) {
LOG(INFO) << "umount timeout, last resort, kill all and try";
if (DUMP_ON_UMOUNT_FAILURE) DumpUmountDebuggingInfo(); // lsof + sysrq l/w
if (ota_update_in_progress) {
return stat; // ★ OTA 中:snapuserd 正在服务 IO,不能杀,直接认输返回
}
KillAllProcesses(); // sysrq i:SIGKILL 所有进程(init 除外)
UmountStat st = UmountPartitions(0ms); // 全杀光后再试最后一轮(MNT_FORCE)
...
}
if (stat == UMOUNT_STAT_SUCCESS && run_fsck) { // 仅用户主动关机跑 fsck
LOG(INFO) << "Pause reboot monitor thread before fsck";
sem_post(reboot_semaphore); // fsck 可能很久:先给看门狗一拍心跳
for (auto& entry : block_devices) {
entry.DoFsck(); // f2fs → fsck.f2fs -a;ext4 → e2fsck -y
}
LOG(INFO) << "Resume reboot monitor thread after fsck";
sem_post(reboot_semaphore); // fsck 完:再来一拍
}
return stat;
}
三个要点:
- OTA 合并进行中不许杀进程:虚拟 A/B 的 snapuserd 在服务合并 IO,杀光进程那套"最后的疯狂"会造成 IO 错误,宁可带着 umount 失败的状态直接重启(fsck 下次开机修);
- umount 超时的最后手段 是
KillAllProcesses()(reboot.cpp:318,sysrqi)------所有进程杀光后再用MNT_FORCE硬卸一轮,死马当活马医; - fsck 前后给看门狗发心跳------fsck 一个大分区可能超过 300s,不发心跳的话看门狗会误判 init 卡死、直接把系统 panic 掉。这个细节和第四节呼应,读代码时非常容易被忽略。
DumpUmountDebuggingInfo()(reboot.cpp:267)在 umount 失败时(DUMP_ON_UMOUNT_FAILURE 控制,默认关)跑 lsof + 打印全部挂载点 + sysrq l/w------排查"谁占着 /data"的全套工具。
UmountStat 各值含义(reboot.cpp:116):
| 值 | 含义 | 对应 powerctl_shutdown_time_ms:...:N 尾号 |
|---|---|---|
UMOUNT_STAT_SUCCESS = 0 |
全部卸载成功 | 0 |
UMOUNT_STAT_SKIPPED = 1 |
未执行 umount | 1 |
UMOUNT_STAT_TIMEOUT = 2 |
重试到超时仍失败 | 2 |
UMOUNT_STAT_ERROR = 3 |
无法执行(如 /proc/mounts 打不开) | 3 |
5.5 KillZramBackingDevice:拆掉压在 /data 上的 swap(reboot.cpp:479)
zram(内存压缩 swap)可以配一个 backing device:把不活跃的页换出到 /data 上的一个文件(经 loop 设备映射)。不清掉它,/data 永远 umount 不掉:
scss
static Result<void> KillZramBackingDevice() { // reboot.cpp:479
... // 读 /sys/block/zram0/initstate
swapoff(ZRAM_DEVICE); // ① 先把 swap 停了
WriteStringToFile("1", ZRAM_RESET); // ② reset zram 设备
if (!StartsWith(backing_dev, "/dev/block/loop")) return {}; // 非 loop 设备到此为止
// ③ 清 loop 设备映射(backing 文件从此与 loop 脱钩)
unique_fd loop(open(backing_dev.c_str(), O_RDWR | O_CLOEXEC));
ioctl(loop.get(), LOOP_CLR_FD, 0); // LOOP_CLR_FD
}
swapoff 可能耗时(要把换出的页读回来),所以有单独的 Timer 日志 swapoff() took ...。
六、岔路:userspace reboot(可选阅读)
HandlePowerctlMessage() 里 reboot,userspace 走的是完全不同的路(reboot.cpp:1075→1154):
ini
if (reboot_target == "userspace") {
userspace_reboot = true; // reboot.cpp:1075
}
...
if (userspace_reboot) {
HandleUserspaceReboot(); // reboot.cpp:1154
return;
}
HandleUserspaceReboot()(reboot.cpp:998)不重启内核,只重造用户态:fork 一个看门狗进程(UserspaceRebootWatchdogThread,reboot.cpp:974:10s 内 sys.init.userspace_reboot.in_progress 没置 1、或 5 分钟内没 sys.boot_completed=1,就带着 userspace_failed,watchdog_triggered,... 的 reason 做脏硬重启 ),然后排队 userspace-reboot-requested 触发器和 userspace-reboot 内建 action。
DoUserspaceReboot()(reboot.cpp:847)的主干:只停 is_post_data() 的服务(SIGTERM 5s → SIGKILL 10s,违规服务记录到 /metadata/userspacereboot/services.txt)→ 拆 zram → vdc volume reset → 停调试服务 → apexd --unmount-all → 切回 bootstrap mount namespace → 删掉 APEX 定义的 action/service → 重新 enable 服务 → 触发 userspace-reboot-resume,从挂载 /data 之后的阶段重新把用户态拉起来。全程用 make_scope_guard 兜底:任何一步失败自动降级为硬重启 (trigger_shutdown("reboot,userspace_failed,shutdown_aborted,..."))。
| 对比项 | 正常硬重启(本文主线) | userspace reboot |
|---|---|---|
| 经过 bootloader / kernel | 是 | 否 |
| 耗时 | 长(完整开机流程) | 短(从 late-init 后段续跑) |
| 主要用途 | 日常重启 | Mainline/框架级更新后激活 |
| 失败兜底 | 硬件 watchdog | scope guard → 自动转硬重启 |
七、全流程时序总结
从用户按下重启到 SoC 复位的完整时序:
scss
用户/system_server init 属性线程 init 主线程
──────────────── ────────────── ──────────────
PMS.reboot()
│
ShutdownThread.run()
│ ACTION_SHUTDOWN 广播
│ AMS/PM/radio 收尾
│
PMS.lowLevelReboot()
│
SystemProperties.set(
"sys.powerctl","reboot,xxx") ──► HandlePropertySet()
│ SELinux 检查
│ 日志 "Received sys.powerctl=..."
│ (虚属性不落盘)
│ NotifyPropertyChange()
▼
PropertyChanged() ──────► TriggerShutdown()
(init.cpp:397) WakeMainInitThread() ──► 主循环被唤醒
│
CheckShutdown() 取出命令
│
HandlePowerctlMessage("reboot,xxx")
│ 解析 / 写 BCB
│ StopSendingMessages
│ ClearQueue
│ QueueEventTrigger("shutdown")
│ QueueBuiltinAction("shutdown_done")
│ EnterShutdown()
▼
ExecuteOneCommand()×N
│ 逐条跑 on shutdown 动作
▼
DoReboot() ★ 不再返回
│ 0 看门狗线程
│ 1 PersistRebootReason
│ 2 /data 检查
│ 3 服务分类
│ 4 SIGTERM → SIGKILL
│ 5 vdc abort_fuse/shutdown
│ 6 杀 logd/adbd ← logcat 到此为止
│ 7 sync → zram → apex → nfs
│ 8 umount /data(+可选 fsck)
│ 9 sync → F2FS_IOC_SHUTDOWN
▼
RebootSystem()
│ syscall(__NR_reboot,
│ CMD_RESTART2, target)
▼
内核 sys_reboot → kernel_restart("xxx")
→ "Restarting system with command 'xxx'"
→ reboot notifiers → machine_restart()
→ PSCI SYSTEM_RESET → SoC 复位
→ bootloader(读 BCB/复位原因)→ kernel → init 开机
八、关键日志与调试技巧
8.1 标志性日志时间线
一次正常重启,在 logcat(-b all)里应能看到:
| 日志 | 来源 | 说明 |
|---|---|---|
Received sys.powerctl='reboot,userrequested' from pid: xxxx (system_server) |
property_service | 谁发起的重启,pid+cmdline 全在这 |
Got shutdown_command 'reboot,userrequested' Calling HandlePowerctlMessage() |
init.cpp:1150 | 命令到达主线程 |
Clear action queue and start shutdown trigger |
reboot.cpp:1159 | 开始重排队列 |
Entering shutdown mode |
reboot.cpp:825 | shutting_down=true |
Reboot start, reason: reboot,userrequested, reboot_target: |
reboot.cpp:637 | DoReboot 入口 |
Shutdown timeout: 6000 ms |
reboot.cpp:652 | 本次的优雅预算 |
Create reboot monitor thread. |
reboot.cpp:662 | 看门狗已就位 |
Stopping N services by sending SIGTERM / ... SIGKILL |
reboot.cpp:548 | 两段式停服务 |
Calling /system/bin/vdc volume abort_fuse / volume shutdown |
reboot.cpp:205 | vold 收尾 |
sync() before umount... |
reboot.cpp:780 | ← logcat 大约到此为止 |
Unmounting /data/media:/mnt/user/... / Umounted ... |
reboot.cpp:140/143 | FUSE/块设备卸载 |
sync() after umount... |
reboot.cpp:798 | |
powerctl_shutdown_time_ms:<ms>:<stat> |
reboot.cpp:219 | 关机总耗时+umount 结果 |
Reboot ending, jumping to kernel |
reboot_utils.cpp | init 的遗言 |
杀掉 logd 之后的日志只存在于内核日志缓冲 ,重启后看 /sys/fs/pstore/console-ramoops*(或老设备的 /proc/last_kmsg)。
8.2 排查套路
- 意外重启 :先查
sys.boot.reason(下次开机后),再去 pstore/lastkmsg 搜Received sys.powerctl=------能直接定位发起进程;搜不到则怀疑内核 panic / 硬件 watchdog(找 ramoops 的 panic 记录); - 重启慢/关机卡 :logcat 搜
[service-misbehaving](谁赖着不死)、Unmounting与Cannot umount(哪个分区卸不掉)、powerctl_shutdown_time_ms的尾号(2=umount 超时);怀疑进程占着 /data 就开DUMP_ON_UMOUNT_FAILURE看 lsof 输出; - 关机疑似卡死 :等 300s 看有没有
Reboot thread timed out------有,说明看门狗介入了,后面跟着的 sysrq dump(栈、D 状态任务)就是卡死现场;没有,就得怀疑 init 本身死了(查 pstore 里的 abort/panic 记录); - 调参 :
ro.build.shutdown_timeout(优雅预算,默认 6s)、ro.build.shutdown.watchdog.timeout(看门狗,默认 300s)、ro.init.shutdown_animation(关机动画开关)。
九、总结
- 交接 :
sys.powerctl是 framework 递给 init 的接力棒。属性线程不直接执行关机(会破坏 action 队列),而是经ShutdownState存命令 + eventfd 唤醒,主循环在下一条命令前优先处理------HandlePowerctlMessage()只解析和排队,真正的执行分散在后续每轮ExecuteOneCommand()里,最终汇合于shutdown_done→DoReboot(); - 队列 :关机时清空整个 action 队列,只留
on shutdown(OEM 收尾)和shutdown_done(关机本体)两件事,EnterShutdown()置位shutting_down后,服务不再重启、ctl 消息不再处理、命令不会重入; - 分层优雅:DoReboot 的顺序是一场"剥洋葱"------SIGTERM 半程 → SIGKILL → vold(先 abort FUSE 防挂死)→ 杀日志服务(logcat 的终点)→ sync → 拆 zram/loop → 卸 APEX → umount /data(100ms 一轮重试)→ 超时杀光所有进程再硬卸 → fsck(仅用户关机)→ F2FS 干净落地 → reboot 系统调用;
- 兜底哲学:umount 失败照样重启(下次开机 fsck 修);优雅流程卡死有 300s 软件看门狗(sysrq dump → emergency sync → 主动 panic);再往下还有硬件 watchdog。"一定能关掉"比"关得完美"优先级更高;
- 调试 :
Received sys.powerctl=(谁发起)、[service-misbehaving](谁拖慢)、Cannot umount(谁占着)、powerctl_shutdown_time_ms(多久)、Reboot thread timed out(卡没卡)------五条日志基本覆盖重启关机问题的定位路径。
一句话总结:framework 负责"体面地送走用户态",init 负责"把家收拾干净再喊内核复位",看门狗负责"init 罢工时强行拉闸" ------三层协作,共同保证按下重启键后机器一定会回来。