Android 7系统休眠唤醒(八)内核层—Alarm定时唤醒与硬件唤醒源
系列目录:第一篇:电源管理架构全景图 | 第二篇:开机全链路 | 第三篇:关机/重启全链路 | 第四篇:休眠唤醒与开关机对比 | 第五篇:休眠全链路 | 第六篇:唤醒全链路 | 第七篇:wakelock与autosleep |第八篇:Alarm定时唤醒与硬件唤醒源| 第九篇: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)维护一个按触发时间排序的批次队列:
// frameworks/base/services/core/java/com/android/server/AlarmManagerService.javapublicclassAlarmManagerServiceextendsSystemService{// ...finalArrayList<Batch>mAlarmBatches=newArrayList<>();// 按触发时间排序的批次finalclassBatch{longstart;// 批次最早触发时间(基于 elapsedRealtime)longend;// 批次最晚触发时间(允许的最大延迟)intflags;// 标志位,如 FLAG_STANDALONEfinalArrayList<Alarm>alarms=newArrayList<Alarm>();// 本批次的 Alarm 列表// ...}privatestaticclassAlarm{publicfinalinttype;// RTC_WAKEUP / RTC / ELAPSED_REALTIME_WAKEUP / ELAPSED_REALTIMEpublicfinallongwhenElapsed;// 触发时间(基于 elapsedRealtime)publicfinallongmaxWhenElapsed;// 最晚触发时间publicfinalPendingIntentoperation;// 触发后执行的 PendingIntentpublicfinalStringpackageName;// 发起者包名publicfinalbooleanwakeup;// 是否唤醒设备// ...}}关键设计:
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:
// frameworks/base/services/core/java/com/android/server/AlarmManagerService.javapublicclassAlarmManagerServiceextendsSystemService{// ...privatelongmNativeData;// Native 层 AlarmImpl 的指针privateclassAlarmThreadextendsThread{publicAlarmThread(){super("AlarmManager");}publicvoidrun(){ArrayList<Alarm>triggerList=newArrayList<Alarm>();while(true){// 1. 等待 Native 层的 Alarm 驱动通知(阻塞调用)intresult=waitForAlarm(mNativeData);// 2. 获取当前时间finallongnowRTC=System.currentTimeMillis();finallongnowELAPSED=SystemClock.elapsedRealtime();// 3. 处理时间变更通知if((result&TIME_CHANGED_MASK)!=0){// 系统时间被修改,重新调度所有 AlarmrebatchAllAlarms();}// 4. 取出所有到期的 Alarmsynchronized(mLock){booleanhasWakeup=triggerAlarmsLocked(triggerList,nowELAPSED,nowRTC);// ... 处理非唤醒 Alarm 的延迟逻辑 ...deliverAlarmsLocked(triggerList,nowELAPSED);// 分发 Alarm}// 5. 重新设置下一个最近的 AlarmrescheduleKernelAlarmsLocked();}}}privatenativeintwaitForAlarm(longnativeData);// 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:
// frameworks/base/services/core/jni/com_android_server_AlarmManagerService.cppclassAlarmImplTimerFd:publicAlarmImpl{// ...intepollfd;// epoll 文件描述符intrtc_id;// RTC 设备 IDintset(inttype,structtimespec*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}structitimerspecspec;memset(&spec,0,sizeof(spec));memcpy(&spec.it_value,ts,sizeof(spec.it_value));returntimerfd_settime(fds[type],TFD_TIMER_ABSTIME,&spec,NULL);// 设置定时器}intwaitForAlarm(){epoll_event events[N_ANDROID_TIMERFDS];intnevents=epoll_wait(epollfd,events,N_ANDROID_TIMERFDS,-1);// 阻塞等待// ... 处理事件 ...returnresult;}};关键设计:
timerfd_settime()设置定时器到期时间,epoll_wait()阻塞等待 timerfd 变为可读。CLOCK_BOOTTIME_ALARM类型的 timerfd 能在设备休眠时唤醒 CPU。
六、内核层 — alarmtimer 子系统
6.1 alarmtimer 架构
源码路径:kernel/msm-3.18/kernel/time/alarmtimer.c
// kernel/time/alarmtimer.cstaticstructalarm_base{spinlock_tlock;// 自旋锁structtimerqueue_headtimerqueue;// 定时器队列(红黑树)ktime_t(*gettime)(void);// 获取当前时间的函数clockid_tbase_clockid;// 时钟类型}alarm_bases[ALARM_NUMTYPE];// RTC 定时器相关staticstructrtc_timerrtctimer;// RTC 定时器staticstructrtc_device*rtcdev;// RTC 设备alarmtimer 维护多个定时器队列(alarm_bases数组),每个队列对应一种时钟类型:
- ALARM_REALTIME:基于墙上时间的定时器
- ALARM_BOOTTIME:基于启动时间的定时器(timerfd 使用的就是此队列)
6.2 设置硬件 RTC 定时器
源码路径:kernel/msm-3.18/kernel/time/alarmtimer.c
当添加 alarm 时,需要将其插入定时器队列,并在必要时编程 RTC 硬件:
// kernel/time/alarmtimer.cstaticvoidalarmtimer_enqueue(structalarm_base*base,structalarm*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 硬件:
// kernel/time/alarmtimer.cstaticintalarmtimer_suspend(structdevice*dev){structrtc_timetm;ktime_tmin,now;structrtc_device*rtc;inti;rtc=alarmtimer_get_rtcdev();if(!rtc)return0;// 查找所有队列中最近的 alarmfor(i=0;i<ALARM_NUMTYPE;i++){structalarm_base*base=&alarm_bases[i];structtimerqueue_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));}return0;}关键设计:
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
显示每个唤醒源的统计信息:
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
直接查看每个中断的触发次数,可以间接判断是哪个硬件唤醒源唤醒了设备:
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 的设计。
