[TOC]

kernel/reboot.c 系统重启与关机(System Reboot and Shutdown) 有序关闭系统的协调中枢

历史与背景

这项技术是为了解决什么特定问题而诞生的?

kernel/reboot.c 中的代码是Linux内核中负责处理系统级重启(reboot)、关机(halt)和断电(poweroff)的核心逻辑。这项技术的诞生是为了解决在一个多任务、多组件的复杂操作系统中,如何安全、有序地关闭系统这一根本性问题。

简单地切断电源会对系统造成严重损害,尤其是在文件系统层面。一个有序的关闭过程是必需的,以应对以下挑战:

  1. 数据一致性:现代操作系统大量使用写缓存(write cache)来提升性能。在关闭前,必须确保所有缓存中的数据(特别是文件系统元数据和用户数据)都被完整地写入持久存储,否则会导致文件损坏或数据丢失。
  2. 服务优雅退出:系统中的服务和驱动程序在关闭前可能需要执行清理操作,例如通知网络对端连接将中断、保存设备状态、或将设备置于安全模式。
  3. 硬件状态管理:需要一种机制来最终告诉硬件执行重启或断电的物理操作。这个过程因架构(x86, ARM等)和平台(BIOS, UEFI, ACPI)的不同而有很大差异。
  4. 提供统一接口:内核需要为用户空间程序(如shutdown, reboot命令)提供一个统一的、与硬件无关的系统调用接口,以触发关闭流程。

kernel/reboot.c 就是这个协调中枢,它提供了reboot()系统调用,并负责编排整个有序关闭的流程。

它的发展经历了哪些重要的里程碑或版本迭代?

reboot()系统调用是源自早期Unix的传统API,其在Linux中的实现随着内核的复杂化而不断演进。

  • 基本实现:早期内核的实现相对简单,主要关注于同步文件系统和调用特定于体系结构的重启指令。
  • 通知链(Notifier Chain)的引入:这是一个关键的里程碑。内核引入了reboot_notifier_list。这允许内核中的任何子系统或驱动程序注册一个回调函数,在系统重启流程的特定阶段被调用。这极大地增强了关闭过程的模块化和健壮性,使得各个组件可以独立地管理自己的关闭逻辑。
  • 与ACPI/APM的集成:随着ACPI(高级配置与电源接口)取代APM成为主流,kernel/reboot.c与ACPI子系统的集成变得至关重要。现在,最终的断电操作通常是通过调用ACPI代码来让主板进入S5(软关机)状态实现的。
  • “Magic”常量的引入:为了防止程序因内存错误等原因意外调用到reboot()这个破坏性极大的系统调用,内核为其增加了“魔法数字”校验。调用者必须传递特定的、无意义的常量值,否则调用会失败。
  • PID命名空间支持:自Linux 3.4起,在PID命名空间内调用reboot()会“重启”该命名空间,即终止该命名空间内的init进程,这对于容器管理非常有用。

目前该技术的社区活跃度和主流应用情况如何?

kernel/reboot.c 是内核中一个极其核心和稳定的组件。

  • 主流应用:所有用户空间用于关闭或重启系统的命令(shutdown, reboot, halt, poweroff,以及通过systemctl触发的相应操作)最终都会通过C库调用到内核的reboot()系统调用。 它是所有Linux系统正常关闭流程的必经之路。

核心原理与设计

它的核心工作原理是什么?

kernel/reboot.c 的核心是一个由reboot()系统调用触发的、精心设计的有序关闭序列。

  1. 用户空间触发:用户空间的程序调用 reboot(int cmd)cmd 参数指定了期望的操作,如 LINUX_REBOOT_CMD_RESTART (重启), LINUX_REBOOT_CMD_HALT (停机), 或 LINUX_REBOOT_CMD_POWER_OFF (断电)。
  2. 权限与参数检查:内核首先检查调用进程是否拥有足够的权限(CAP_SYS_BOOT),并验证传入的“魔法数字”是否正确,以确保调用的意图是明确的。
  3. 执行通知链 (Notifier Chain):这是优雅关闭的关键步骤。内核会调用 blocking_notifier_call_chain(&reboot_notifier_list, ...)。 这会遍历一个链表,依次执行所有已注册的回调函数。 许多关键子系统都会在此注册:
    • 文件系统:执行最后的同步操作。
    • 设备驱动:将硬件置于安全状态。
    • Watchdog驱动:通知硬件看门狗即将进行计划内的重启,避免其误触发硬件复位。
  4. 关闭设备:在通知链执行后,内核会调用device_shutdown()来系统性地关闭系统中所有设备。
  5. 迁移至单一CPU:在多核系统中,内核会执行migrate_to_reboot_cpu(),将执行流强制迁移到一个特定的“主”CPU上,并停止其他所有CPU的活动,以确保后续的硬件操作在一个可预测的环境中进行。
  6. 调用体系结构特定代码:最后,通用的内核代码会调用特定于硬件架构的函数(位于arch/目录下,如machine_restart(), machine_power_off())来执行最终的物理操作。 例如,在x86平台上,这可能意味着向ACPI或键盘控制器发送命令,或者直接执行一条能让CPU复位的汇编指令。

它的主要优势体现在哪些方面?

  • 有序与安全:通过通知链机制,确保了系统各组件能以正确的顺序优雅关闭,最大限度地保护数据和硬件。
  • 抽象与可移植性:将通用的关闭流程与特定于平台的硬件操作分离开来,使得上层逻辑可以跨多种硬件架构复用。
  • 健壮性:魔法数字等机制提供了一定程度的保护,防止意外的重启。

它存在哪些已知的劣势、局限性或在特定场景下的不适用性?

  • 依赖所有组件的正确实现:有序关闭的成功,依赖于所有注册了reboot_notifier的驱动都能正确、快速地完成其清理工作。任何一个回调函数的挂起都可能导致整个关闭过程卡住。
  • 无法应对内核完全锁死:如果内核已经发生严重死锁或崩溃(Kernel Panic),reboot()系统调用可能根本无法被执行。在这种情况下,系统将依赖于硬件看门狗或需要手动干预。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?

reboot()系统调用是所有需要从用户空间发起有序系统关闭场景的唯一标准方案

  • 标准关机命令shutdown -h nowsystemctl poweroff
  • 标准重启命令rebootsystemctl reboot
  • 自动化运维:在需要自动进行系统维护(如内核升级后)的脚本中,最后一步通常是调用reboot命令。
  • ACPI事件处理:当用户按下机箱上的物理电源按钮时,acpid守护进程会捕捉到这个ACPI事件,并调用shutdown命令来触发有序关闭。

是否有不推荐使用该技术的场景?为什么?

  • 内核或系统完全无响应:在这种情况下,用户空间的命令无法执行,reboot()系统调用也无法被触达。此时需要使用更低级的恢复手段。
  • 需要立即、强制重启:在某些极端情况下,如果怀疑有序关闭过程会失败或耗时过长,可能会选择强制重启。但这通常被视为最后手段。

对比分析

请将其 与 其他相似技术 进行详细对比。

存在多种重启系统的方式,它们处于不同的层次,适用于不同的场景。

