系列目录 :第一篇:电源管理架构全景图 | 第二篇:开机全链路---BootROM到Launcher | 第三篇:关机/重启全链路---ShutdownThread到kernel_power_off | 第四篇:休眠唤醒与开关机---核心差异深度对比 | 第五篇:休眠全链路---PMS到Kernel Suspend | 第六篇:唤醒全链路---Kernel Resume到屏幕点亮 | 第七篇:内核层---wakelock与autosleep机制 | 第八篇:内核层---Alarm定时唤醒与硬件唤醒源 | 第九篇:Native层---libsuspend与Power HAL | 第十篇:实战调试与问题排查
一、为什么要深入内核 wakelock
第五篇和第六篇追踪休眠/唤醒全链路时,两次提到了 suspend_blocker 和内核 wakelock------它们是连接用户空间与内核休眠机制的桥梁。但你可能会疑惑:
- 标准 Linux 用
wakeup_count解决休眠竞态,Android 为什么要另起炉灶搞一套 wakelock? - 内核 wakelock 到底是怎么阻止休眠的?只要有一个 wakelock 没释放,系统就真的不能睡?
- autosleep 线程是怎么做到"自动休眠"的?它和用户空间轮询有什么本质区别?
带着这些问题,本篇深入内核层,逐一拆解。
二、设计背景:为什么需要内核 wakelock
2.1 Linux 标准 suspend 的缺陷
在标准 Linux 中,用户空间通过写入 "mem" 到 /sys/power/state 触发休眠。但这带来了一个竞态问题:
用户空间:准备休眠 → write("mem", /sys/power/state)
↑ ↓
│ 内核进入 suspend...
│ ↓
正在此时,一个唤醒事件发生了!
│
事件被忽略,系统继续休眠 → 用户按电源键也没反应 → 死休眠
Linux 社区引入了 wakeup_count 机制解决此问题(见第五篇),但 Android 选择了一条不同的路线------wakelock。
2.2 Android wakelock 的设计哲学
核心思想:只要系统中有任何组件持有 wakelock,内核就不允许休眠。
这意味着休眠的决策权从"用户空间主动请求"变成了"内核被动等待 wakelock 全部释放"。这避免了竞态问题------因为 wakelock 的获取和释放是原子操作。
关键设计:wakeup_count 是"用户空间问内核:现在能睡吗?",wakelock 是"内核自己知道:还有锁没放,不能睡"。后者天然无竞态。
三、wakelock 的数据结构
3.1 struct wakeup_source
源码路径 :include/linux/wakeup_source.h
c
struct wakeup_source {
const char *name; // wakelock 名称
struct list_head entry; // 链表节点,链入全局列表
spinlock_t lock; // 保护当前结构的自旋锁
ktime_t last_time; // 最后一次活跃事件的时间
unsigned long active_count; // activate 调用次数
unsigned long event_count; // 事件触发次数
unsigned long wakeup_count; // 唤醒事件次数
unsigned long long total_time; // 累计活跃时间
unsigned long long max_time; // 单次最长活跃时间
unsigned long expire_count; // 超时到期次数
struct timer_list timer; // 超时定时器
bool active; // 当前是否活跃
};
关键设计 :三个时间字段(
last_time、total_time、max_time)是功耗分析的宝贵数据------通过 debugfs 可以查看每个唤醒源的累积活跃时间,定位功耗热点。
核心字段:
active:是否处于活跃状态。为 true 时阻止系统休眠timer:超时定时器。pm_wakeup_event(ws, timeout)在timeout毫秒后自动释放 wakelocktotal_time:累计活跃时间,用于功耗分析
3.2 全局唤醒源列表
源码路径 :kernel/power/wakelock.c
c
// 全局链表,所有注册的 wakeup_source 都链接在此
static LIST_HEAD(wakeup_sources);
关键设计 :内核通过遍历这个链表,检查是否有
active == true的 wakeup_source,来决定是否允许休眠。整个判断逻辑只有一个链表遍历,非常轻量。
四、wakelock 的 API
4.1 创建和销毁
源码路径 :kernel/power/wakelock.c
c
// 创建唤醒源
struct wakeup_source *wakeup_source_create(const char *name);
// 注册到全局列表
void wakeup_source_add(struct wakeup_source *ws);
// 注销并销毁
void wakeup_source_remove(struct wakeup_source *ws);
void wakeup_source_destroy(struct wakeup_source *ws);
关键设计 :创建和注册分离------先
create再add,允许驱动在注册前初始化自定义字段。销毁也是先remove再destroy,确保注销后不会有新的访问。
4.2 激活和释放
这三个函数是 wakelock 的核心操作------激活阻止休眠、释放允许休眠、带超时激活:
c
// 激活 wakelock(阻止休眠)
void __pm_stay_awake(struct wakeup_source *ws);
// 释放 wakelock(允许休眠)
void __pm_relax(struct wakeup_source *ws);
// 带超时的激活:timeout 毫秒后自动释放
void __pm_wakeup_event(struct wakeup_source *ws, unsigned long msec);
关键设计 :
__pm_wakeup_event是中断处理中最常用的接口------硬件中断触发后持锁,timeout 毫秒后自动释放,无需手动管理生命周期。
4.3 查询函数
源码路径 :kernel/power/wakelock.c
c
// 检查是否有活跃的唤醒源阻止休眠
bool pm_wakeup_pending(void);
// 检查指定唤醒源是否活跃
bool pm_wakeup_active(struct wakeup_source *ws);
关键设计 :
pm_wakeup_pending()是 autosleep 线程和 suspend 流程的核心判断依据------遍历全局链表,只要有一个active == true就返回 true。
4.4 wake_lock/wake_unlock 兼容接口
源码路径 :kernel/power/wakelock.c
为了兼容旧的 wakelock 驱动,内核提供了基于字符串的简化接口:
c
// 通过字符串名称获取/释放锁
void wake_lock_init(struct wake_lock *lock, int type, const char *name);
void wake_lock(struct wake_lock *lock);
void wake_unlock(struct wake_lock *lock);
void wake_lock_destroy(struct wake_lock *lock);
关键设计 :这是给旧驱动用的"字符串接口"------通过名称查找 wakeup_source,性能不如直接操作指针。新驱动应该使用
wakeup_source_*系列 API。
五、autosleep 工作线程
5.1 设计动机
wakeup_count 模式下,用户空间每次想休眠都需要:
- 确认有 wakelock 未释放 → 不写 state
- wakelock 全部释放 → 写
/sys/power/state触发休眠 - 唤醒后 → 循环
这种"用户空间轮询"的方式繁琐且效率低。autosleep 将休眠决策交给内核自己处理。
5.2 autosleep 线程
源码路径 :kernel/power/autosleep.c
c
// autosleep 线程的主循环
static int autosleep_thread(void *unused) {
while (!kthread_should_stop()) {
// 1. 等待:autosleep 启用 + wakelock 全部释放
// 或收到停止信号
wait_event_freezable(autosleep_wq,
autosleep_state == AUTOSLEEP_ACTIVE &&
!pm_wakeup_pending());
// 2. 休眠
if (autosleep_state == AUTOSLEEP_ACTIVE) {
pm_suspend(requested_suspend_state);
}
}
return 0;
}
关键设计 :
wait_event_freezable()是可冻结的等待------系统休眠前会冻结此线程,唤醒后恢复执行。线程只在两个条件同时满足时触发休眠:autosleep 已启用 且 没有活跃 wakelock。
关键机制:
wait_event_freezable()--- 这是一个可冻结的等待。在休眠过程中,autosleep 线程本身被冻结,唤醒后继续执行pm_wakeup_pending()--- 检查是否有活跃 wakelock。如果有,线程继续等待pm_suspend()--- 当 autosleep 启用且没有活跃 wakelock 时,调用标准休眠函数
5.3 /sys/power/autosleep --- 控制接口
源码路径 :kernel/power/autosleep.c
用户空间通过 sysfs 接口控制 autosleep 线程的启用和禁用:
c
static ssize_t autosleep_store(struct kobject *kobj, ...) {
if (strncmp(buf, "mem", 3) == 0) {
// 启用 autosleep,目标休眠状态为 mem
autosleep_state = AUTOSLEEP_ACTIVE;
requested_suspend_state = PM_SUSPEND_MEM;
wake_up(&autosleep_wq);
} else if (strncmp(buf, "off", 3) == 0) {
// 禁用 autosleep
autosleep_state = AUTOSLEEP_INACTIVE;
}
}
关键设计 :写入
"mem"后,autosleep 线程被唤醒并开始自动循环------此后用户空间无需任何操作,内核会在 wakelock 全部释放时自动休眠,唤醒后自动等待下一次休眠时机。
典型用法:
bash
# 启用 autosleep:内核自动管理休眠
echo mem > /sys/power/autosleep
# 禁用 autosleep
echo off > /sys/power/autosleep
六、sysfs 接口全览
6.1 /sys/power/state
源码路径 :kernel/power/main.c
最基础的休眠接口。写入 "mem" 立即触发一次 pm_suspend(PM_SUSPEND_MEM)。
bash
echo mem > /sys/power/state
关键设计:这是"一次性触发"------每次写入都执行一次休眠,不像 autosleep 那样持续自动管理。
6.2 /sys/power/wake_lock / wake_unlock
源码路径 :kernel/power/wakelock.c
用户空间可以直接操作内核 wakelock:
bash
echo mylock > /sys/power/wake_lock # 获取
echo mylock > /sys/power/wake_unlock # 释放
关键设计 :libsuspend 就是通过这两个节点管理
mainwakelock 的------用户空间所有 wakelock 汇总为一个main锁,写入wake_lock持有,写入wake_unlock释放。
6.3 /sys/power/wakeup_count
源码路径 :kernel/power/main.c
wakeup_count 机制的核心接口:
c
static ssize_t wakeup_count_store(struct kobject *kobj, ...) {
// 读取传入的 count 值
// 与当前全局 events_check_enabled 比较
// 如果不等 → 说明有新唤醒事件发生 → 返回错误
// 如果相等 → 记录 split_count → 允许此次休眠
}
关键设计:这是一个"乐观锁"机制------先读后写,如果写成功说明期间没有新唤醒事件,允许休眠;写失败说明有新事件,应放弃此次休眠。
用法:
bash
# 1. 读取当前值
count=$(cat /sys/power/wakeup_count)
# 2. 写回确认
echo $count > /sys/power/wakeup_count
# 如果写成功 → 说明没有新唤醒事件
# 如果写失败 → 说明有新事件,不应休眠
# 3. 触发休眠
echo mem > /sys/power/state
6.4 /sys/kernel/debug/wakeup_sources
源码路径 :kernel/power/wakelock.c
调试接口,列出所有 wakeup_source 的统计数据:
bash
cat /sys/kernel/debug/wakeup_sources
输出格式:
name active_count event_count wakeup_count ...
PowerManagerService.WakeLocks 123 456 78 ...
wlan_wake 45 89 12 ...
关键设计 :这是功耗调试的第一站------
total_time最高的 wakelock 就是阻止系统休眠最久的元凶,active_count最高的则是唤醒最频繁的。
七、wakelock 统计机制
7.1 时间统计
源码路径 :kernel/power/wakelock.c
c
void __pm_stay_awake(struct wakeup_source *ws) {
// 记录开始时间
ws->last_time = ktime_get();
ws->active = true;
ws->active_count++;
}
void __pm_relax(struct wakeup_source *ws) {
// 计算本次活跃时长
ktime_t duration = ktime_sub(ktime_get(), ws->last_time);
ws->total_time = ktime_add(ws->total_time, duration);
// 更新最大单次活跃时长
if (duration > ws->max_time) {
ws->max_time = duration;
}
ws->active = false;
}
关键设计 :
total_time和max_time是功耗分析的宝贵数据------通过/sys/kernel/debug/wakeup_sources可以查看每个唤醒源的累积活跃时间,定位功耗热点。
7.2 超时机制
源码路径 :kernel/power/wakelock.c
pm_wakeup_event(ws, timeout) 设置一个定时器,timeout 毫秒后自动调用 __pm_relax():
c
void __pm_wakeup_event(struct wakeup_source *ws, unsigned int msec) {
__pm_stay_awake(ws);
mod_timer(&ws->timer, jiffies + msecs_to_jiffies(msec)); ← 到期后自动调用 __pm_relax
}
关键设计:超时机制常见于硬件中断场景------按键消抖、WiFi 数据收发等短暂处理,处理完成后自动释放 wakelock,无需手动管理。
八、wakelock 的内核使用场景
8.1 驱动中使用示例
c
#include <linux/wakelock.h>
// 方式一:直接使用 wake_lock
struct wake_lock my_lock;
wake_lock_init(&my_lock, WAKE_LOCK_SUSPEND, "my_driver_lock");
// 处理关键事件时持有锁
wake_lock(&my_lock);
// ... 处理逻辑 ...
wake_unlock(&my_lock);
wake_lock_destroy(&my_lock);
关键设计 :
wake_lock_init和wake_lock_destroy必须配对使用------init 注册到全局列表,destroy 注销并释放内存。遗漏 destroy 会导致内存泄漏和悬挂链表节点。
8.2 常见的内核 wakelock 来源
下表列出了系统中常见的 wakelock 来源:
| wakelock 名称 | 持有者 | 用途 |
|---|---|---|
main |
PMS(通过 libsuspend) | 汇总所有用户空间 wakelock |
PowerManagerService.WakeLocks |
PMS | partial wake_lock 汇总 |
PowerManagerService.Display |
PMS | 屏幕供电期间持有 |
wlan_wake |
WiFi 驱动 | 数据收发期间持有 |
alarm |
Alarm 驱动 | 有 pending alarm 时持有 |
PowerManagerService.Broadcasts |
PMS | 广播分发期间持有 |
event0-xxx |
input 子系统 | 按键事件处理期间持有 |
九、autosleep vs wakeup_count 对比
下表对比两种休眠决策机制的核心差异:
| 维度 | wakeup_count | autosleep |
|---|---|---|
| 休眠决策 | 用户空间决定何时休眠 | 内核自动决定 |
| 实现复杂度 | 简单,仅需 sysfs | 需要内核线程 |
| 竞态防护 | wakeup_count 写入检查 | wakelock 天然无竞态 |
| CPU 唤醒次数 | 用户空间可能反复尝试 | 仅在有 wakelock 释放时尝试 |
| 适用场景 | 简单系统 / 调试 | 生产环境(Android 默认) |
关键设计:Android 优先使用 autosleep(当内核支持时),回退到 wakeup_count 模式------autosleep 更高效,但 wakeup_count 是标准 Linux 方案,兼容性更好。
十、小结
内核 wakelock 是连接用户空间与内核休眠机制的核心桥梁:
- 数据结构 :
wakeup_source记录名称、活跃状态、时间统计 - 设计哲学:只要有活跃 wakelock,内核绝不休眠------避免了竞态问题
- autosleep:内核线程自动管理休眠,用户空间仅需启用/禁用
- sysfs 接口 :
wake_lock/wake_unlock/autosleep/wakeup_sources - 统计机制:每个 wakelock 的累计活跃时间是功耗分析的宝贵数据
下一篇将分析另一个关键的唤醒机制:Alarm 定时唤醒,看 RTC 如何让系统在指定时间"准时醒来"。