系列目录 :第一篇:电源管理架构全景图 | 第二篇:开机全链路---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 唤醒次数。start和end定义了批次的触发窗口,非精确闹钟可以在窗口内任意时刻触发。
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
AlarmThread 是 AlarmManagerService 的内部线程,负责等待和分发 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) - 中断名:
rtc0或pm8xxx_rtc
7.3 Modem(基带处理器)
- 源:Modem 到 AP 的 IPC 中断(来电、短信、网络事件)
- 高通设备的实现:
drivers/soc/qcom/smd.c或drivers/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 系统的完整分析:
- 框架层:AlarmManagerService 维护触发时间优先队列,AlarmThread 等待和分发 Alarm
- Native 层:通过 timerfd (CLOCK_BOOTTIME_ALARM) 与内核 alarmtimer 交互
- 内核层:alarmtimer 子系统管理定时器红黑树,编程 RTC 硬件定时器
- 硬件层:RTC 芯片在 CPU 休眠期间持续计时,到期触发中断唤醒 CPU
此外枚举了 GPIO 按键、RTC、Modem、USB、WiFi、传感器等主要硬件唤醒源,以及 /sys/power/wakeup_sources 和 /proc/interrupts 等调试接口。
下一篇将回到 Native 层,深入 libsuspend 的实现细节和 Power HAL 的设计。