特性 reboot() 系统调用 硬件看门狗 (Watchdog Timer) Magic SysRq Key 带外管理 (e.g., IPMI)
触发方式 用户空间软件调用。 内核/软件停止“喂狗”导致硬件定时器超时。 特殊的键盘组合键 (Alt+SysRq+B)。 通过独立的网络接口发送远程命令。
优雅程度 非常优雅。执行完整的、有序的关闭流程。 不优雅。是硬复位,不进行任何软件清理,可能导致数据损坏。 不优雅。绕过大部分关闭流程,直接触发重启,仅比硬复位稍好。 不优雅。通常是模拟按下电源或复位按钮,是硬复位。
可靠性 依赖于内核正常运行。内核崩溃则无效。 非常高。只要硬件正常,即使内核完全死锁也能触发。 较高。只要键盘驱动和中断处理等底层内核功能尚存,就有可能工作。 极高。完全独立于主机的操作系统和电源状态。
主要用途 常规的、计划内的系统关闭和重启。 内核完全锁死等软件故障中自动恢复。 在系统严重卡顿、无响应时,由管理员进行手动紧急恢复。 对物理服务器进行远程的、最终的电源控制和恢复。

kernel/reboot.c

register_restart_handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* register_restart_handler - 要调用的 Register 函数以重置系统
* @nb:要调用的处理程序函数的信息
* @nb->priority:处理程序优先级。处理程序应遵循以下准则来设置优先级。
* 0:最后的重启处理程序,重启功能有限
* 128:默认重启处理程序;如果预计没有其他重新启动处理程序可用,并且/或者如果重新启动功能足以重新启动整个系统,则使用
* 255:最高优先级的重启处理程序,将抢占所有其他重启处理程序
*
* 使用要调用的代码注册一个函数,以重新启动系统。
*
* 作为重启序列的最后一步,将从 machine_restart 调用已注册的函数(如果特定于架构的 machine_restart 函数调用 do_kernel_restart - 有关详细信息,请参阅下文)。
* 已注册的功能应立即重新启动系统。如果注册了多个函数,则 restart 处理程序优先级将首先选择将调用哪个函数。
*
* 重启处理程序应从非体系结构代码(通常来自驱动程序)注册。一个典型的使用案例是通过看门狗提供重启功能的系统。可能存在多个重启处理程序;例如,一个 restart 处理程序可能会重新启动整个系统,而另一个处理程序仅重新启动 CPU。在这种情况下,仅重新启动部分硬件的重新启动处理程序应以低优先级注册,以确保它仅在没有其他重新启动系统的方法可用时运行。
*
* 当前始终返回零,因为 atomic_notifier_chain_register() 始终返回零。
*/
int register_restart_handler(struct notifier_block *nb)
{
return atomic_notifier_chain_register(&restart_handler_list, nb);
}
EXPORT_SYMBOL(register_restart_handler);

kernel_shutdown_prepare: 准备内核关闭

此函数是内核关机或重启流程的第一阶段, 其核心原理是执行一个有序的、自上而下的”软件层面”关闭序列。它负责通知系统中的所有高级子系统和设备驱动程序”关机即将开始”, 让他们有机会执行各自的清理工作, 以此来”平息(quiesce)”整个系统, 为后续的硬件关机做好准备。

这个函数执行了三个关键的、按顺序排列的动作:

  1. 调用重启通知链 (Reboot Notifier Chain): 这是最重要的一步。内核维护了一个名为reboot_notifier_list的通知链, 其他内核子系统(如文件系统、网络栈、块设备驱动等)可以向这个链表注册一个回调函数。调用blocking_notifier_call_chain会同步地、挨个地调用链表中的每一个回调函数。这实现了一个观察者设计模式, 使得核心关机代码无需知道每个子系统的具体实现, 就能让它们各自完成清理任务, 例如:

    • 文件系统会将所有缓存的数据同步(sync)到物理存储介质(如SD卡、eMMC)中, 保证数据不丢失。
    • 网络驱动会关闭网络接口, 终止所有连接。
    • 任何需要保存状态的驱动程序都可以在这里执行其保存操作。
  2. 禁用用户模式帮助程序 (Usermode Helper): “用户模式帮助程序”是内核用来请求用户空间执行特定任务(如加载固件)的机制。usermodehelper_disable()函数会禁止内核创建任何新的用户空间进程。这是一个关键的”静默”步骤, 因为在关机过程中, 用户空间本身正在被拆除, 此时再尝试启动新进程会引入不确定性和竞争条件, 必须被阻止。

  3. 关闭设备 (Device Shutdown): device_shutdown()函数会遍历系统设备模型中的所有设备, 并调用每个设备驱动程序提供的.shutdown()方法(如果驱动实现了这个方法)。这是比重启通知链更具体、更偏向硬件的通知。它给予每个驱动最后一次机会去执行硬件相关的关闭操作, 例如:

    • 向设备发送一个断电或复位命令。
    • 禁用设备中断。
    • 将设备置于一个已知的、安全的低功耗状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
* kernel_shutdown_prepare: 为内核关闭做准备.
* @state: 目标系统状态, 如 SYSTEM_POWER_OFF, SYSTEM_HALT 等.
*/
static void kernel_shutdown_prepare(enum system_states state)
{
/*
* 步骤1: 调用阻塞式通知链.
* &reboot_notifier_list 是一个全局的链表头, 挂载了所有关心重启/关机事件的监听者.
* 根据传入的 state, 决定广播的事件码是 SYS_HALT (暂停) 还是 SYS_POWER_OFF (关机/重启).
* 此调用会遍历链表, 挨个执行所有注册的清理函数.
*/
blocking_notifier_call_chain(&reboot_notifier_list,
(state == SYSTEM_HALT) ? SYS_HALT : SYS_POWER_OFF, NULL);
/*
* 步骤2: 设置全局的系统状态变量.
* 这使得内核的其他部分可以查询到系统当前正处于关机流程中.
*/
system_state = state;
/*
* 步骤3: 禁用用户模式帮助程序.
* 这会阻止内核在此之后尝试启动任何新的用户空间进程.
*/
usermodehelper_disable();
/*
* 步骤4: 关闭设备.
* 这个函数会遍历所有设备, 并调用其驱动提供的 .shutdown() 回调函数,
* 以执行特定于硬件的关机操作.
*/
device_shutdown();
}

unregister_sys_off_handler: 注销一个系统关闭处理程序

此函数的作用是从内核的关机/重启通知链中移除一个先前已注册的”系统关闭处理程序”(sys-off handler)。这是与register_sys_off_handler相配对的清理操作。

它的核心原理是根据处理程序注册时指定的类型(阻塞式或原子式), 在正确的通知链上执行注销操作, 然后释放为该处理程序分配的内存。这是一个标准的内核资源清理流程, 确保了在模块卸载或特定功能不再需要时, 不会留下悬空的、无效的回调函数指针, 从而维护了系统的稳定性和健壮性。

