Android 7系统休眠唤醒(八)内核层—Alarm定时唤醒与硬件唤醒源

系列目录第一篇:电源管理架构全景图 | 第二篇:开机全链路---BootROM到Launcher | 第三篇:关机/重启全链路---ShutdownThread到kernel_power_off | 第四篇:休眠唤醒与开关机---核心差异深度对比 | 第五篇:休眠全链路---PMS到Kernel Suspend | 第六篇:唤醒全链路---Kernel Resume到屏幕点亮 | 第七篇:内核层---wakelock与autosleep机制 | 第八篇:内核层---Alarm定时唤醒与硬件唤醒源 | 第九篇:Native层---libsuspend与Power HAL | 第十篇:实战调试与问题排查


一、为什么要深入理解 Alarm 唤醒

你可能遇到过这些问题:

  • 闹钟设置后,设备休眠了还能准时响吗?内核休眠时谁在计时?
  • 为什么有些定时任务在 Doze 模式下不执行?Alarm 被延迟了吗?
  • 除了 RTC 闹钟,还有哪些硬件能唤醒系统?它们在内核中如何注册?

第七篇分析了 wakelock 如何"阻止"系统休眠,本篇分析另一个方向------Alarm 系统如何"定时唤醒"系统。此外,还将枚举 Android 设备中所有的硬件唤醒源,以及它们在内核和驱动层的实现原理。


二、定时唤醒的需求场景

Alarm 是 Android 定时唤醒机制的核心,支撑着以下典型场景:

  • 闹钟:用户在特定时间点被唤醒
  • Doze 维护窗口:Android 6+ 的 Doze 模式定期退出深度休眠以同步数据
  • 周期性任务:JobScheduler / AlarmManager 的定时任务
  • 网络协议保活:TCP keepalive、推送通道心跳
  • 系统维护:定期检查更新、清理缓存

这些场景的共同需求:设备在指定时间点(精确到毫秒)从休眠中醒来。


三、Android Alarm 的完整架构

在深入源码之前,先看整体架构。Alarm 系统从应用层到硬件层分为五个层次:

复制代码
应用层
  AlarmManager API (set / setExact / setRepeating / setAlarmClock)

框架层
  AlarmManagerService (AMS的Alarm)
  维护 Alarm 优先队列(按触发时间排序)

Native层
  alarm_driver (timerfd / /dev/alarm / RTC_WAKEUP ioctl)

内核层
  RTC (Real-Time Clock) 驱动
  alarmtimer 子系统
    → rtc_timer → 编程 RTC 硬件定时器

硬件层
  RTC 芯片 PMIC (Power Management IC)
  在 CPU 休眠期间持续运行
  到达设定时间 → 产生硬件中断 → 唤醒 CPU

关键设计:Alarm 系统的核心是 RTC 硬件定时器。即使 CPU 进入 deep sleep,RTC 芯片仍由独立电源供电持续计时,到达设定时间后触发中断唤醒系统。


四、AlarmManagerService --- 框架层 Alarm 管理

4.1 数据结构

源码路径frameworks/base/services/core/java/com/android/server/AlarmManagerService.java

AlarmManagerService(简称 AMS,注意不是 ActivityManagerService)维护一个按触发时间排序的批次队列:

java 复制代码
// frameworks/base/services/core/java/com/android/server/AlarmManagerService.java
public class AlarmManagerService extends SystemService {
    // ...
    final ArrayList<Batch> mAlarmBatches = new ArrayList<>();  // 按触发时间排序的批次

    final class Batch {
        long start;     // 批次最早触发时间(基于 elapsedRealtime)
        long end;       // 批次最晚触发时间(允许的最大延迟)
        int flags;      // 标志位,如 FLAG_STANDALONE
        final ArrayList<Alarm> alarms = new ArrayList<Alarm>();  // 本批次的 Alarm 列表
        // ...
    }

    private static class Alarm {
        public final int type;              // RTC_WAKEUP / RTC / ELAPSED_REALTIME_WAKEUP / ELAPSED_REALTIME
        public final long whenElapsed;      // 触发时间(基于 elapsedRealtime)
        public final long maxWhenElapsed;   // 最晚触发时间
        public final PendingIntent operation;  // 触发后执行的 PendingIntent
        public final String packageName;    // 发起者包名
        public final boolean wakeup;        // 是否唤醒设备
        // ...
    }
}

关键设计Batch 将触发时间相近的 Alarm 合并为一个批次,减少 CPU 唤醒次数。startend 定义了批次的触发窗口,非精确闹钟可以在窗口内任意时刻触发。

4.2 Alarm 类型

