Android 7系统休眠唤醒(七)内核层—wakelock与autosleep机制

系列目录第一篇:电源管理架构全景图 | 第二篇:开机全链路---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_timetotal_timemax_time)是功耗分析的宝贵数据------通过 debugfs 可以查看每个唤醒源的累积活跃时间,定位功耗热点。

核心字段:

  • active:是否处于活跃状态。为 true 时阻止系统休眠
  • timer:超时定时器。pm_wakeup_event(ws, timeout)timeout 毫秒后自动释放 wakelock
  • total_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);

关键设计 :创建和注册分离------先 createadd,允许驱动在注册前初始化自定义字段。销毁也是先 removedestroy,确保注销后不会有新的访问。

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 模式下,用户空间每次想休眠都需要:

  1. 确认有 wakelock 未释放 → 不写 state
  2. wakelock 全部释放 → 写 /sys/power/state 触发休眠
  3. 唤醒后 → 循环

这种"用户空间轮询"的方式繁琐且效率低。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。

关键机制:

  1. wait_event_freezable() --- 这是一个可冻结的等待。在休眠过程中,autosleep 线程本身被冻结,唤醒后继续执行
  2. pm_wakeup_pending() --- 检查是否有活跃 wakelock。如果有,线程继续等待
  3. 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 就是通过这两个节点管理 main wakelock 的------用户空间所有 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_timemax_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_initwake_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 是连接用户空间与内核休眠机制的核心桥梁:

  1. 数据结构wakeup_source 记录名称、活跃状态、时间统计
  2. 设计哲学:只要有活跃 wakelock,内核绝不休眠------避免了竞态问题
  3. autosleep:内核线程自动管理休眠,用户空间仅需启用/禁用
  4. sysfs 接口wake_lock/wake_unlock/autosleep/wakeup_sources
  5. 统计机制:每个 wakelock 的累计活跃时间是功耗分析的宝贵数据

下一篇将分析另一个关键的唤醒机制:Alarm 定时唤醒,看 RTC 如何让系统在指定时间"准时醒来"。

相关推荐
starvapour2 小时前
在关机键损坏的情况下让手机关机
android·adb·手机
AFinalStone2 小时前
Android 7系统休眠唤醒(五)休眠全链路
android·电源管理·休眠唤醒
zzq77973 小时前
Android 风险识别机制实测:从录屏悬浮窗到远程控制的边界评估
android·安全·安卓·app加固·御盾安全
码云数智-园园4 小时前
MySQL 慢查询排查完整流程
android
码云骑士4 小时前
【Android Performance】进程关联启动治理详解——从唤醒链分析到自启动管控的完整方案
android
心念枕惊5 小时前
PHP 在领域驱动(DDD)设计中的核心实践
android·开发语言·php
蜡台5 小时前
Android WebView 设计指南
android·java·kotlin
晓梦林5 小时前
[Dest0g3 520迎新赛]EasyPHP-学习笔记
android·笔记·学习
朱涛的自习室6 小时前
Munk AI 桌面端「预告」
android·前端·人工智能