详细工作流程:

  1. 有效性检查: 函数首先通过IS_ERR_OR_NULL宏来检查传入的handler指针是否有效。如果是一个无效指针(NULL或错误编码的指针), 它会直接返回, 避免了后续的非法内存访问。
  2. 条件性注销: 它检查handler->blocking标志位。这个标志位是在注册处理程序时设定的, 它记录了该处理程序是被注册到了阻塞式通知链还是原子式通知链。
    • 如果blockingtrue, 它就调用blocking_notifier_chain_unregister在可休眠的通知链(如reboot_notifier_list)上注销处理程序。
    • 如果blockingfalse, 它就调用atomic_notifier_chain_unregister在不可休眠的原子通知链(如power_off_handler_list)上注销处理程序。
    • 这个条件判断确保了注销操作总是作用于正确的链表。
  3. 完整性检查: WARN_ON(err)是一个健全性检查。notifier_chain_unregister函数在正常情况下应该总是成功并返回0。如果它返回错误, 意味着内核的通知链数据结构可能已损坏, 这是一个非常严重的、不应该发生的问题。WARN_ON会在内核日志中打印一个警告信息, 以便开发者能够注意到这个异常情况。
  4. 内存释放: 在成功地从通知链中移除回调后, handler这个包装结构体本身也就不再需要了。函数最后调用free_sys_off_handler来释放当初为它分配的内存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* unregister_sys_off_handler - 注销一个sys-off处理程序
* @handler: 要注销的Sys-off处理程序
*
* 注销给定的sys-off处理程序.
*/
void unregister_sys_off_handler(struct sys_off_handler *handler)
{
int err;

/*
* 步骤1: 检查传入的 handler 指针是否有效.
* IS_ERR_OR_NULL 是一个宏, 用于检查指针是否为 NULL 或是一个编码为指针的错误码.
* 如果无效, 则直接返回, 不执行任何操作.
*/
if (IS_ERR_OR_NULL(handler))
return;

/*
* 步骤2: 根据处理程序的类型, 在正确的通知链上执行注销操作.
* handler->blocking 标志是在注册时设置的.
*/
if (handler->blocking)
/*
* 如果是阻塞式处理程序, 调用 blocking_notifier_chain_unregister
* 将其从可休眠的通知链中移除.
* handler->list 指向目标链表头, &handler->nb 是要被移除的通知块.
*/
err = blocking_notifier_chain_unregister(handler->list,
&handler->nb);
else
/*
* 如果是原子式处理程序, 调用 atomic_notifier_chain_unregister
* 将其从不可休眠的通知链中移除.
*/
err = atomic_notifier_chain_unregister(handler->list,
&handler->nb);

/*
* 步骤3: 健全性检查.
* 在正常情况下, 注销操作不应该失败 (err应该为0).
* 如果失败了, WARN_ON 会在内核日志中打印一条警告, 提示可能存在内核数据结构损坏等严重问题.
*/
WARN_ON(err);

/*
* 步骤4: 释放为 handler 结构体本身分配的内存.
* free_sys_off_handler 是一个辅助函数, 用于安全地释放内存.
*/
free_sys_off_handler(handler);
}
/*
* 将 unregister_sys_off_handler 函数导出, 使其对其他遵循GPL许可证的内核模块可用.
*/
EXPORT_SYMBOL_GPL(unregister_sys_off_handler);

最终的硬件关机序列

此代码片段展示了Linux内核关机流程的最后、最底层的两个步骤。它们负责执行从”完全静默的软件状态”到”物理电源关闭”的最终过渡。machine_power_off是这个过程的入口, do_kernel_power_off是它调用的核心执行器。


machine_power_off: 进入硬件关机前的最后准备

此函数是平台无关的关机序列的最后一站。它的核心原理是在执行真正的、不可逆的硬件关机操作之前, 对CPU状态进行最终的、最低级别的”静默”处理

工作流程与原理:

  1. 禁用本地中断 (local_irq_disable()): 这是最关键的第一步。它会无条件地禁用当前CPU核心上的所有中断。在关机流程的这一最末端, 系统不能再被任何外部事件(如定时器、设备中断)所打扰, 必须保证后续的指令序列能够不被中断地完整执行。
  2. 停止其他CPU (smp_send_stop()): 在多核(SMP)系统上, 此函数会向所有其他”非主”CPU发送一个核间中断(IPI), 指示它们进入一个无限的while(1)循环中并停止所有活动。这确保了只有一个CPU(即执行machine_power_off的这个CPU)在执行最后的关机指令, 其他所有核心都已被冻结, 不会产生任何干扰。
  3. 执行关机动作 (do_kernel_power_off()): 在确保整个CPU系统完全静默之后, 它调用do_kernel_power_off来执行真正的、平台相关的关机动作。
1
2
3
4
5
6
7
8
9
10
11
12
13
/*
* Power-off 简单地要求其他CPU停止执行任何活动(任务、中断).
* smp_send_stop() 实现了这一点. 当系统电源关闭时, 它会带走所有的CPU.
*/
void machine_power_off(void)
{
/* 步骤1: 禁用当前CPU核心的所有本地中断. 这是最后的屏障. */
local_irq_disable();
/* 步骤2: (仅多核系统)向所有其他CPU发送停止信号, 使其进入停机状态. */
smp_send_stop();
/* 步骤3: 调用平台相关的关机处理链. */
do_kernel_power_off();
}

do_kernel_power_off: 关机处理程序的调度器

此函数是硬件关机操作的最终分发器。它的核心原理是提供一个可扩展的、基于优先级和通知链的机制, 来调用一个或多个由平台或驱动程序注册的、负责执行物理断电的函数

工作流程与原理:

  1. 兼容旧版API (pm_power_off): 内核为了向后兼容, 仍然支持一个旧的、名为pm_power_off的全局函数指针。如果板级支持包(BSP)定义了这个函数指针, do_kernel_power_off会通过register_sys_off_handler动态地将这个旧的函数包装成一个符合新框架的”系统关闭处理程序”(sys_off_handler), 并注册它。
  2. 调用通知链 (atomic_notifier_call_chain): 这是现代内核的首选机制。它会遍历一个名为power_off_handler_list的通知链, 并调用所有注册在上面的处理函数。
    • 原子性: atomic_前缀意味着这个调用链是在一个原子上下文(中断已禁用)中执行的, 所有注册的回调函数都必须是原子的, 即绝不能休眠。
    • 可扩展性: 允许多个模块(例如, 一个电源管理芯片PMIC的驱动和一个看门狗驱动)都注册自己的关机回调。
    • 优先级: 新的sys_off框架允许为每个处理程序指定优先级, 以确保它们按正确的顺序被调用。
  3. 执行与终结: 在一个典型的关机流程中, 这个通知链中的某个函数(很可能就是那个由pm_power_off包装而来的函数)将会执行操作, 导致物理电源被切断。因此, atomic_notifier_call_chain将永远不会返回。后面的unregister_sys_off_handler调用只是为了代码逻辑的完整性, 以应对那些只执行了部分关机操作但未断电的异常情况。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* do_kernel_power_off - 执行内核电源关闭处理程序调用链