类型 时间基准 是否唤醒设备
RTC_WAKEUP System.currentTimeMillis() (墙上时间) ✅ 唤醒
RTC System.currentTimeMillis() (墙上时间) ❌ 不唤醒
ELAPSED_REALTIME_WAKEUP SystemClock.elapsedRealtime() (启动时间) ✅ 唤醒
ELAPSED_REALTIME SystemClock.elapsedRealtime() (启动时间) ❌ 不唤醒

_WAKEUP 后缀表示设备休眠时仍需触发。不带 _WAKEUP 的仅在设备已经清醒时触发。

4.3 时间基准的选择

  • RTC 类型使用墙上时间(wall clock),受用户修改系统时间影响
  • ELAPSED_REALTIME 类型使用开机以来的时间,单调递增,不受用户修改时间影响

对于闹钟场景,用 RTC 类型(用户期望在确定的"墙钟时间"响起)。对于"每 30 分钟执行一次"的周期性任务,用 ELAPSED_REALTIME 更合适。

4.4 Alarm 触发流程

源码路径frameworks/base/services/core/java/com/android/server/AlarmManagerService.java

AlarmThreadAlarmManagerService 的内部线程,负责等待和分发 Alarm:

java 复制代码
// frameworks/base/services/core/java/com/android/server/AlarmManagerService.java
public class AlarmManagerService extends SystemService {
    // ...
    private long mNativeData;  // Native 层 AlarmImpl 的指针

    private class AlarmThread extends Thread {
        public AlarmThread() {
            super("AlarmManager");
        }

        public void run() {
            ArrayList<Alarm> triggerList = new ArrayList<Alarm>();

            while (true) {
                // 1. 等待 Native 层的 Alarm 驱动通知(阻塞调用)
                int result = waitForAlarm(mNativeData);

                // 2. 获取当前时间
                final long nowRTC = System.currentTimeMillis();
                final long nowELAPSED = SystemClock.elapsedRealtime();

                // 3. 处理时间变更通知
                if ((result & TIME_CHANGED_MASK) != 0) {
                    // 系统时间被修改,重新调度所有 Alarm
                    rebatchAllAlarms();
                }

                // 4. 取出所有到期的 Alarm
                synchronized (mLock) {
                    boolean hasWakeup = triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);
                    // ... 处理非唤醒 Alarm 的延迟逻辑 ...
                    deliverAlarmsLocked(triggerList, nowELAPSED);  // 分发 Alarm
                }

                // 5. 重新设置下一个最近的 Alarm
                rescheduleKernelAlarmsLocked();
            }
        }
    }

    private native int waitForAlarm(long nativeData);  // JNI 调用
}

关键设计waitForAlarm() 是 native 方法,底层通过 epoll_wait() 阻塞在 timerfd 上。当 RTC 硬件定时器到期触发中断后,timerfd 变为可读,epoll_wait() 返回,线程继续处理到期的 Alarm。


五、Native 层 --- alarm 驱动交互

5.1 timerfd 机制(Android 4.4+)

源码路径frameworks/base/services/core/jni/com_android_server_AlarmManagerService.cpp

Android 4.4 开始,框架层的 Alarm 使用 Linux 标准 timerfd 代替了早期的 /dev/alarm

cpp 复制代码
// frameworks/base/services/core/jni/com_android_server_AlarmManagerService.cpp
class AlarmImplTimerFd : public AlarmImpl {
    // ...
    int epollfd;  // epoll 文件描述符
    int rtc_id;   // RTC 设备 ID

    int set(int type, struct timespec *ts) {
        if (type > ANDROID_ALARM_TYPE_COUNT) {
            errno = EINVAL;
            return -1;
        }

        if (!ts->tv_nsec && !ts->tv_sec) {
            ts->tv_nsec = 1;  // timerfd 解释 0 为解除,替换为 1ns
        }

        struct itimerspec spec;
        memset(&spec, 0, sizeof(spec));
        memcpy(&spec.it_value, ts, sizeof(spec.it_value));

        return timerfd_settime(fds[type], TFD_TIMER_ABSTIME, &spec, NULL);  // 设置定时器
    }

    int waitForAlarm() {
        epoll_event events[N_ANDROID_TIMERFDS];
        int nevents = epoll_wait(epollfd, events, N_ANDROID_TIMERFDS, -1);  // 阻塞等待
        // ... 处理事件 ...
        return result;
    }
};

关键设计timerfd_settime() 设置定时器到期时间,epoll_wait() 阻塞等待 timerfd 变为可读。CLOCK_BOOTTIME_ALARM 类型的 timerfd 能在设备休眠时唤醒 CPU。


六、内核层 --- alarmtimer 子系统

6.1 alarmtimer 架构

源码路径kernel/msm-3.18/kernel/time/alarmtimer.c

