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 唤醒次数。startend定义了批次的触发窗口,非精确闹钟可以在窗口内任意时刻触发。

4.2 Alarm 类型

类型时间基准是否唤醒设备
RTC_WAKEUPSystem.currentTimeMillis() (墙上时间)✅ 唤醒
RTCSystem.currentTimeMillis() (墙上时间)❌ 不唤醒
ELAPSED_REALTIME_WAKEUPSystemClock.elapsedRealtime() (启动时间)✅ 唤醒
ELAPSED_REALTIMESystemClock.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:

// 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_POWERgpio-keys
音量+ / 音量-KEY_VOLUMEUP / KEY_VOLUMEDOWNgpio-keys
Home 键KEY_HOMEgpio-keys
耳机插拔SW_HEADPHONE_INSERTheadset-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

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

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 系统的完整分析:

  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 的设计。