*
* 期望作为电源关闭序列的最后一步被调用.
*
* 如果一个电源关闭处理函数已被注册, 则立即关闭系统电源.
* 否则什么也不做.
*/
void do_kernel_power_off(void)
{
struct sys_off_handler *sys_off = NULL;

/*
* 步骤1: 兼容旧版API.
* 如果 pm_power_off (一个全局函数指针)被平台代码定义了.
*/
if (pm_power_off)
/*
* 将这个旧的函数指针包装成一个新的 sys_off 处理程序并注册.
* 这使得旧的平台代码能无缝地在新内核框架下工作.
*/
sys_off = register_sys_off_handler(SYS_OFF_MODE_POWER_OFF,
SYS_OFF_PRIO_DEFAULT,
legacy_pm_power_off, NULL);

/*
* 步骤2: 调用原子通知链.
* 遍历 power_off_handler_list, 调用所有注册的关机处理函数.
* 在一个成功的关机中, 某个处理函数会切断电源, 此函数将不会返回.
*/
atomic_notifier_call_chain(&power_off_handler_list, 0, NULL);

/*
* (仅在关机失败时可达) 注销为旧API临时创建的处理程序.
*/
unregister_sys_off_handler(sys_off);
}

kernel_restart 框架: 内核的有序重启最终阶段

这是一个在Linux内核内部用于执行强制性、有序重启的核心框架。它的作用是在接收到重启命令后(例如, 从用户空间reboot命令的最终执行, 或从内核其他部分的紧急调用), 以一个明确的、分阶段的、”不归路”式(point-of-no-return)的流程, 关闭内核的核心功能, 并最终触发平台相关的硬件复位

该框架的原理是确保在执行最后的硬件复位之前, 内核有机会通知其他子系统进行最后的准备工作, 将系统置于一个已知的、稳定的”半停机”状态, 并尽可能地保存用于事后调试的信息。这是一个从通用内核代码到特定于硬件的机器代码的优雅交接过程。

对于STM32H750这样的单核嵌入式系统, 这个流程的原理和步骤同样适用且至关重要:

  • 通知链 (restart_prep_handler_list): 即使没有多核, 系统中也可能有驱动程序需要在一个干净的重启前执行特定操作, 例如, 一个与外部非易失性存储器通信的驱动可能需要确保其硬件处于空闲状态。这个通知链为它们提供了这样的机会。
  • 迁移到重启CPU (migrate_to_reboot_cpu): 在单核系统上, 这个函数的作用演变为确保后续的关机代码在一个”安全”的上下文中执行, 通常会禁用抢占和中断, 防止在执行这最后几行关键代码时被意外打断。
  • 核心子系统关机 (syscore_shutdown): 这会调用STM32片上核心外设(如定时器、中断控制器)的晚期关机回调, 将它们置于一个已知的状态。
  • 日志转储 (kmsg_dump): 这是调试的关键。如果系统是因为一个内核错误(panic)而重启, 这是将内核日志环形缓冲区的内容转储到持久化存储(如果板级支持代码实现了这个功能, 例如写入Flash的一个特定分区)的最后机会。
  • 机器重启 (machine_restart): 这是最关键的平台特定步骤。对于绝大多数嵌入式系统, 包括STM32H750, 这个函数的最终实现几乎总是触发内部的看门狗定时器(Watchdog Timer)来强制复位整个SoC。这被认为是最可靠的硬件复位方式, 因为它能从许多软件无法恢复的硬件锁死状态中恢复系统。

restart_prep_handler_listdo_kernel_restart_prepare: 重启准备通知

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*
* BLOCKING_NOTIFIER_HEAD 宏:
* 这是一个静态初始化宏, 它在编译时创建一个名为 restart_prep_handler_list 的
* 通知链头部 (struct blocking_notifier_head).
* "BLOCKING" 意味着当调用这个链时, 调用者会同步地等待所有订阅者的回调函数执行完毕后才继续.
* "HEAD" 表示这是一个链表的头节点.
*
* 作用:
* 内核的其他驱动程序可以通过 notifier_chain_register(&restart_prep_handler_list, ...)
* 来注册一个回调函数. 在系统即将重启时, 这些回调函数会被依次调用,
* 给予这些驱动一个执行最后清理或准备工作的机会.
*/
static BLOCKING_NOTIFIER_HEAD(restart_prep_handler_list);

/*
* 静态函数: do_kernel_restart_prepare
* 这是一个简单的封装函数, 作用是调用上面的通知链.
*/
static void do_kernel_restart_prepare(void)
{
/*
* 调用 blocking_notifier_call_chain, 遍历 restart_prep_handler_list 链表,
* 并执行每一个已注册的回调函数.
* 0 和 NULL 是传递给回调函数的通用参数, 在此场景下没有特定含义.
*/
blocking_notifier_call_chain(&restart_prep_handler_list, 0, NULL);
}

kernel_restart: 内核重启的协调中心

这是暴露给内核其他部分使用的、用于启动重启流程的标准API。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
void kernel_restart_prepare(char *cmd)
{
blocking_notifier_call_chain(&reboot_notifier_list, SYS_RESTART, cmd);
system_state = SYSTEM_RESTART;
usermodehelper_disable();
device_shutdown();
}

/**
* kernel_restart - 重启系统
* @cmd: 指向包含重启时要执行的命令的缓冲区的指针, 或为 %NULL
*
* 关闭所有东西并执行一个干净的重启.
* 在中断上下文中调用此函数是不安全的.
*/
void kernel_restart(char *cmd)
{
/*
* 1. 第一次准备: 调用 kernel_restart_prepare(cmd).
* 这是一个更上层的准备函数(此处未提供源码), 它可能会处理传入的命令'cmd',
* 并执行一些与命令相关的初始准备工作.
*/
kernel_restart_prepare(cmd);
/*
* 2. 通知链准备: 调用 do_kernel_restart_prepare().
* 执行上面定义的通知链, 让所有订阅了重启事件的驱动程序执行它们的准备回调.
*/
do_kernel_restart_prepare();
/*
* 3. 迁移CPU: 调用 migrate_to_reboot_cpu().
* 在多核系统中, 这会停止所有其他CPU核心, 并将当前执行流迁移到一个指定的
* "重启CPU"上. 在单核系统上, 它会禁用抢占和中断, 确保后续代码的原子执行.
*/
migrate_to_reboot_cpu();
/*
* 4. 核心子系统关机: 调用 syscore_shutdown().
* 执行在内核核心子系统(如定时器, 时钟管理)中注册的晚期关机回调.
*/
syscore_shutdown();
/*
* 5. 打印紧急日志:
* 使用 pr_emerg 打印一条最高优先级的日志, 通知控制台系统即将重启.
*/
if (!cmd)
pr_emerg("Restarting system\n");
else
pr_emerg("Restarting system with command '%s'\n", cmd);
/*
* 6. 转储内核日志: 调用 kmsg_dump().
* 将内核的环形日志缓冲区(dmesg)转储出来. 如果平台支持,
* 这可能会被写入一个持久化的存储位置(如 pstore/ramoops), 用于事后调试.
*/
kmsg_dump(KMSG_DUMP_SHUTDOWN);
/*
* 7. 最终的硬件重启: 调用 machine_restart(cmd).
* 这是整个流程的终点. 这个函数是平台和架构特定的.
* 它不会返回.
* 对于STM32H750, 这个函数最终会通过写入寄存器来触发看门狗定时器的立即复位,
* 从而实现整个芯片的硬件重启.
*/
machine_restart(cmd);
}
/*
* 将 kernel_restart 函数导出, 使其对其他遵循GPL许可证的内核模块可用.
*/
EXPORT_SYMBOL_GPL(kernel_restart);