c 复制代码
// kernel/time/alarmtimer.c
static struct alarm_base {
    spinlock_t          lock;           // 自旋锁
    struct timerqueue_head  timerqueue; // 定时器队列(红黑树)
    ktime_t             (*gettime)(void);  // 获取当前时间的函数
    clockid_t           base_clockid;   // 时钟类型
} alarm_bases[ALARM_NUMTYPE];

// RTC 定时器相关
static struct rtc_timer     rtctimer;   // RTC 定时器
static struct rtc_device    *rtcdev;    // RTC 设备

alarmtimer 维护多个定时器队列(alarm_bases 数组),每个队列对应一种时钟类型:

  • ALARM_REALTIME:基于墙上时间的定时器
  • ALARM_BOOTTIME:基于启动时间的定时器(timerfd 使用的就是此队列)

6.2 设置硬件 RTC 定时器

源码路径kernel/msm-3.18/kernel/time/alarmtimer.c

当添加 alarm 时,需要将其插入定时器队列,并在必要时编程 RTC 硬件:

c 复制代码
// kernel/time/alarmtimer.c
static void alarmtimer_enqueue(struct alarm_base *base, struct alarm *alarm) {
    // 如果已入队,先移除
    if (alarm->state & ALARMTIMER_STATE_ENQUEUED)
        timerqueue_del(&base->timerqueue, &alarm->node);

    // 插入到定时器队列(红黑树)
    timerqueue_add(&base->timerqueue, &alarm->node);
    alarm->state |= ALARMTIMER_STATE_ENQUEUED;
}

关键设计alarmtimer_enqueue() 将 alarm 插入 alarm_base 的红黑树队列。插入后,如果该 alarm 是队列中最早到期的,内核会在系统休眠时通过 alarmtimer_suspend() 将其编程到 RTC 硬件。

6.3 系统休眠时的 RTC 编程

源码路径kernel/msm-3.18/kernel/time/alarmtimer.c

当系统进入休眠时,alarmtimer_suspend() 会查找最近的 alarm 并编程 RTC 硬件:

c 复制代码
// kernel/time/alarmtimer.c
static int alarmtimer_suspend(struct device *dev) {
    struct rtc_time tm;
    ktime_t min, now;
    struct rtc_device *rtc;
    int i;

    rtc = alarmtimer_get_rtcdev();
    if (!rtc)
        return 0;

    // 查找所有队列中最近的 alarm
    for (i = 0; i < ALARM_NUMTYPE; i++) {
        struct alarm_base *base = &alarm_bases[i];
        struct timerqueue_node *next;

        spin_lock_irqsave(&base->lock, flags);
        next = timerqueue_getnext(&base->timerqueue);
        if (next && (min > next->expires || !min)) {
            min = next->expires;  // 记录最近的到期时间
        }
        spin_unlock_irqrestore(&base->lock, flags);
    }

    // 如果找到 alarm,编程 RTC 硬件
    if (min) {
        rtc_timer_cancel(rtc, &rtctimer);
        // 设置 RTC 定时器在 min 时刻触发
        rtc_timer_start(rtc, &rtctimer, min, ktime_set(0, 0));
    }

    return 0;
}

关键设计alarmtimer_suspend() 在系统休眠前被调用,它会遍历所有 alarm 队列,找到最近的到期时间,然后编程 RTC 硬件定时器。这样即使 CPU 进入 deep sleep,RTC 芯片仍会在指定时刻触发中断唤醒系统。

6.4 RTC 中断的唤醒路径

当 RTC 硬件到达设定时间:

复制代码
RTC 硬件定时器到期
  → 产生 IRQ(已在 suspend 前标记为 wakeup capable)
    → 中断控制器唤醒 CPU
      → CPU 退出 deep sleep
        → RTC 驱动 IRQ handler 执行
          → rtc_timer_handler()
            → alarmtimer 子系统收到通知
              → 触发到期的 alarm 回调
                → timerfd 变为可读
                  → epoll_wait 返回
                    → JNI 层 waitForAlarm() 返回
                      → Java 层 AlarmThread 继续处理

七、硬件唤醒源全枚举

除了 RTC Alarm,Android 设备还存在多种硬件唤醒源。以下按类型枚举。

7.1 GPIO 唤醒源

最常见的按键类唤醒:

GPIO 源 对应事件 GPIO 标签(示例)
电源键 KEY_POWER gpio-keys
音量+ / 音量- KEY_VOLUMEUP / KEY_VOLUMEDOWN gpio-keys
Home 键 KEY_HOME gpio-keys
耳机插拔 SW_HEADPHONE_INSERT headset-detect

内核中通过 irq_set_irq_wake() 注册为唤醒源(详见第六篇)。

