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 sleepRTC 芯片仍由独立电源供电持续计时到达设定时间后触发中断唤醒系统。四、AlarmManagerService — 框架层 Alarm 管理4.1 数据结构源码路径frameworks/base/services/core/java/com/android/server/AlarmManagerService.javaAlarmManagerService简称 AMS注意不是 ActivityManagerService维护一个按触发时间排序的批次队列// frameworks/base/services/core/java/com/android/server/AlarmManagerService.javapublicclassAlarmManagerServiceextendsSystemService{// ...finalArrayListBatchmAlarmBatchesnewArrayList();// 按触发时间排序的批次finalclassBatch{longstart;// 批次最早触发时间基于 elapsedRealtimelongend;// 批次最晚触发时间允许的最大延迟intflags;// 标志位如 FLAG_STANDALONEfinalArrayListAlarmalarmsnewArrayListAlarm();// 本批次的 Alarm 列表// ...}privatestaticclassAlarm{publicfinalinttype;// RTC_WAKEUP / RTC / ELAPSED_REALTIME_WAKEUP / ELAPSED_REALTIMEpublicfinallongwhenElapsed;// 触发时间基于 elapsedRealtimepublicfinallongmaxWhenElapsed;// 最晚触发时间publicfinalPendingIntentoperation;// 触发后执行的 PendingIntentpublicfinalStringpackageName;// 发起者包名publicfinalbooleanwakeup;// 是否唤醒设备// ...}}关键设计Batch将触发时间相近的 Alarm 合并为一个批次减少 CPU 唤醒次数。start和end定义了批次的触发窗口非精确闹钟可以在窗口内任意时刻触发。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.javaAlarmThread是AlarmManagerService的内部线程负责等待和分发 Alarm// frameworks/base/services/core/java/com/android/server/AlarmManagerService.javapublicclassAlarmManagerServiceextendsSystemService{// ...privatelongmNativeData;// Native 层 AlarmImpl 的指针privateclassAlarmThreadextendsThread{publicAlarmThread(){super(AlarmManager);}publicvoidrun(){ArrayListAlarmtriggerListnewArrayListAlarm();while(true){// 1. 等待 Native 层的 Alarm 驱动通知阻塞调用intresultwaitForAlarm(mNativeData);// 2. 获取当前时间finallongnowRTCSystem.currentTimeMillis();finallongnowELAPSEDSystemClock.elapsedRealtime();// 3. 处理时间变更通知if((resultTIME_CHANGED_MASK)!0){// 系统时间被修改重新调度所有 AlarmrebatchAllAlarms();}// 4. 取出所有到期的 Alarmsynchronized(mLock){booleanhasWakeuptriggerAlarmsLocked(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.cppAndroid 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(typeANDROID_ALARM_TYPE_COUNT){errnoEINVAL;return-1;}if(!ts-tv_nsec!ts-tv_sec){ts-tv_nsec1;// 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];intneventsepoll_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-stateALARMTIMER_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;rtcalarmtimer_get_rtcdev();if(!rtc)return0;// 查找所有队列中最近的 alarmfor(i0;iALARM_NUMTYPE;i){structalarm_base*basealarm_bases[i];structtimerqueue_node*next;spin_lock_irqsave(base-lock,flags);nexttimerqueue_getnext(base-timerqueue);if(next(minnext-expires||!min)){minnext-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 sleepRTC 芯片仍会在指定时刻触发中断唤醒系统。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 AlarmAndroid 设备还存在多种硬件唤醒源。以下按类型枚举。7.1 GPIO 唤醒源最常见的按键类唤醒GPIO 源对应事件GPIO 标签示例电源键KEY_POWERgpio-keys音量 / 音量-KEY_VOLUMEUP / KEY_VOLUMEDOWNgpio-keysHome 键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中断名rtc0或pm8xxx_rtc7.3 Modem基带处理器源Modem 到 AP 的 IPC 中断来电、短信、网络事件高通设备的实现drivers/soc/qcom/smd.c或drivers/soc/qcom/glink.c中断名smd/glink/ipc_router7.4 USB 插拔源USB PHY 或充电 IC 的 VBUS 检测中断中断名usb-vbus/charger/usb_id7.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-Ertc|wake|key|wlan|smd九、Alarm 与 Doze 模式的交互Android 6 引入的 Doze 模式低电耗模式对 Alarm 的管理更严格9.1 Doze 维护窗口源码路径frameworks/base/services/core/java/com/android/server/DeviceIdleController.javaDoze 模式下设备进入深度休眠后Alarm 被限制只能在固定的维护窗口Maintenance Window触发设备进入 Doze 后非白名单应用的 Alarm 被延迟定期退出 Doze 的维护窗口期间批量处理积压的 Alarm随着进入 Doze 的时间增长维护窗口的间隔越来越长9.2 优先级分类类型Doze 下的行为API 方法普通 Alarm被延迟到下一个维护窗口set() / setRepeating()高优先级 Alarm允许立即触发setExact() / setExactAndAllowWhileIdle()闹钟 Alarm始终允许且系统会在触发前提前唤醒setAlarmClock()十、小结本篇完成了 Android Alarm 系统的完整分析框架层AlarmManagerService 维护触发时间优先队列AlarmThread 等待和分发 AlarmNative 层通过 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 的设计。