kernel_power_off: 关闭系统电源

此函数负责执行一个完整、有序的系统关机流程。它的核心原理不是简单地切断电源, 而是通过一个预定义的、分阶段的序列, 逐级关闭内核的各个子系统, 确保所有数据被妥善处理、所有硬件进入安全状态, 最后才调用平台特定的代码来执行物理上的断电或进入等效的深度休眠状态。这个有序的过程对于保证文件系统完整性和硬件设备状态的一致性至关重要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
static void do_kernel_power_off_prepare(void)
{
blocking_notifier_call_chain(&power_off_prep_handler_list, 0, NULL);
}

/**
* kernel_power_off - 关闭系统电源
*
* 关闭所有部件, 并执行一次干净的系统断电操作.
*/
void kernel_power_off(void)
{
/*
* 调用 kernel_shutdown_prepare, 并传入 SYSTEM_POWER_OFF 状态.
* 作用: 这是关机流程的第一步, 用于通知整个系统“关机即将发生”.
* 它会通过一个通知链(notifier chain)来调用所有注册了关机回调的子系统和驱动程序
* (例如, 文件系统驱动会同步所有数据到磁盘, 网络设备会关闭连接等), 让他们有机会执行清理工作.
*/
kernel_shutdown_prepare(SYSTEM_POWER_OFF);
/*
* 调用 do_kernel_power_off_prepare.
* 作用: 这是一个特定的钩子(hook)函数, 允许特定的驱动或架构代码执行专门为"电源关闭"这一特定场景准备的操作.
* 默认情况下它可能为空, 但可以被定制.
*/
do_kernel_power_off_prepare();
/*
* 调用 migrate_to_reboot_cpu.
* 作用: 在多核系统中, 此函数会将当前的执行流程迁移到一个指定的、专门用于处理重启/关机过程的CPU核心上,
* 并将其他所有CPU核心置于离线状态, 以防止它们干扰后续的关机流程.
* 在STM32H750这样的单核系统上, 此函数实际上是一个无操作(no-op), 它会立即返回, 因为只有一个核心, 无需也无法迁移.
*/
migrate_to_reboot_cpu();
/*
* 调用 syscore_shutdown.
* 作用: 关闭那些必须在关机流程非常后期才能关闭的核心系统设备(syscore devices).
* 这些设备通常是像中断控制器(NVIC), 定时器, DMA等基础外设, 它们需要维持运行直到最后一刻.
*/
syscore_shutdown();
/*
* 调用 pr_emerg 打印一条最高优先级的内核消息.
* 作用: 在系统日志中留下一条明确的记录, 表明系统即将断电.
*/
pr_emerg("Power down\n");
/*
* 调用 pr_flush, 尝试在1000毫秒的超时时间内, 将内核日志缓冲区的内容强制刷新到控制台.
* 作用: 在嵌入式系统中, 控制台(如UART)通常很慢. 这一步是确保"Power down"这条消息以及之前的任何其他紧急日志
* 能够被物理地发送出去, 而不会因为系统立即断电而丢失在缓冲区中.
*/
pr_flush(1000, true);
/*
* 调用 kmsg_dump, 并传入关机原因.
* 作用: 将内核日志环形缓冲区(kernel log ring buffer)的全部内容转储到一个预定义的位置.
* 这是一种用于事后调试(post-mortem debugging)的机制. 在系统意外关机或崩溃时,
* 这些日志可以帮助开发者分析问题原因. 在嵌入式系统中, 它可能被配置为转储到一块特殊的RAM或非易失性存储器中.
*/
kmsg_dump(KMSG_DUMP_SHUTDOWN);
/*
* 调用 machine_power_off.
* 作用: 这是关机流程的最后一步, 它会调用一个平台相关的函数指针(pm_power_off)来执行真正意义上的硬件断电.
* 在STM32H750上, 这个函数指针在内核初始化时会被板级支持包(BSP)设置为一个特定的函数,
* 该函数会通过操作STM32的电源管理单元(PWR)的寄存器, 来切断主电源域或使系统进入一个无法被唤醒的深度睡眠模式, 从而实现关机.
*/
machine_power_off();
}
/*
* 将 kernel_power_off 函数导出, 使其对其他遵循GPL许可证的内核模块可用.
* 例如, 一个处理电源按钮的驱动可能会调用此函数.
*/
EXPORT_SYMBOL_GPL(kernel_power_off);

“有序”关机/重启框架: 委托用户空间, 内核托底

这一组函数共同构成了Linux内核中实现**”有序”(orderly)关机和重启的核心机制。其核心原理是将关机/重启的主要任务委托给用户空间的程序, 同时保留一个内核级的、强制执行的后备方案(fallback)**。

这种设计的哲学是:

  • 优雅处理优先: 一个”有序”的关机/重启过程不仅仅是断电或复位。它需要正确地停止服务、同步文件系统(将缓存数据写入磁盘)、卸载网络接口、执行清理脚本等。这些复杂的、策略性的任务最适合由用户空间的init系统(如systemd, sysvinit, 或嵌入式中常见的BusyBox init)来管理。因此, 内核的首选策略是请求用户空间来执行这些操作。
  • 内核提供最后保障: 内核也知道用户空间可能会因为各种原因(例如, init进程卡死, 文件系统损坏)而失败。在这种情况下, 为了响应一个紧急的关机/重启请求(例如, 来自hw_protection_trigger), 内核必须有一个”B计划”。这个B计划就是跳过用户空间, 直接执行内核级的、更底层的强制关机/重启操作。

这个框架通过以下几个关键部分实现这一原理:

  1. run_cmd: 这是内核空间到用户空间的桥梁。它使用call_usermodehelper API, 创建一个用户空间进程来执行一个指定的命令(如/sbin/poweroff)。
  2. __orderly_poweroff / __orderly_reboot: 这是策略中心。它们首先尝试通过run_cmd来执行优雅的关机/重启。如果用户空间程序启动失败(run_cmd返回错误), 并且这是一个强制请求, 它们就会立即触发内核的后备方案。
  3. Workqueue (poweroff_work): 关机操作被封装在一个工作项中。这确保了关机请求可以在一个安全的、非紧急的内核线程上下文(process context)中执行, 避免了在可能持有锁或处于中断上下文的调用路径中直接启动一个复杂的用户空间进程。

在STM32H750这样的单核嵌入式系统上, 这个框架同样适用且重要:

  • 用户空间: 运行Linux的STM32系统通常会有一个基于BusyBox的最小化用户空间, 它提供了/sbin/poweroff/sbin/reboot这两个程序。
  • init进程: 这些程序的工作通常是向系统的init进程(PID 1)发送一个信号, init进程会负责执行预定义的关机脚本。
  • 后备方案: 如果这个最小化的用户空间出现问题, 内核的后备方案(kernel_restart通常会触发看门狗复位)是恢复系统的唯一可靠途径。

