一、摘要
本文深入分析了Linux内核中设备电源管理的核心实现文件drivers/base/power/main.c,详细阐述了系统级挂起(suspend)和恢复(resume)的完整流程。通过对源码的深入剖析,揭示了Linux内核如何通过分阶段处理、设备依赖管理、异步优化等机制来实现高效可靠的设备电源管理。
二、概述
这个文件是 Linux 设备模型中电源管理的核心实现,负责管理系统中所有设备的挂起(suspend)和恢复(resume)操作。
1、核心数据结构
c
LIST_HEAD(dpm_list); // 主设备列表
static LIST_HEAD(dpm_prepared_list); // 已准备的设备列表
static LIST_HEAD(dpm_suspended_list); // 已挂起的设备列表
static LIST_HEAD(dpm_late_early_list); // 晚期/早期处理列表
static LIST_HEAD(dpm_noirq_list); // 无中断处理列表
2、Suspend 流程详细分析
c
graph TD
A[dpm_suspend_start] --> B[dpm_prepare]
B --> C[dpm_suspend]
A --> D[dpm_suspend_end]
D --> E[dpm_suspend_late]
E --> F[dpm_suspend_noirq]
B --> B1[device_prepare]
C --> C1[device_suspend]
E --> E1[device_suspend_late]
F --> F1[device_suspend_noirq]
B1 --> B2[移动到dpm_prepared_list]
C1 --> C2[移动到dpm_suspended_list]
E1 --> E2[移动到dpm_late_early_list]
F1 --> F2[移动到dpm_noirq_list]
c
正常运行 → 准备阶段 → 挂起阶段 → 晚期挂起 → 无中断挂起
↓ ↑ ↑ ↑ ↑
完成阶段 ← 恢复阶段 ← 早期恢复 ← 无中断恢复 ← [最深睡眠]
1、详细函数分析
c
int dpm_suspend_start(pm_message_t state)
{
ktime_t starttime = ktime_get();
int error;
error = dpm_prepare(state);
if (error)
dpm_save_failed_step(SUSPEND_PREPARE);
else
error = dpm_suspend(state);
dpm_show_time(starttime, state, error, "start");
return error;
}
bash
功能: 启动设备挂起流程的入口函数
调用 dpm_prepare() 准备所有设备
调用 dpm_suspend() 挂起所有设备
记录执行时间和错误信息
c
int dpm_prepare(pm_message_t state)
{
int error = 0;
wait_for_device_probe(); // 等待设备探测完成
device_block_probing(); // 阻止新设备探测
mutex_lock(&dpm_list_mtx);
while (!list_empty(&dpm_list) && !error) {
struct device *dev = to_device(dpm_list.next);
get_device(dev);
mutex_unlock(&dpm_list_mtx);
error = device_prepare(dev, state);
mutex_lock(&dpm_list_mtx);
if (!error) {
dev->power.is_prepared = true;
list_move_tail(&dev->power.entry, &dpm_prepared_list);
}
put_device(dev);
}
mutex_unlock(&dpm_list_mtx);
return error;
}
bash
功能: 准备阶段
阻止新设备的探测和注册
遍历 dpm_list 中的所有设备
调用每个设备的 prepare 回调
将准备好的设备移动到 dpm_prepared_list
c
static int device_prepare(struct device *dev, pm_message_t state)
{
int (*callback)(struct device *) = NULL;
int ret = 0;
pm_runtime_get_noresume(dev); // 阻止运行时挂起
if (dev->power.syscore)
return 0;
device_lock(dev);
// 按优先级选择回调函数
if (dev->pm_domain)
callback = dev->pm_domain->ops.prepare;
else if (dev->type && dev->type->pm)
callback = dev->type->pm->prepare;
else if (dev->class && dev->class->pm)
callback = dev->class->pm->prepare;
else if (dev->bus && dev->bus->pm)
callback = dev->bus->pm->prepare;
if (!callback && dev->driver && dev->driver->pm)
callback = dev->driver->pm->prepare;
if (callback)
ret = callback(dev);
device_unlock(dev);
// 设置直接完成标志
spin_lock_irq(&dev->power.lock);
dev->power.direct_complete = state.event == PM_EVENT_SUSPEND &&
(ret > 0 || dev->power.no_pm_callbacks) &&
!dev_pm_test_driver_flags(dev, DPM_FLAG_NO_DIRECT_COMPLETE);
spin_unlock_irq(&dev->power.lock);
return 0;
}
bash
功能: 单个设备的准备
选择合适的 prepare 回调函数(优先级:pm_domain > type(设备模型中的一个抽象层,主要用于将设备归类) > class(用于表示设备所属的"设备类") > bus > driver)
执行回调函数
设置 direct_complete 标志用于优化
c
int dpm_suspend(pm_message_t state)
{
ktime_t starttime = ktime_get();
int error = 0;
devfreq_suspend(); // 挂起设备频率调节
cpufreq_suspend(); // 挂起CPU频率调节
pm_transition = state;
async_error = 0;
mutex_lock(&dpm_list_mtx);
while (!list_empty(&dpm_prepared_list)) {
struct device *dev = to_device(dpm_prepared_list.prev);
list_move(&dev->power.entry, &dpm_suspended_list);
if (dpm_async_fn(dev, async_suspend))
continue;
get_device(dev);
mutex_unlock(&dpm_list_mtx);
error = device_suspend(dev, state, false);
put_device(dev);
mutex_lock(&dpm_list_mtx);
if (error || async_error)
break;
}
mutex_unlock(&dpm_list_mtx);
async_synchronize_full();
return error;
}
bash
功能: 主要挂起阶段
挂起 CPU 和设备频率调节
支持异步挂起以提高性能
将设备从 dpm_prepared_list 移动到 dpm_suspended_list
c
static int dpm_noirq_suspend_devices(pm_message_t state)
{
/* 记录函数开始执行的时间,用于性能统计 */
ktime_t starttime = ktime_get();
int error = 0;
/* 开始追踪无中断挂起阶段,用于系统调试和性能分析 */
trace_suspend_resume(TPS("dpm_suspend_noirq"), state.event, true);
/* 设置全局电源管理转换状态,供其他函数使用 */
pm_transition = state;
/* 重置异步错误标志,为新的挂起流程做准备 */
async_error = 0;
/*
* 获取设备电源管理列表的互斥锁
* 这是关键的同步机制,确保多线程环境下列表操作的原子性
*/
mutex_lock(&dpm_list_mtx);
/*
* 主要处理循环:处理所有在晚期挂起列表中的设备
*
* 处理顺序说明:
* - 从dpm_late_early_list的尾部开始处理(LIFO - 后进先出)
* - 这确保了子设备在父设备之前被挂起
* - 维护了正确的设备依赖关系顺序
*/
while (!list_empty(&dpm_late_early_list)) {
/*
* 获取列表中的最后一个设备(最晚进入晚期挂起的设备)
* 使用LIFO顺序确保依赖关系的正确性:
* 子设备先挂起,父设备后挂起
*/
struct device *dev = to_device(dpm_late_early_list.prev);
/*
* 关键的列表状态转换:
* 将设备从 dpm_late_early_list 移动到 dpm_noirq_list
*
* 状态转换意义:
* dpm_late_early_list: 已完成晚期挂起,准备无中断挂起
* dpm_noirq_list: 正在进行或已完成无中断挂起
*/
list_move(&dev->power.entry, &dpm_noirq_list);
/*
* 尝试异步处理设备挂起
*
* 异步处理的优势:
* 1. 提高系统挂起性能,减少总体挂起时间
* 2. 允许独立设备并行处理
* 3. 不影响有依赖关系的设备的正确顺序
*
* 如果设备支持异步处理且异步调度成功,则continue跳过同步处理
*/
if (dpm_async_fn(dev, async_suspend_noirq))
continue;
/*
* 同步处理路径:
* 对于不支持异步处理或异步调度失败的设备
*/
/* 增加设备引用计数,防止设备在处理过程中被意外释放 */
get_device(dev);
/*
* 临时释放互斥锁的重要原因:
* 1. device_suspend_noirq可能是耗时操作
* 2. 允许异步处理的设备继续执行
* 3. 避免长时间持有锁导致的性能问题
* 4. 防止死锁情况的发生
*/
mutex_unlock(&dpm_list_mtx);
/*
* 执行设备的无中断挂起操作
*
* 这是核心的设备挂起逻辑:
* - 调用设备特定的suspend_noirq回调函数
* - 在中断被禁用的环境中执行
* - 处理设备的最终挂起状态设置
* - async参数为false表示这是同步处理
*/
error = device_suspend_noirq(dev, state, false);
/* 减少设备引用计数,与前面的get_device配对 */
put_device(dev);
/* 重新获取互斥锁,准备处理下一个设备或检查错误 */
mutex_lock(&dpm_list_mtx);
/*
* 错误检查和处理:
*
* 检查两种类型的错误:
* 1. error: 当前设备同步处理的错误
* 2. async_error: 异步处理设备产生的错误
*
* 任何错误都会导致整个挂起流程中断,这是为了:
* - 保证系统状态的一致性
* - 避免部分设备挂起导致的不稳定状态
* - 及时发现和处理硬件或驱动问题
*/
if (error || async_error)
break;
}
/* 释放设备电源管理列表互斥锁 */
mutex_unlock(&dpm_list_mtx);
/*
* 等待所有异步操作完成
*
* 这个调用的重要性:
* 1. 确保所有异步启动的设备挂起操作都已完成
* 2. 收集异步操作中可能产生的错误
* 3. 保证函数返回时所有设备都已处理完毕
* 4. 维护系统状态的一致性
*/
async_synchronize_full();
/*
* 最终错误状态确定:
* 如果同步处理没有错误,但异步处理有错误,
* 则将异步错误作为最终错误返回
*/
if (!error)
error = async_error;
/*
* 错误记录和调试支持:
* 如果挂起过程中发生错误,记录失败的步骤
* 这对于系统调试和问题诊断非常重要
*/
if (error)
dpm_save_failed_step(SUSPEND_SUSPEND_NOIRQ);
/*
* 性能统计和监控:
* 显示无中断挂起阶段的执行时间
* 包括成功/失败状态,用于性能分析和优化
*/
dpm_show_time(starttime, state, error, "noirq");
/* 结束追踪无中断挂起阶段 */
trace_suspend_resume(TPS("dpm_suspend_noirq"), state.event, false);
/* 返回最终的错误状态 */
return error;
}
c
static int device_suspend(struct device *dev, pm_message_t state, bool async)
{
pm_callback_t callback = NULL;
const char *info = NULL;
int error = 0;
DECLARE_DPM_WATCHDOG_ON_STACK(wd);
dpm_wait_for_subordinate(dev, async); // 等待子设备完成
pm_runtime_barrier(dev); // 等待运行时PM完成
if (pm_wakeup_pending()) {
async_error = -EBUSY;
goto Complete;
}
// 处理 direct_complete 优化
if (dev->power.direct_complete) {
if (pm_runtime_status_suspended(dev)) {
pm_runtime_disable(dev);
if (pm_runtime_status_suspended(dev)) {
dev->power.is_suspended = true;
goto Complete;
}
pm_runtime_enable(dev);
}
dev->power.direct_complete = false;
}
dpm_watchdog_set(&wd, dev);
device_lock(dev);
// 选择回调函数(优先级同prepare)
if (dev->pm_domain) {
info = "power domain ";
callback = pm_op(&dev->pm_domain->ops, state);
} else if (dev->type && dev->type->pm) {
info = "type ";
callback = pm_op(dev->type->pm, state);
}
// ... 其他优先级选择
error = dpm_run_callback(callback, dev, state, info);
if (!error) {
dev->power.is_suspended = true;
if (device_may_wakeup(dev))
dev->power.wakeup_path = true;
dmp_propagate_wakeup_to_parent(dev);
}
device_unlock(dev);
dpm_watchdog_clear(&wd);
Complete:
complete_all(&dev->power.completion);
return error;
}
bash
功能: 单个设备挂起
等待子设备和依赖设备完成
处理 direct_complete 优化
设置看门狗防止挂起超时
执行设备特定的挂起回调
处理唤醒路径传播
3、Resume 流程详细分析
bash
graph TD
A[dpm_resume_start] --> B[dpm_resume_noirq]
B --> C[dpm_resume_early]
A --> D[dpm_resume_end]
D --> E[dpm_resume]
E --> F[dpm_complete]
B --> B1[device_resume_noirq]
C --> C1[device_resume_early]
E --> E1[device_resume]
F --> F1[device_complete]
B1 --> B2[从dpm_noirq_list移动到dmp_late_early_list]
C1 --> C2[从dpm_late_early_list移动到dpm_suspended_list]
E1 --> E2[从dpm_suspended_list移动到dpm_prepared_list]
F1 --> F2[从dpm_prepared_list移动到dpm_list]
1、详细函数分析
c
void dpm_resume_start(pm_message_t state)
{
dpm_resume_noirq(state);
dpm_resume_early(state);
}
c
void dpm_resume_noirq(pm_message_t state)
{
dpm_noirq_resume_devices(state);
resume_device_irqs(); // 恢复设备中断
device_wakeup_disarm_wake_irqs(); // 解除唤醒中断
}
c
static void dpm_noirq_resume_devices(pm_message_t state)
{
struct device *dev;
ktime_t starttime = ktime_get();
/* 开始追踪无中断恢复阶段 */
trace_suspend_resume(TPS("dpm_resume_noirq"), state.event, true);
/* 重置异步错误标志 */
async_error = 0;
/* 设置当前电源管理转换状态 */
pm_transition = state;
/* 获取设备电源管理列表互斥锁,确保线程安全 */
mutex_lock(&dpm_list_mtx);
/*
* 第一阶段:启动异步设备的恢复
* 优先触发"异步"设备的恢复,这样它们就不必等待
* 那些它们不依赖的"非异步"设备完成
* 这是一个重要的性能优化策略
*/
list_for_each_entry(dev, &dpm_noirq_list, power.entry)
dpm_async_fn(dev, async_resume_noirq);
/*
* 第二阶段:处理同步设备和等待异步设备完成
* 从dpm_noirq_list的头部开始处理设备(FIFO顺序)
* 这确保了设备按照正确的依赖顺序进行恢复
*/
while (!list_empty(&dpm_noirq_list)) {
/* 获取列表中的第一个设备(最早进入的设备) */
dev = to_device(dpm_noirq_list.next);
/*
* 将设备从dpm_noirq_list移动到dpm_late_early_list
* 这标志着设备已经完成了无中断恢复阶段,
* 准备进入下一个恢复阶段(早期恢复阶段)
*/
list_move_tail(&dev->power.entry, &dpm_late_early_list);
/*
* 检查设备是否正在进行异步处理
* 如果设备没有在异步处理中,则进行同步处理
*/
if (!dev->power.async_in_progress) {
/* 增加设备引用计数,防止设备在处理过程中被释放 */
get_device(dev);
/*
* 临时释放互斥锁,允许其他线程访问设备列表
* 这在执行可能耗时的设备操作时很重要
*/
mutex_unlock(&dpm_list_mtx);
/*
* 执行设备的无中断恢复操作
* 这是核心的设备恢复逻辑,会调用设备特定的resume_noirq回调
* async参数为false表示这是同步处理
*/
device_resume_noirq(dev, state, false);
/* 减少设备引用计数 */
put_device(dev);
/* 重新获取互斥锁,继续处理下一个设备 */
mutex_lock(&dpm_list_mtx);
}
/*
* 如果设备正在异步处理中,则跳过同步处理
* 异步处理会在后面的async_synchronize_full()中等待完成
*/
}
/* 释放设备电源管理列表互斥锁 */
mutex_unlock(&dpm_list_mtx);
/*
* 等待所有异步操作完成
* 这确保了所有设备(无论是同步还是异步处理的)
* 都已经完成了无中断恢复阶段
*/
async_synchronize_full();
/*
* 显示无中断恢复阶段的执行时间统计
* 这对于性能分析和调试很有用
*/
dpm_show_time(starttime, state, 0, "noirq");
/*
* 检查是否有异步错误发生
* 如果有错误,保存失败步骤信息用于调试
*/
if (async_error)
dpm_save_failed_step(SUSPEND_RESUME_NOIRQ);
/* 结束追踪无中断恢复阶段 */
trace_suspend_resume(TPS("dpm_resume_noirq"), state.event, false);
}
c
static void device_resume_noirq(struct device *dev, pm_message_t state, bool async)
{
pm_callback_t callback = NULL;
const char *info = NULL;
bool skip_resume;
int error = 0;
if (!dev->power.is_noirq_suspended)
goto Out;
if (!dpm_wait_for_superior(dev, async))
goto Out;
skip_resume = dev_pm_skip_resume(dev);
// 根据skip_resume设置运行时PM状态
if (skip_resume)
pm_runtime_set_suspended(dev);
else if (dev_pm_skip_suspend(dev))
pm_runtime_set_active(dev);
// 选择回调函数
if (dev->pm_domain) {
info = "noirq power domain ";
callback = pm_noirq_op(&dev->pm_domain->ops, state);
}
// ... 其他选择逻辑
if (callback)
goto Run;
if (skip_resume)
goto Skip;
if (dev->driver && dev->driver->pm) {
info = "noirq driver ";
callback = pm_noirq_op(dev->driver->pm, state);
}
Run:
error = dpm_run_callback(callback, dev, state, info);
Skip:
dev->power.is_noirq_suspended = false;
Out:
complete_all(&dev->power.completion);
}
c
static bool dpm_wait_for_superior(struct device *dev, bool async)
{
struct device *parent;
mutex_lock(&dpm_list_mtx);
if (!device_pm_initialized(dev)) {
mutex_unlock(&dpm_list_mtx);
return false;
}
parent = get_device(dev->parent);
mutex_unlock(&dpm_list_mtx);
dpm_wait(parent, async); // 等待父设备
put_device(parent);
dpm_wait_for_suppliers(dev, async); // 等待供应商设备
return device_pm_initialized(dev);
}
c
A[设备回调选择] --> B{pm_domain存在?}
B -->|是| C[使用pm_domain回调]
B -->|否| D{type存在?}
D -->|是| E[使用type回调]
D -->|否| F{class存在?}
F -->|是| G[使用class回调]
F -->|否| H{bus存在?}
H -->|是| I[使用bus回调]
H -->|否| J[使用driver回调]
总结
bash
/* 列表状态转换 */
dpm_list → dpm_prepared_list (prepare完成)
dpm_prepared_list → dpm_suspended_list (suspend完成)
dmp_suspended_list → dpm_late_early_list (suspend_late完成)
dpm_late_early_list → dpm_noirq_list (suspend_noirq完成)
dpm_noirq_list → dpm_late_early_list (resume_noirq完成)
dpm_late_early_list → dpm_suspended_list (resume_early完成)
dpm_suspended_list → dpm_prepared_list (resume完成)
dpm_prepared_list → dpm_list (complete完成)
bash
drivers/base/power/main.c 是 Linux 内核电源管理的核心,它实现了:
分阶段的设备电源管理: prepare → suspend → suspend_late → suspend_noirq
设备依赖关系处理: 确保父设备和供应商设备的正确顺序
异步处理支持: 提高系统挂起/恢复性能
多种优化机制: direct_complete、smart_suspend 等
完善的错误处理: 看门狗、错误记录、回滚机制
这个文件确保了系统中所有设备能够安全、有序地进行电源状态转换,是 Linux 电源管理子系统的基石。