7.2 RTC(实时时钟)

  • 源:rtc0(通常挂载在 I2C/SPI 总线上)
  • 内核驱动:drivers/rtc/rtc-*.c(如 rtc-pm8xxx.c 用于高通 PMIC)
  • 中断名:rtc0pm8xxx_rtc

7.3 Modem(基带处理器)

  • 源:Modem 到 AP 的 IPC 中断(来电、短信、网络事件)
  • 高通设备的实现:drivers/soc/qcom/smd.cdrivers/soc/qcom/glink.c
  • 中断名:smd / glink / ipc_router

7.4 USB 插拔

  • 源:USB PHY 或充电 IC 的 VBUS 检测中断
  • 中断名:usb-vbus / charger / usb_id

7.5 WiFi

  • 源:WiFi 芯片的 GPIO 唤醒线(WOW --- Wake on Wireless)
  • 中断名:wlan / wlan_hostwake
  • 用途:收到推送通知时唤醒设备

7.6 传感器

  • 源:传感器 Hub 或独立传感器的中断线
  • 中断名:sns / sensor_irq
  • 用途:计步器、接近传感器、加速度计触发

7.7 SD 卡

  • 源:SD 卡检测引脚中断
  • 中断名:sd_detect

八、内核层的唤醒源查看接口

8.1 /sys/power/wakeup_sources

显示每个唤醒源的统计信息:

bash 复制代码
cat /sys/kernel/debug/wakeup_sources

输出示例:

复制代码
name          active_count  event_count  wakeup_count  expire_count  ...
event0        145           89           12            0             ...
alarmtimer    2345          2345         456           0             ...
wlan_wake     345           123          34            0             ...
smd           89            45           15            0             ...

8.2 /sys/power/wakeup_count

/sys/power/wakeup_sources 统计为基础,用于竞态检测(见第五篇)。

8.3 /proc/interrupts

直接查看每个中断的触发次数,可以间接判断是哪个硬件唤醒源唤醒了设备:

bash 复制代码
cat /proc/interrupts | grep -E "rtc|wake|key|wlan|smd"

九、Alarm 与 Doze 模式的交互

Android 6 引入的 Doze 模式(低电耗模式)对 Alarm 的管理更严格:

9.1 Doze 维护窗口

源码路径frameworks/base/services/core/java/com/android/server/DeviceIdleController.java

Doze 模式下,设备进入深度休眠后,Alarm 被限制只能在固定的维护窗口(Maintenance Window)触发:

  • 设备进入 Doze 后,非白名单应用的 Alarm 被延迟
  • 定期退出 Doze 的维护窗口期间,批量处理积压的 Alarm
  • 随着进入 Doze 的时间增长,维护窗口的间隔越来越长

9.2 优先级分类

类型 Doze 下的行为 API 方法
普通 Alarm 被延迟到下一个维护窗口 set() / setRepeating()
高优先级 Alarm 允许立即触发 setExact() / setExactAndAllowWhileIdle()
闹钟 Alarm 始终允许,且系统会在触发前提前唤醒 setAlarmClock()

十、小结

本篇完成了 Android Alarm 系统的完整分析:

  1. 框架层:AlarmManagerService 维护触发时间优先队列,AlarmThread 等待和分发 Alarm
  2. Native 层:通过 timerfd (CLOCK_BOOTTIME_ALARM) 与内核 alarmtimer 交互
  3. 内核层:alarmtimer 子系统管理定时器红黑树,编程 RTC 硬件定时器
  4. 硬件层:RTC 芯片在 CPU 休眠期间持续计时,到期触发中断唤醒 CPU

此外枚举了 GPIO 按键、RTC、Modem、USB、WiFi、传感器等主要硬件唤醒源,以及 /sys/power/wakeup_sources/proc/interrupts 等调试接口。

下一篇将回到 Native 层,深入 libsuspend 的实现细节和 Power HAL 的设计。

相关推荐
xiaopang2 小时前
RK3568 AOSP 编译与快速部署实战
android
码农coding3 小时前
android12 SystemUI之通知
android
summerkissyou19873 小时前
android - 性能 - Perfetto - cpu 分析教程及例子
android·性能
AFinalStone5 小时前
Android 7系统休眠唤醒(七)内核层—wakelock与autosleep机制
android·休眠唤醒
starvapour5 小时前
在关机键损坏的情况下让手机关机
android·adb·手机
AFinalStone6 小时前
Android 7系统休眠唤醒(五)休眠全链路
android·电源管理·休眠唤醒
zzq77977 小时前
Android 风险识别机制实测:从录屏悬浮窗到远程控制的边界评估
android·安全·安卓·app加固·御盾安全
码云数智-园园7 小时前
MySQL 慢查询排查完整流程
android