run_cmd: 内核到用户空间的命令执行器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/*
* run_cmd: 执行一个用户空间命令的内部辅助函数.
* @cmd: 一个包含命令及其参数的字符串.
* @return: 成功时返回0, 失败时返回负的错误码.
*/
static int run_cmd(const char *cmd)
{
/*
* argv: 一个指向字符串数组的指针, 用于存放解析后的命令和参数.
*/
char **argv;
/*
* envp: 一个静态的环境变量数组.
* 这为即将执行的用户空间程序提供了一个最小化的、标准的环境,
* 确保它能够找到像/sbin, /bin里的基本工具.
* 最后的NULL是数组的结束标记.
*/
static char *envp[] = {
"HOME=/",
"PATH=/sbin:/bin:/usr/sbin:/usr/bin",
NULL
};
int ret;

/*
* argv_split: 将输入的命令字符串 cmd 按照空格分割成一个字符串数组.
* GFP_KERNEL: 内核内存分配标志, 表示如果内存不足可以休眠.
*/
argv = argv_split(GFP_KERNEL, cmd, NULL);
if (argv) {
/*
* call_usermodehelper: 这是内核用于启动用户空间程序的核心API.
* argv[0]: 要执行的程序路径 (例如 "/sbin/poweroff").
* argv: 完整的参数列表 (包括程序名自身).
* envp: 环境变量.
* UMH_WAIT_EXEC: 一个标志, 告诉内核等待, 直到用户空间程序
* 成功开始执行(execve系统调用完成)之后再返回.
* 注意, 它不等待程序执行结束.
*/
ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
/*
* argv_free: 释放由 argv_split 分配的内存.
*/
argv_free(argv);
} else {
/*
* 如果内存分配失败.
*/
ret = -ENOMEM;
}

return ret;
}

__orderly_reboot__orderly_poweroff: 策略中心与后备方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#define POWEROFF_CMD_PATH_LEN  256
static char poweroff_cmd[POWEROFF_CMD_PATH_LEN] = "/sbin/poweroff";
static const char reboot_cmd[] = "/sbin/reboot";


/*
* __orderly_reboot: 执行有序重启.
*/
static int __orderly_reboot(void)
{
int ret;

/*
* 尝试通过用户空间程序 /sbin/reboot 来执行重启.
*/
ret = run_cmd(reboot_cmd);

/*
* 如果 run_cmd 失败 (ret != 0), 说明用户空间程序未能启动.
*/
if (ret) {
pr_warn("Failed to start orderly reboot: forcing the issue\n");
/*
* 启动后备方案:
* 1. emergency_sync(): 尝试将所有文件系统缓存紧急同步到存储设备,
* 这是保存数据的最后机会.
* 2. kernel_restart(NULL): 调用内核的强制重启函数. 在STM32上,
* 这通常最终会触发看门狗复位.
*/
emergency_sync();
kernel_restart(NULL);
}

return ret;
}

/*
* __orderly_poweroff: 执行有序关机.
* @force: 一个布尔值, 如果为true, 在用户空间命令失败后会启动强制后备方案.
*/
static int __orderly_poweroff(bool force)
{
int ret;

/*
* 尝试通过用户空间程序 /sbin/poweroff 来执行关机.
*/
ret = run_cmd(poweroff_cmd);

/*
* 如果 run_cmd 失败, 并且调用者要求强制执行.
*/
if (ret && force) {
pr_warn("Failed to start orderly shutdown: forcing the issue\n");
/*
* 启动后备方案:
* 1. emergency_sync(): 紧急同步文件系统.
* 2. kernel_power_off(): 调用内核的强制关机函数. 在STM32上,
* 这通常会通过GPIO控制外部PMIC来断电.
*/
emergency_sync();
kernel_power_off();
}

return ret;
}

工作队列封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
* 全局变量, 用于从提交工作的函数传递 "force" 参数给工作函数.
*/
static bool poweroff_force;

/*
* poweroff_work_func: 这是实际执行关机操作的工作函数.
* @work: 指向 work_struct 的指针.
*
* 这个函数将在内核的工作队列线程中被执行.
*/
static void poweroff_work_func(struct work_struct *work)
{
/*
* 调用有序关机函数, 并传入之前保存的 force 标志.
*/
__orderly_poweroff(poweroff_force);
}

/*
* DECLARE_WORK 宏:
* 这是一个静态初始化宏, 它在编译时创建一个名为 poweroff_work 的
* struct work_struct 实例, 并将其处理函数指定为 poweroff_work_func.
* 当需要关机时, 其他代码会调用 schedule_work(&poweroff_work) 来提交这个工作.
*/
static DECLARE_WORK(poweroff_work, poweroff_work_func);

orderly_powerofforderly_reboot: 安全的、可从任何上下文调用的系统关闭/重启接口

这两个函数是Linux内核中用于启动一个有序的(graceful)、干净的系统关闭或重启的标准公共API。它们的核心原理是利用内核的工作队列(workqueue)机制, 将实际的、可能导致休眠或阻塞的关闭/重启操作, 从当前的、可能是原子(atomic)的执行上下文中, 安全地延迟(defer)到一个专用的、安全的内核工作线程中去执行

这个”上下文延迟”机制是理解这两个函数的关键, 它解决了内核编程中的一个核心问题:

  • 问题: 像文件系统同步、设备卸载等关机步骤, 都是需要花费时间并且可能会导致当前任务”休眠”(sleep)或”阻塞”(block)的。然而, 内核中的许多代码路径, 例如中断处理程序或持有自旋锁(spinlock)的代码, 都处于**原子上下文(atomic context)**中, 在这种上下文中休眠是绝对禁止的, 会导致系统崩溃。
  • 解决方案: orderly_powerofforderly_reboot本身并不执行任何关闭操作。它们只做一件非常快速且安全的事情: 调用schedule_work。这个函数会将一个预先定义好的”工作项”(poweroff_workreboot_work)放入一个全局的工作队列中。稍后, 一个专门的内核线程(kworker)会从这个队列中取出该工作项, 并在其自身的、安全的**进程上下文(process context)**中执行与该工作项关联的处理函数(poweroff_work_funcreboot_work_func)。在这个线程中, 执行休眠或阻塞操作是完全安全的。

因此, 任何内核代码, 无论它处于何种危险的上下文, 都可以安全地调用orderly_powerofforderly_reboot来”请求”一次关机或重启, 而不必担心违反上下文规则。


orderly_poweroff: 请求有序关机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* orderly_poweroff - 触发一个有序的系统关机
* @force: 如果命令执行失败, 是否强制关机
*
* 可在任何上下文中调用此函数以触发系统关机.
* 如果有序关机失败, 它将强制立即关机.
*/
void orderly_poweroff(bool force)
{
/*
* 如果调用者请求强制执行 (force=true), 就设置一个全局标志 poweroff_force.
* 这里的注释 "do not override the pending 'true'" 意味着, 一旦这个标志被设置为true,
* 后续的、force=false 的调用也不会将其清除. 这确保了"强制"的意图不会丢失.
* 这个标志会传递给工作线程中的实际处理函数, 告诉它如果优雅关机失败, 就必须采取强制措施.
*/
if (force) /* do not override the pending "true" */
poweroff_force = true;
/*
* 核心操作: 调度 poweroff_work 工作项.
* 这会将 poweroff_work 放入工作队列, 内核的kworker线程稍后会执行其处理函数.
* 这个调用本身是非阻塞的, 会立即返回.
*/
schedule_work(&poweroff_work);
}
/* 将此函数导出, 使其对其他内核模块可用. */
EXPORT_SYMBOL_GPL(orderly_poweroff);

