了解Linux 内核 suspend的调用流程
使用ftrace进行suspend/resume调试
解析dmesg输出,分析关键路径
阶段 |
函数 |
作用 |
---|---|---|
准备阶段 |
suspend_prepare() |
检查系统状态,通知用户空间 |
设备冻结 |
dpm_suspend() |
冻结用户态进程,调用设备驱动 suspend |
架构相关 |
arch_suspend_disable_irqs() |
关闭中断,调用 CPU suspend |
进入睡眠 |
syscore_suspend() |
关闭系统核心,进入低功耗状态 |
恢复阶段 |
dpm_resume() → suspend_finish() |
重新开启设备、恢复状态 |
static int suspend_prepare(suspend_state_t state)
{
int error, nr_calls = 0;
if (!sleep_state_supported(state))
return -EPERM;
pm_prepare_console();
error = __pm_notifier_call_chain(PM_SUSPEND_PREPARE, -1, &nr_calls);
if (error) {
nr_calls--;
goto Finish;
}
trace_suspend_resume(TPS("freeze_processes"), 0, true);
error = suspend_freeze_processes();
trace_suspend_resume(TPS("freeze_processes"), 0, false);
if (!error)
return 0;
log_suspend_abort_reason("One or more tasks refusing to freeze");
suspend_stats.failed_freeze++;
dpm_save_failed_step(SUSPEND_FREEZE);
Finish:
__pm_notifier_call_chain(PM_POST_SUSPEND, nr_calls, NULL);
pm_restore_console();
return error;
}
pm_prepare_console()
: 这是一个函数调用,目的是为系统挂起做控制台相关的准备工作。这可能包括:
保存当前的控制台设置。
设置控制台进入特定的低功耗模式 (如果需要)。
确保在挂起和恢复期间控制台的行为是可预测和稳定的。
为什么需要准备控制台? 在系统挂起和恢复过程中,控制台的显示和行为可能会受到影响。为了确保用户在系统恢复后能够正常使用控制台,需要在挂起前进行准备,并在恢复后进行恢复(pm_restore_console()
将在后面看到)。
作用: 调用电源管理通知链,通知注册的模块进行挂起准备。
详细说明:
__pm_notifier_call_chain(...)
: 这是一个核心的电源管理框架函数,用于执行通知链 (notifier chain)。通知链是一种发布-订阅模式,允许不同的模块(例如设备驱动程序、子系统)注册对特定电源管理事件的兴趣,并在事件发生时收到通知。
PM_SUSPEND_PREPARE
: 这是一个预定义的宏或枚举值,表示要触发的电源管理事件类型是 "挂起准备"。当这个事件被触发时,所有注册了对 PM_SUSPEND_PREPARE
事件感兴趣的通知器(notifier)都会被调用。
作用: 冻结用户空间的所有进程。
详细说明:
suspend_freeze_processes()
: 这是一个关键函数,负责暂停所有用户空间进程的运行。 这是挂起准备过程中至关重要的一步,因为要将系统安全地挂起,必须确保用户空间进程停止活动,避免在系统状态保存期间发生数据修改或状态不一致。
冻结进程通常通过发送信号 (如 SIGSTOP
) 给所有用户空间进程来实现。 这些进程会被暂停执行,但其状态 (内存、寄存器等) 会被保存下来,以便在系统恢复后能够继续运行。
作用: 调用电源管理通知链,进行挂起 后处理 (post suspend) 或 错误恢复。
详细说明:
__pm_notifier_call_chain(PM_POST_SUSPEND, nr_calls, NULL);
: 再次调用 __pm_notifier_call_chain
,但这次触发的事件类型是 PM_POST_SUSPEND
("挂起后处理")。
PM_POST_SUSPEND
: 表示要触发 "挂起后处理" 事件。 当发生错误导致挂起准备阶段失败,或者在挂起恢复后,需要进行一些清理或恢复操作时,会触发这个事件。
作用: 恢复控制台到挂起准备前的状态。
详细说明:
pm_restore_console()
: 与第 3 步的 pm_prepare_console()
相对应。 pm_restore_console()
函数用于 撤销 pm_prepare_console()
所做的控制台准备工作,将控制台恢复到挂起准备之前的状态。 这确保了即使挂起操作失败,控制台也能恢复到正常的工作状态。
/**
* dpm_suspend - Execute "suspend" callbacks for all non-sysdev devices.
* @state: PM transition of the system being carried out.
*/
int dpm_suspend(pm_message_t state)
{
ktime_t starttime = ktime_get();
int error = 0;
trace_suspend_resume(TPS("dpm_suspend"), state.event, true);
might_sleep();
devfreq_suspend();
cpufreq_suspend();
mutex_lock(&dpm_list_mtx);
pm_transition = state;
async_error = 0;
while (!list_empty(&dpm_prepared_list)) {
struct device *dev = to_device(dpm_prepared_list.prev);
get_device(dev);
mutex_unlock(&dpm_list_mtx);
error = device_suspend(dev);
mutex_lock(&dpm_list_mtx);
if (error) {
pm_dev_err(dev, state, "", error);
dpm_save_failed_dev(dev_name(dev));
put_device(dev);
break;
}
if (!list_empty(&dev->power.entry))
list_move(&dev->power.entry, &dpm_suspended_list);
put_device(dev);
if (async_error)
break;
}
mutex_unlock(&dpm_list_mtx);
async_synchronize_full();
if (!error)
error = async_error;
if (error) {
suspend_stats.failed_suspend++;
dpm_save_failed_step(SUSPEND_SUSPEND);
}
dpm_show_time(starttime, state, error, NULL);
trace_suspend_resume(TPS("dpm_suspend"), state.event, false);
return error;
}
devfreq_suspend()
: 挂起设备频率管理(devfreq)子系统,降低设备频率。
cpufreq_suspend()
: 挂起 CPU 频率管理(cpufreq)子系统,降低 CPU 频率。
mutex_lock(&dpm_list_mtx);
pm_transition = state;
async_error = 0;
while (!list_empty(&dpm_prepared_list)) {
struct device *dev = to_device(dpm_prepared_list.prev);
get_device(dev);
mutex_unlock(&dpm_list_mtx);
error = device_suspend(dev);
mutex_lock(&dpm_list_mtx);
if (error) {
pm_dev_err(dev, state, "", error);
dpm_save_failed_dev(dev_name(dev));
put_device(dev);
break;
}
if (!list_empty(&dev->power.entry))
list_move(&dev->power.entry, &dpm_suspended_list);
put_device(dev);
if (async_error)
break;
}
mutex_unlock(&dpm_list_mtx);
mutex_lock(&dpm_list_mtx);
: 获取互斥锁,保护设备列表的访问。
pm_transition = state;
: 记录当前的电源管理状态。
async_error = 0;
: 初始化异步错误码。
while (!list_empty(&dpm_prepared_list))
: 循环遍历 dpm_prepared_list
列表中的所有设备。
struct device *dev = to_device(dpm_prepared_list.prev);
: 从列表中获取下一个要挂起的设备。
get_device(dev);
: 增加设备的引用计数。
mutex_unlock(&dpm_list_mtx);
: 释放互斥锁,允许其他线程访问设备列表。
error = device_suspend(dev);
: 调用设备的 device_suspend
函数,执行设备的挂起操作。
mutex_lock(&dpm_list_mtx);
: 重新获取互斥锁。
if (error)
: 如果挂起操作失败,记录错误信息,并将设备名称保存到失败列表中。
if (!list_empty(&dev->power.entry)) list_move(&dev->power.entry, &dpm_suspended_list);
: 如果设备成功挂起,将其从 dpm_prepared_list
移动到 dpm_suspended_list
。
put_device(dev);
: 减少设备的引用计数。
if (async_error) break;
: 如果出现异步错误,退出循环。
mutex_unlock(&dpm_list_mtx);
: 释放互斥锁。
/* default implementation */
void __weak arch_suspend_disable_irqs(void)
{
local_irq_disable();
}
这段代码定义了一个名为 arch_suspend_disable_irqs
的函数,其作用是在系统挂起过程中禁用本地 CPU 的中断。
__weak
是一个函数属性,表示该函数是一个弱符号。在链接过程中,如果存在同名的强符号函数定义,则会覆盖这个弱符号函数。这使得架构特定的代码可以提供自己的 arch_suspend_disable_irqs
实现,而无需修改通用代码。
local_irq_disable()
是一个内核函数,用于禁用本地 CPU 的中断。在 ARM 架构中,这个函数最终调用 arch_local_irq_disable()
,通过汇编指令设置处理器的状态寄存器,以屏蔽中断。
/**
* syscore_suspend - Execute all the registered system core suspend callbacks.
*
* This function is executed with one CPU on-line and disabled interrupts.
*/
int syscore_suspend(void)
{
struct syscore_ops *ops;
int ret = 0;
trace_suspend_resume(TPS("syscore_suspend"), 0, true);
pr_debug("Checking wakeup interrupts\n");
/* Return error code if there are any wakeup interrupts pending. */
if (pm_wakeup_pending())
return -EBUSY;
WARN_ONCE(!irqs_disabled(),
"Interrupts enabled before system core suspend.\n");
list_for_each_entry_reverse(ops, &syscore_ops_list, node)
if (ops->suspend) {
if (initcall_debug)
pr_info("PM: Calling %pS\n", ops->suspend);
ret = ops->suspend();
if (ret)
goto err_out;
WARN_ONCE(!irqs_disabled(),
"Interrupts enabled after %pS\n", ops->suspend);
}
trace_suspend_resume(TPS("syscore_suspend"), 0, false);
return 0;
err_out:
log_suspend_abort_reason("System core suspend callback %pS failed",
ops->suspend);
pr_err("PM: System core suspend callback %pF failed.\n", ops->suspend);
list_for_each_entry_continue(ops, &syscore_ops_list, node)
if (ops->resume)
ops->resume();
return ret;
}
EXPORT_SYMBOL_GPL(syscore_suspend);
调用 pm_wakeup_pending()
检查是否有挂起的唤醒事件。如果存在,返回 -EBUSY
错误码,表示系统正忙于处理唤醒事件,无法进入挂起状态。
使用 list_for_each_entry_reverse
宏以逆序方式遍历 syscore_ops_list
链表中的每个 syscore_ops
结构。对于每个元素:
如果 suspend
回调存在:
如果启用了初始化调用调试(initcall_debug
),输出正在调用的挂起回调的地址。
调用 ops->suspend()
执行挂起操作,并将返回值赋给 ret
。
如果返回值不为 0,表示挂起操作失败,跳转到错误处理部分。
再次使用 WARN_ONCE
检查中断状态,如果在挂起回调后中断被启用,输出警告信息。
如果suspend某个core失败,那么回滚之前的操作,唤醒之前已经suspend的cores。
继续遍历剩余的 syscore_ops
,对于每个具有 resume
回调的元素,调用其 resume
回调,以恢复已挂起的系统核心组件。
指示接下来的代码可能会导致睡眠,以确保在不允许睡眠的上下文中不会调用此函数。
/**
* dpm_resume - Execute "resume" callbacks for non-sysdev devices.
* @state: PM transition of the system being carried out.
*
* Execute the appropriate "resume" callback for all devices whose status
* indicates that they are suspended.
*/
void dpm_resume(pm_message_t state)
{
struct device *dev;
ktime_t starttime = ktime_get();
trace_suspend_resume(TPS("dpm_resume"), state.event, true);
might_sleep();
mutex_lock(&dpm_list_mtx);
pm_transition = state;
async_error = 0;
list_for_each_entry(dev, &dpm_suspended_list, power.entry)
dpm_async_fn(dev, async_resume);
while (!list_empty(&dpm_suspended_list)) {
dev = to_device(dpm_suspended_list.next);
get_device(dev);
if (!is_async(dev)) {
int error;
mutex_unlock(&dpm_list_mtx);
error = device_resume(dev, state, false);
if (error) {
suspend_stats.failed_resume++;
dpm_save_failed_step(SUSPEND_RESUME);
dpm_save_failed_dev(dev_name(dev));
pm_dev_err(dev, state, "", error);
}
mutex_lock(&dpm_list_mtx);
}
if (!list_empty(&dev->power.entry))
list_move_tail(&dev->power.entry, &dpm_prepared_list);
put_device(dev);
}
mutex_unlock(&dpm_list_mtx);
async_synchronize_full();
dpm_show_time(starttime, state, 0, NULL);
cpufreq_resume();
devfreq_resume();
trace_suspend_resume(TPS("dpm_resume"), state.event, false);
}