11.1 基本原理
Alarm 鬧鍾是 android 系統中在標准 RTC 驅動上開發的一個新的驅動,提供了一個定時器 用於把設備從睡眠狀態喚醒,當然因為它是依賴 RTC 驅動的,所以它同時還可以為系統提 供一個掉電下還能運行的實時時鍾。
當系統斷電時,主板上的 rtc 芯片將繼續維持系統的時間,這樣保證再次開機后系統的時間 不會錯誤。當系統開始時,內核從 RTC 中讀取時間來初始化系統時間,關機時便又將系統 時間寫回到 rtc 中,關機階段將有主板上另外的電池來供應 rtc 計時。Android 中的 Alarm 在設備處於睡眠模式時仍保持活躍,它可以設置來喚醒設備。
上圖為android系統中 alarm 和 rtc 驅動的框架。Alarm依賴於rtc 驅動框架,但它不是一個 rtc 驅動,主要還是實現定時鬧鍾的功能。相關源代碼在 kernel/drivers/rtc/alarm.c 和 drivers/rtc/alarm_dev.c。
其中 alarm.c 文件實現的是所有 alarm 設備的通用性操作,它創建了一個設備 class,而
www.linuxidc.com
Linux公社(LinuxIDC.com) 是包括Ubuntu,Fedora,SUSE技術,最新IT資訊等Linux專業類網站。
alarm_dev.c 則創建具體的 alarm 設備,注冊到該設備 class 中。 alarm.c 還實現了與 interface.c 的接口,即建立了與具體 rtc 驅動和rtc 芯片的聯系。 alarm_dev.c在 alarm.c 基礎包裝了一層, 主要是實現了標准的 miscdevice 接口,提供給應用層調用。
可以這樣概括:alarm.c 實現的是機制和框架,alarm_dev.c 則是實現符合這個框架的設備驅 動,alarm_dev.c 相當於在底層硬件 rtc 鬧鍾功能的基礎上虛擬了多個軟件鬧鍾。
11.2 關鍵數據結構
alarm 定義在 include/linux/android_alarm.h 中。 struct alarm { struct rb_node node; enum android_alarm_type type; ktime_t softexpires; //最早的到期時間 ktime_t expires; //絕對到期時間 void (*function)(struct alarm *); //當到期時系統回調該函數 };
這個結構體代表 alarm 設備,所有的 alarm 設備按照它們過期時間的先后被組織成一 個紅黑樹,alarm.node 即紅黑樹的節點,alarm 設備通過這個變量插入紅黑樹。 alarm.type 是類型,android 中一共定義了如下 5 種類型,在現在的系統中每種類型只有一個設備。
enum android_alarm_type { /* return code bit numbers or set alarm arg */ ANDROID_ALARM_RTC_WAKEUP, ANDROID_ALARM_RTC, ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, ANDROID_ALARM_ELAPSED_REALTIME, ANDROID_ALARM_SYSTEMTIME, ANDROID_ALARM_TYPE_COUNT, /* return code bit numbers */ /* ANDROID_ALARM_TIME_CHANGE = 16 */ };
alarm_queue struct alarm_queue { struct rb_root alarms; //紅黑樹的根 struct rb_node *first; //指向第一個 alarm device,即最早到時的 struct hrtimer timer; //內核定時器,android 利用它來確定 alarm 過期時間 ktime_t delta; //是一個計算 elasped realtime 的修正值 bool stopped; ktime_t stopped_time; };
這個結構體用於將前面的 struct alarm 表示的設備組織成紅黑樹。它是基於內核定時器 來實現 alarm 的到期鬧鈴的。
11.3 關鍵代碼分析
alarm_dev.c
www.linuxidc.com
Linux公社(LinuxIDC.com) 是包括Ubuntu,Fedora,SUSE技術,最新IT資訊等Linux專業類網站。
該文件依賴於 alarm.c 提供的框架,實現了與應用層交互的功能,具體說就是暴露出 miscdevice 的設備接口。Alarm_dev.c 定義了幾個全局變量:
每種類型一個 alarm 設備,android 目前創建了 5 個 alarm 設備。 static struct alarm alarms[ANDROID_ALARM_TYPE_COUNT];
wake lock 鎖,當加鎖時,阻止系統進 suspend 狀態。 static struct wake_lock alarm_wake_lock;
標志位,alarm 設備是否被打開。 static int alarm_opened;
標志位,alarm 設備是否就緒。所謂就緒是指該 alarm 設備的鬧鈴時間到達,但原本等待在 該 alarm 設備上的進程還未喚醒,一旦喚醒,該標志清零。 static uint32_t alarm_pending;
標志位,表示 alarm 設備是否 enabled,表示該設備設置了鬧鈴時間(並且鬧鈴時間還未到) , 一旦鬧鈴時間到了,該標志清零。 static uint32_t alarm_enabled;
標志位,表示原先等待該 alarm 的進程被喚醒了(它們等待的 alarm 到時了)。 static uint32_t wait_pending; 該文件提供的主要函數有: 1,模塊初始化和 exit 函數:alarm_dev_init 和 alarm_dev_exit 2,模塊 miscdevice 標准接口函數:alarm_open、alarm_release 和 alarm_ioctl 3, alarm 定時時間到時候的回調函數:alarm_triggered
alarm_dev_init 初始化函數調用 misc_register 注冊一個 miscdevice。 static int __init alarm_dev_init(void){ int err; int i;
err = misc_register(&alarm_device); if (err) return err;
for (i = 0; i < ANDROID_ALARM_TYPE_COUNT; i++) alarm_init(&alarms[i], i, alarm_triggered); wake_lock_init(&alarm_wake_lock, WAKE_LOCK_SUSPEND, "alarm");
return 0; }
該設備稱為 alarm_device,定義如下: static struct miscdevice alarm_device = { .minor = MISC_DYNAMIC_MINOR, .name = "alarm", .fops = &alarm_fops, };
www.linuxidc.com
Linux公社(LinuxIDC.com) 是包括Ubuntu,Fedora,SUSE技術,最新IT資訊等Linux專業類網站。
對應的 file operations 為 alarm_fops,定義為: static const struct file_operations alarm_fops = { .owner = THIS_MODULE, .unlocked_ioctl = alarm_ioctl, .open = alarm_open, .release = alarm_release, };
然后為每個 alarm device 調用 alarm_init 初始化,這個函數代碼在 alarm.c 中,如下: void alarm_init(struct alarm *alarm, enum android_alarm_type type, void (*function)(struct alarm *)){ RB_CLEAR_NODE(&alarm->node); alarm->type = type; alarm->function = function; pr_alarm(FLOW, "created alarm, type %d, func %pF\n", type, function); }
就是初始化 alarm 結構體,設置其回調函數為 alarm_triggered。最后調用 wake_lock_init 初 始化 alarm_wake_lock,它是 suspend 型的。alarm_triggered 是回調函數,當定時鬧鈴的時間 到了,alarm_timer_triggered 函數會調用該函數(詳細請看 alarm.c 的 alarm_timer_triggered 函數)。
static void alarm_triggered(struct alarm *alarm){ unsigned long flags; uint32_t alarm_type_mask = 1U << alarm->type;
pr_alarm(INT, "alarm_triggered type %d\n", alarm->type); spin_lock_irqsave(&alarm_slock, flags); if (alarm_enabled & alarm_type_mask) { wake_lock_timeout(&alarm_wake_lock, 5 * HZ); alarm_enabled &= ~alarm_type_mask; alarm_pending |= alarm_type_mask; wake_up(&alarm_wait_queue); } spin_unlock_irqrestore(&alarm_slock, flags); }
這個函數里調用 wake_lock_timeout 對全局 alarm_wake_lock(超時鎖,超時時間是 5 秒) 加鎖,禁止對應的 alarm 設備。喚醒所有等待在該 alarm 設備上的進程。這時,如果 AP 層 呼叫 ioctl(fd, ANDROID_ALARM_WAIT),會返回表示等到 alarm 的返回值(這個會在 AlarmManagerSevice.java 中細述)。
alarm_ioctl 定義了以下命令: ANDROID_ALARM_CLEAR 清除 alarm,即 deactivate 這個 alarm ANDROID_ALARM_SET_OLD 設置 alarm 鬧鈴時間 ANDROID_ALARM_SET 同上 ANDROID_ALARM_SET_AND_WAIT_OLD 設置 alarm 鬧鈴時間並等待這個 alarm ANDROID_ALARM_SET_AND_WAIT 同上 ANDROID_ALARM_WAIT 等待 alarm ANDROID_ALARM_SET_RTC 設置 RTC 時間 ANDROID_ALARM_GET_TIME 讀取 alarm 時間,根據 alarm 類型又分四種情況
www.linuxidc.com
Linux公社(LinuxIDC.com) 是包括Ubuntu,Fedora,SUSE技術,最新IT資訊等Linux專業類網站。
alarm.c 該文件完成主要功能有: 創建一個 alarm class,所有 alarm 設備都屬於這個類; 注冊了 platform driver,提供 suspend 和 resume 支持; 實 現 了 一 系 列 函 數 , 包 括 alarm_init , alarm_start_range , alarm_cancel , alarm_timer_triggered 函數等。
Alarm.c 的初始化函數 alarm_driver_init 如下: static int __init alarm_driver_init(void){ int err; int i;
for (i = 0; i < ANDROID_ALARM_SYSTEMTIME; i++) { hrtimer_init(&alarms[i].timer, CLOCK_REALTIME, HRTIMER_MODE_ABS); alarms[i].timer.function = alarm_timer_triggered; } hrtimer_init(&alarms[ANDROID_ALARM_SYSTEMTIME].timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); alarms[ANDROID_ALARM_SYSTEMTIME].timer.function = alarm_timer_triggered; err = platform_driver_register(&alarm_driver); if (err < 0) goto err1; wake_lock_init(&alarm_rtc_wake_lock, WAKE_LOCK_SUSPEND, "alarm_rtc"); rtc_alarm_interface.class = rtc_class; err = class_interface_register(&rtc_alarm_interface); if (err < 0) goto err2;
return 0;
err2: wake_lock_destroy(&alarm_rtc_wake_lock); platform_driver_unregister(&alarm_driver); err1: return err; }
該函數初始化 5 個 alarm device 相關聯的 hrtimer 定時器,設置 hrtimer 定時器的回調函數為 alarm_timer_triggered 函數,再注冊一個 plateform driver 和 class interface。 如果設置了鬧 鈴時間,則內核通過 hrtimer 定時器來跟蹤是否到時間,到時后會觸發調用 hrtimer 的處理 函數 alarm_timer_triggered。alarm_timer_triggered 的 code 如下:
www.linuxidc.com
Linux公社(LinuxIDC.com) 是包括Ubuntu,Fedora,SUSE技術,最新IT資訊等Linux專業類網站。
static enum hrtimer_restart alarm_timer_triggered(struct hrtimer *timer){ struct alarm_queue *base; struct alarm *alarm; unsigned long flags; ktime_t now;
spin_lock_irqsave(&alarm_slock, flags);
base = container_of(timer, struct alarm_queue, timer); now = base->stopped ? base->stopped_time : hrtimer_cb_get_time(timer); now = ktime_sub(now, base->delta);
pr_alarm(INT, "alarm_timer_triggered type %d at %lld\n", base - alarms, ktime_to_ns(now));
while (base->first) { alarm = container_of(base->first, struct alarm, node); if (alarm->softexpires.tv64 > now.tv64) { pr_alarm(FLOW, "don't call alarm, %pF, %lld (s %lld)\n", alarm->function, ktime_to_ns(alarm->expires), ktime_to_ns(alarm->softexpires)); break; } base->first = rb_next(&alarm->node); rb_erase(&alarm->node, &base->alarms); RB_CLEAR_NODE(&alarm->node); pr_alarm(CALL, "call alarm, type %d, func %pF, %lld (s %lld)\n", alarm->type, alarm->function, ktime_to_ns(alarm->expires), ktime_to_ns(alarm->softexpires)); spin_unlock_irqrestore(&alarm_slock, flags); alarm->function(alarm); spin_lock_irqsave(&alarm_slock, flags); } if (!base->first) pr_alarm(FLOW, "no more alarms of type %d\n", base - alarms); update_timer_locked(base, true); spin_unlock_irqrestore(&alarm_slock, flags); return HRTIMER_NORESTART; }
它會輪詢紅黑樹中的所有 alarm 節點,符合條件的節點會執行 alarm.function(alarm),指向 alarm_dev.c 的 alarm_triggered 函數。因為我們在執行 alarm_dev.c 的 alarm_init 時,把每個 alarm 節點的 function 設置成了 alarm_triggered。
請注意這兩個函數的區別,alarm_triggered 和 alarm_timer_triggered。前者是 rtc 芯片的 alarm 中斷的回調函數,后者是 android alarm_queue->timer 到時的回調函數。
上面說到,alarm_driver_init 注冊了一個類接口 class_interface_register(&rtc_alarm_interface)
rtc_alarm_interface 的 code 如下: static struct class_interface rtc_alarm_interface = { .add_dev = &rtc_alarm_add_device, .remove_dev = &rtc_alarm_remove_device, };
在 rtc_alarm_add_device 中,注冊了一個 rtc 中斷 rtc_irq_register(rtc,&alarm_rtc_task)
www.linuxidc.com
Linux公社(LinuxIDC.com) 是包括Ubuntu,Fedora,SUSE技術,最新IT資訊等Linux專業類網站。
static int rtc_alarm_add_device(struct device *dev, struct class_interface *class_intf){ int err; struct rtc_device *rtc = to_rtc_device(dev);
mutex_lock(&alarm_setrtc_mutex);
if (alarm_rtc_dev) { err = -EBUSY; goto err1; }
alarm_platform_dev = platform_device_register_simple("alarm", -1, NULL, 0); if (IS_ERR(alarm_platform_dev)) { err = PTR_ERR(alarm_platform_dev); goto err2; } err = rtc_irq_register(rtc, &alarm_rtc_task); if (err) goto err3; alarm_rtc_dev = rtc; pr_alarm(INIT_STATUS, "using rtc device, %s, for alarms", rtc->name); mutex_unlock(&alarm_setrtc_mutex);
return 0;
err3: platform_device_unregister(alarm_platform_dev); err2: err1: mutex_unlock(&alarm_setrtc_mutex); return err; }
中斷的回調函數為 alarm_triggered_func: static struct rtc_task alarm_rtc_task = { .func = alarm_triggered_func };
static void alarm_triggered_func(void *p){ struct rtc_device *rtc = alarm_rtc_dev; if (!(rtc->irq_data & RTC_AF)) return; pr_alarm(INT, "rtc alarm triggered\n"); wake_lock_timeout(&alarm_rtc_wake_lock, 1 * HZ); } 當硬件 rtc chip 的 alarm 中斷發生時,系統會調用 alarm_triggered_func 函數。 alarm_triggered_func 的功能很簡單,wake_lock_timeout 鎖住 alarm_rtc_wake_lock 1 秒。因 為這時,alarm 會進入 alarm_resume,lock 住 alarm_rtc_wake_lock 以防止 alarm 在此時進入 suspend。
AlarmManager.java,該文件提供的接口主要有: 設置鬧鍾 public void set(int type, long triggerAtTime, PendingIntent operation); 設置周期鬧鍾。 public void setRepeating(int type, long triggerAtTime, long interval,PendingIntent operation); 取消鬧鍾 public void cancel(PendingIntent operation);
www.linuxidc.com
Linux公社(LinuxIDC.com) 是包括Ubuntu,Fedora,SUSE技術,最新IT資訊等Linux專業類網站。
上面 3 個函數分別會呼叫到 AlarmManagerSevice.java 以下三個函數: public void set(int type, long triggerAtTime, PendingIntent operation); public void setRepeating(int type, long triggerAtTime, long interval,PendingIntent operation); public void remove(PendingIntent operation);
AlarmManagerSevice.java 通過 JNI 機制可以呼叫 com_android_server_AlarmManagerService.cpp 透出的幾個接口。
AlarmManagerSevice.java 有關接口的 code 如下: private native int init(); private native void close(int fd); private native void set(int fd, int type, long seconds, long nanoseconds); private native int waitForAlarm(int fd); private native int setKernelTimezone(int fd, int minuteswest);
com_android_server_AlarmManagerService.cpp 有關接口的對應 code 如下: static JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ {"init", "()I", (void*)android_server_AlarmManagerService_init}, {"close", "(I)V", (void*)android_server_AlarmManagerService_close}, {"set", "(IIJJ)V", (void*)android_server_AlarmManagerService_set}, {"waitForAlarm", "(I)I", (void*)android_server_AlarmManagerService_waitForAlarm}, {"setKernelTimezone", "(II)I", (void*)android_server_AlarmManagerService_setKernelTimezone}, };
當 AP 呼叫 AlarmManager.java 的 set 或 setRepeating 函數時,最終會呼叫 com_android_server_AlarmManagerService.cpp 的 static void android_server_AlarmManagerService_set (JNIEnv* env, jobject obj, jint fd, jint type, jlong seconds, jlong nanoseconds) 在此函數中,會執行 ioctl(fd, ANDROID_ALARM_SET(type), &ts); 然后會呼叫到 alarm-dev.c 中 alarm_ioctl 中,接着 alarm-dev.c 會往它的紅黑樹中增加一個 alarm 節點。
在 AlarmManagerService 開始的時候,會啟動一個 AlarmThread。在這個 AlarmThread 中有一 個 while 循環去執行 waitForAlarm 這個動作,這個函數最終通過 JNI 機制呼叫到 com_android_server_AlarmManagerService.cpp 的 static jint android_server_AlarmManagerService_waitForAlarm(JNIEnv* env, jobject obj, jint fd){ #if HAVE_ANDROID_OS int result = 0; do { result = ioctl(fd, ANDROID_ALARM_WAIT); } while (result < 0 && errno == EINTR); if (result < 0) { LOGE("Unable to wait on alarm: %s\n", strerror(errno)); return 0; }
www.linuxidc.com
Linux公社(LinuxIDC.com) 是包括Ubuntu,Fedora,SUSE技術,最新IT資訊等Linux專業類網站。
return result; #endif } 從 code 中可以看到,實際上它是在不斷地執行 ioctl (fd,ANDROID_ALARM_WAIT),上面說 到,當鬧鍾到期時,alarm.c 中的 alarm_timer_triggered 函數會調用 alarm_triggered,這時, AP 層在呼叫 ioctl ( fd,ANDROID_ALARM_WAIT)時,會返回表示等到 alarm 的返回值。
所以當鬧鍾到期時,AlarmThread 的 waitForAlarm 會返回一個值。接着通過執行 triggerAlarmsLocked,把幾種類型的鬧鍾列表中符合要求的 alarm 添加到 triggerList 中,然后 用 alarm.operation.send 發送消息,調起小鬧鍾程序。
AlarmThread 的 code 如下: private class AlarmThread extends Thread { public AlarmThread() { super("AlarmManager"); }
public void run() { while (true) { int result = waitForAlarm(mDescriptor); ArrayList<Alarm> triggerList = new ArrayList<Alarm>(); if ((result & TIME_CHANGED_MASK) != 0) { remove(mTimeTickSender); mClockReceiver.scheduleTimeTickEvent(); Intent intent = new Intent(Intent.ACTION_TIME_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); mContext.sendBroadcast(intent); } synchronized (mLock) { final long nowRTC = System.currentTimeMillis(); final long nowELAPSED = SystemClock.elapsedRealtime(); if (localLOGV) Slog.v( TAG, "Checking for alarms... rtc=" + nowRTC + ", elapsed=" + nowELAPSED);
if ((result & RTC_WAKEUP_MASK) != 0) triggerAlarmsLocked(mRtcWakeupAlarms, triggerList, nowRTC); if ((result & RTC_MASK) != 0) triggerAlarmsLocked(mRtcAlarms, triggerList, nowRTC); if ((result & ELAPSED_REALTIME_WAKEUP_MASK) != 0) triggerAlarmsLocked(mElapsedRealtimeWakeupAlarms, triggerList, nowELAPSED); if ((result & ELAPSED_REALTIME_MASK) != 0) triggerAlarmsLocked(mElapsedRealtimeAlarms, triggerList, nowELAPSED); // now trigger the alarms Iterator<Alarm> it = triggerList.iterator(); while (it.hasNext()) { Alarm alarm = it.next(); try { if (localLOGV) Slog.v(TAG, "sending alarm " + alarm); alarm.operation.send(mContext, 0,
www.linuxidc.com
Linux公社(LinuxIDC.com) 是包括Ubuntu,Fedora,SUSE技術,最新IT資訊等Linux專業類網站。
mBackgroundIntent.putExtra( Intent.EXTRA_ALARM_COUNT, alarm.count), mResultReceiver, mHandler); // we have an active broadcast so stay awake. if (mBroadcastRefCount == 0) { mWakeLock.acquire(); } mBroadcastRefCount++; BroadcastStats bs = getStatsLocked(alarm.operation); if (bs.nesting == 0) { bs.startTime = nowELAPSED; } else { bs.nesting++; } if (alarm.type == AlarmManager.ELAPSED_REALTIME_WAKEUP || alarm.type == AlarmManager.RTC_WAKEUP) { bs.numWakeup++; ActivityManagerNative.noteWakeupAlarm( alarm.operation); } } catch (PendingIntent.CanceledException e) { if (alarm.repeatInterval > 0) { remove(alarm.operation); } } catch (RuntimeException e) { Slog.w(TAG, "Failure sending alarm.", e); } } } } } }
11.4 接口
Android 中,Alarm 的操作通過 AlarmManager 來處理,AlarmManager 系統服務的具體實現 在:frameworks/base/services/java/com/android/server/AlarmManagerServic.java 文件中。應用 程序中可以通過 getSystemService 獲得其系統服務,如下所示: AlarmManager alarms = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
為了創建一個新的 Alarm,使用 set 方法並指定一個 Alarm 類型、觸發時間和在 Alarm 觸發 時要調用的 Intent。如果你設定的 Alarm 發生在過去,那么它將立即觸發。
這里有 4 種 Alarm 類型。你的選擇將決定你在 set 方法中傳遞的時間值代表什么,是特定的 時間或者是時間流逝:
RTC_WAKEUP:在指定的時刻(設置 Alarm 的時候),喚醒設備來觸發 Intent。 RTC:在一個顯式的時間觸發 Intent,但不喚醒設備。 ELAPSED_REALTIME:從設備啟動后,如果流逝的時間達到總時間,那么觸發 Intent,但 不喚醒設備。流逝的時間包括設備睡眠的任何時間。注意一點的是,時間流逝的計算點 是自從它最后一次啟動算起。 ELAPSED_REALTIME_WAKEUP:從設備啟動后,達到流逝的總時間后,如果需要將喚醒 設備並觸發 Intent。
www.linuxidc.com
Linux公社(LinuxIDC.com) 是包括Ubuntu,Fedora,SUSE技術,最新IT資訊等Linux專業類網站。
這 4 種 Alarm 類型詳情請參考 frameworks/base/core/java/android/app/AlarmManager.java。
11.5 實例
最后,請看一個 Alarm 的實例: 1、 建立一個 AlarmReceiver 繼承入 BroadcastReceiver,並在 AndroidManifest.xml 聲明 Public static class AlarmReceiver extends BroadcastReceiver { @Override Public void onReceive (Context context, Intent intent) { Toast.makeText(context, “時間到”, Toast.LENGTH_LONG).show(); } }
2、 建立 Intent 和 PendingIntent,來調用目標組件。 Intent it = new Intent(this, AlarmReceiver.class); PendingIntent pi = PendingIntent.getBroadcast(this, 0, intent, 0);
3、 設置鬧鍾 獲取鬧鍾管理的實例: AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
設置單次鬧鍾: am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (5*1000), pi);
設置周期鬧鍾: am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (10*1000), (24*60*60*1000), pi);