orderly_reboot: 请求有序重启

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*
* 静态函数: reboot_work_func
* 这是与 reboot_work 工作项关联的处理函数.
* @work: 指向被执行的 work_struct 的指针.
* 当 kworker 线程执行 reboot_work 时, 这个函数会被调用.
*/
static void reboot_work_func(struct work_struct *work)
{
/*
* 在安全的工作线程上下文中, 调用 __orderly_reboot() (一个内部函数),
* 它会启动实际的、有序的重启流程.
*/
__orderly_reboot();
}

/*
* DECLARE_WORK 宏:
* 这是一个静态初始化宏, 它在编译时创建一个名为 reboot_work 的
* struct work_struct 实例, 并将其处理函数初始化为 reboot_work_func.
* 这是定义工作项的标准、便捷方式.
*/
static DECLARE_WORK(reboot_work, reboot_work_func);

/**
* orderly_reboot - 触发一个有序的系统重启
*
* 可在任何上下文中调用此函数以触发系统重启.
* 如果有序重启失败, 它将强制立即重启.
*/
void orderly_reboot(void)
{
/*
* 核心操作: 调度 reboot_work 工作项.
* 将重启请求提交给内核工作队列, 以便在安全的上下文中执行.
*/
schedule_work(&reboot_work);
}
/* 将此函数导出, 使其对其他内核模块可用. */
EXPORT_SYMBOL_GPL(orderly_reboot);

硬件保护执行函数: 最后的强制措施

这一组函数是上一节中介绍的硬件保护框架的执行部分。它们是**”定时炸弹”的”引爆”代码**。当hw_failure_emergency_schedule调度了一个延迟工作项后, 如果系统未能成功地有序关机或重启, 那么在超时之后, 内核的工作队列就会执行这里的hw_failure_emergency_action_func函数。

这个函数的核心原理是采用一个多层次的、逐步升级的强制手段来确保系统最终能够被关闭或重启, 无论系统处于多么不稳定的状态。它是一个不计后果的、以保护硬件为最高优先级的最终执行者。


hw_protection_action_str: 将动作枚举转换为字符串

这是一个简单的辅助函数, 作用是将enum hw_protection_action的数值转换为人类可读的字符串, 专门用于日志记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
* 静态函数: hw_protection_action_str
* @action: 一个 enum hw_protection_action 类型的值.
* @return: 一个指向描述该动作的静态字符串的指针.
*/
static const char *hw_protection_action_str(enum hw_protection_action action)
{
/*
* 使用一个标准的 switch 语句来进行转换.
*/
switch (action) {
case HWPROT_ACT_SHUTDOWN:
return "shutdown";
case HWPROT_ACT_REBOOT:
return "reboot";
default:
/*
* default 分支确保了即使传入一个无效的枚举值, 函数也能安全地返回一个有意义的字符串.
*/
return "undefined";
}
}

hw_failure_emergency_action_func: 紧急动作的最终执行者

这是整个硬件保护框架的最后一道防线。当有序关机/重启失败, 并且超时定时器到期后, 这个函数会被内核的工作队列线程调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/*
* 这是一个静态全局变量, 用于在调度和执行之间传递要执行的动作.
* hw_failure_emergency_schedule() 会设置这个变量.
* hw_failure_emergency_action_func() 会读取这个变量.
*/
static enum hw_protection_action hw_failure_emergency_action;

/**
* hw_failure_emergency_action_func - 在已知延迟后执行的紧急动作工作函数
* @work: 与紧急动作函数关联的 work_struct
*
* 此函数在非常危急的情况下被调用, 以在一个可配置的超时后强制
* 内核关机或重启.
*/
static void hw_failure_emergency_action_func(struct work_struct *work)
{
/*
* 使用辅助函数获取要执行的动作的字符串表示, 用于日志记录.
*/
const char *action_str = hw_protection_action_str(hw_failure_emergency_action);

/*
* 打印一条最高优先级的紧急日志, 告知系统管理员或开发者发生了什么.
* 这条日志是事后调试的关键线索, 表明有序关机/重启已经失败,
* 系统现在将尝试强制执行.
*/
pr_emerg("Hardware protection timed-out. Trying forced %s\n",
action_str);

/*
* 我们在紧急动作等待期满后到达了这里. 这意味着
* orderly_poweroff/reboot 由于某种原因未能关闭系统.
*
* 尝试立即关闭系统, 如果可能的话.
*/

/*
* --- 第一级强制措施 ---
* 根据之前保存的动作, 调用更直接、更少清理的内核函数.
*/
if (hw_failure_emergency_action == HWPROT_ACT_REBOOT)
/*
* kernel_restart(): 尝试立即重启系统, 会跳过许多正常的关闭步骤.
*/
kernel_restart(NULL);
else
/*
* kernel_power_off(): 尝试立即关闭系统电源.
*/
kernel_power_off();

/*
* --- 最坏情况下的最终措施 ---
* 如果 kernel_restart() 或 kernel_power_off() 由于某种原因被卡住或失败
* (这在理论上是可能的, 如果系统已经极度不稳定), 代码会继续执行到这里.
* 这几乎意味着内核已经完全失控.
*/
pr_emerg("Hardware protection %s failed. Trying emergency restart\n",
action_str);
/*
* emergency_restart(): 这是最强制的重启方式.
* 它通常会绕过几乎所有的内核代码, 直接触发平台特定的硬件复位机制,
* 例如在STM32上, 它最常见的实现就是触发看门狗定时器(watchdog)
* 来强制重启整个芯片, 就像按下了物理复位按钮一样.
*/
emergency_restart();
}

硬件保护触发框架: 最后的硬件安全防线

这是一个内核级的、最后的硬件保护框架。它的核心作用是在检测到可能导致永久性硬件损坏的严重故障(例如, 调节器欠压、芯片过热)时, 启动一个健壮的、两阶段的紧急系统关机或重启程序

该框架的原理是**”先尝试优雅处理, 同时设置一个强制执行的最后通牒”**, 以确保无论系统处于何种不稳定的状态, 保护硬件的最终目标都能达成:

  1. 第一阶段: 尝试”有序”关机/重启 (Attempt Graceful Action): 当hw_protection_trigger被调用时, 它会立即调用orderly_poweroff()orderly_reboot()。这会启动正常的系统关闭流程, 允许内核尝试同步文件系统、卸载设备、执行清理程序等。这是一个”君子协定”, 试图以最安全、最干净的方式关闭系统。

  2. 第二阶段: 调度”强制”关机/重启 (Schedule Forced Action): 与此同时, 它调用hw_failure_emergency_schedule()调度一个延迟的内核工作项(delayed work)。这个工作项就像一个”定时炸弹”或”死士”。

    • 如果第一阶段的有序关机成功了, 系统将在定时器到期前就已经断电或重启, 那么这个工作项就永远没有机会执行。
    • 但是, 如果系统因为严重的故障已经处于半死机状态, 导致有序关机流程被卡住, 那么在ms_until_forced毫秒的超时之后, 内核的工作队列(workqueue)仍然会调度并执行这个延迟工作项。该工作项的处理函数(hw_failure_emergency_action_func)会执行一个强制的、不计后果的关机或重启(例如, 直接触发看门狗复位), 确保硬件最终被断电或重置。

为了防止在混乱的故障期间被重复触发, 整个框架使用了一个原子计数器(atomic_t allow_proceed)作为一次性保险丝, 确保只有第一次触发是有效的。

对于STM32H750这样的单核嵌入式系统, 这个框架的原理和实现同样至关重要:

  • 原子操作 (atomic_t): 即使只有一个CPU核心, atomic_dec_and_test也能保证操作的原子性, 防止在任务上下文和一个可能触发此操作的中断上下文之间发生竞态条件。
  • 紧急日志 (pr_emerg): pr_emerg是最高优先级的内核日志。在系统即将崩溃时, 这条信息有最大的机会被成功地通过串口等控制台输出, 为事后调试提供关键的线索。
  • 延迟工作 (delayed_work): 这依赖于内核的定时器中断和工作队列调度器。即使系统主应用逻辑已经卡死, 只要内核调度器和定时器中断还能工作, 这个最后的强制关机/重启操作就能被执行。
  • orderly_poweroff/reboot: 在STM32上, 这些函数最终会调用到板级支持代码中实现的pm_power_offmachine_restart函数, 这些函数会执行平台特定的操作, 例如通过GPIO控制外部PMIC断电, 或触发内部的看门狗定时器来复位系统。

hw_protection_trigger__hw_protection_trigger: 触发硬件保护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
/**
* enum hw_protection_action - 硬件保护动作
* @HWPROT_ACT_DEFAULT: 应采取默认动作. 默认为HWPROT_ACT_SHUTDOWN, 但可被覆盖.
* @HWPROT_ACT_SHUTDOWN: 系统应为硬件保护而关机(断电).
* @HWPROT_ACT_REBOOT: 系统应为硬件保护而重启.
*/
enum hw_protection_action { HWPROT_ACT_DEFAULT, HWPROT_ACT_SHUTDOWN, HWPROT_ACT_REBOOT };

/*
* __hw_protection_trigger 的前向声明, 因为它在 hw_protection_trigger 中被使用.
*/
void __hw_protection_trigger(const char *reason, int ms_until_forced,
enum hw_protection_action action);

/**
* hw_protection_trigger - 触发默认的紧急系统硬件保护动作
* @reason: 要打印的紧急关机或重启的原因.
* @ms_until_forced: 在强制触发关机或重启前, 等待有序操作的时间.
* 负值会禁用强制关机或重启.
*
* 启动一个紧急系统关机或重启, 以保护硬件免受进一步损害.
* 具体采取的动作在运行时是可控的, 默认为关机.
*/
static inline void hw_protection_trigger(const char *reason, int ms_until_forced)
{
/*
* 这是一个内联的便利性封装函数.
* 它直接调用核心实现 __hw_protection_trigger, 并将动作指定为 HWPROT_ACT_DEFAULT.
* 这使得内核其他部分可以方便地以默认方式触发保护, 而无需关心具体的动作是什么.
*/
__hw_protection_trigger(reason, ms_until_forced, HWPROT_ACT_DEFAULT);
}

/*
* DECLARE_DELAYED_WORK 宏:
* 这是一个静态初始化宏, 它在编译时创建一个名为 hw_failure_emergency_action_work 的
* struct delayed_work 实例, 并将其处理函数指定为 hw_failure_emergency_action_func.
* 这个工作项就是我们"定时炸弹"的载体.
*/
static DECLARE_DELAYED_WORK(hw_failure_emergency_action_work,
hw_failure_emergency_action_func);

/**
* __hw_protection_trigger - 触发一个紧急系统关机或重启
* @reason: ...
* @ms_until_forced: ...
* @action: ...
*
* ...
* 注意: 如果一个保护性关机或重启已经挂起, 即使之前的请求给了一个很长的超时时间,
* 后续的请求也会被忽略.
*/
void __hw_protection_trigger(const char *reason, int ms_until_forced,
enum hw_protection_action action)
{
/*
* 定义一个静态的原子计数器, 并初始化为1.
* 'static' 确保它在多次函数调用之间保持其值.
* 'atomic_t' 确保对它的操作是原子的, 不会被中断或抢占打断.
* 这就是实现"一次性保险丝"的关键.
*/
static atomic_t allow_proceed = ATOMIC_INIT(1);

/*
* 如果请求的动作是默认动作, 就从全局变量 hw_protection_action 中获取实际的动作.
* 这个全局变量可以通过 sysfs 等方式在运行时配置.
*/
if (action == HWPROT_ACT_DEFAULT)
action = hw_protection_action;

/*
* 使用 pr_emerg 打印一条最高优先级的日志, 包含动作和原因.
*/
pr_emerg("HARDWARE PROTECTION %s (%s)\n",
hw_protection_action_str(action), reason);

/*
* "保险丝"机制: atomic_dec_and_test 会原子地将计数器减1, 并测试结果是否为0.
* - 第一次调用时: 计数器从1变为0, 测试结果为true, 函数继续执行.
* - 后续所有调用: 计数器从0变为-1, -1变为-2..., 测试结果总是false, 函数立即返回.
* 这确保了关机流程只会被启动一次.
*/
if (!atomic_dec_and_test(&allow_proceed))
return;

/*
* 第二阶段: 调度"强制"后备动作.
* 调用 hw_failure_emergency_schedule 来设置定时炸弹.
*/
hw_failure_emergency_schedule(action, ms_until_forced);
/*
* 第一阶段: 尝试"有序"处理.
* 根据请求的动作, 调用有序重启或有序关机.
*/
if (action == HWPROT_ACT_REBOOT)
orderly_reboot();
else
orderly_poweroff(true);
}
/* 将核心函数导出, 使其对其他内核模块可用 (例如调节器框架). */
EXPORT_SYMBOL_GPL(__hw_protection_trigger);

hw_failure_emergency_schedule: 调度强制后备动作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* hw_failure_emergency_schedule - 调度一个紧急系统关机或重启
* @action: 要采取的硬件保护动作
* @action_delay_ms: 在触发动作前要经过的毫秒数
*
* 可在任何关键情况下调用此函数, 以在给定时间段后触发系统关机或重启.
* 如果时间是负数, 则不会被调度.
*/
static void hw_failure_emergency_schedule(enum hw_protection_action action,
int action_delay_ms)
{
/*
* 如果超时时间小于等于0, 说明调用者不希望有强制后备动作, 直接返回.
*/
if (action_delay_ms <= 0)
return;
/*
* 将要执行的动作(关机或重启)保存到一个全局变量中.
* 当延迟工作项的处理函数 hw_failure_emergency_action_func 被执行时,
* 它会读取这个全局变量来知道该做什么.
*/
hw_failure_emergency_action = action;
/*
* 调用 schedule_delayed_work 将之前声明的 hw_failure_emergency_action_work
* 提交到内核的系统工作队列(system_wq)中.
* msecs_to_jiffies() 会将毫秒超时转换为内核内部使用的 "jiffies" (时钟节拍)
* 内核定时器子系统会在指定的延迟后, 唤醒工作队列来执行这个工作项.
*/
schedule_delayed_work(&hw_failure_emergency_action_work,
msecs_to_jiffies(action_delay_ms));
}