從應用層到內核層,簡單分析Android Alarm的工作流程。基於Android 4.4和Kernel 2.6.39。
應用層
/packages/apps/DeskClock/目錄下爲Android的系統鬧鐘APP,可參考其鬧鐘實現。
在/packages/apps/DeskClock/src/com/android/deskclock/AlarmClockFragment.java:
mAddAlarmButton = (ImageButton) v.findViewById(R.id.alarm_add_alarm);
mAddAlarmButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
hideUndoBar(true, null);
startCreatingAlarm();
}
});
給按鈕綁定監聽事件,調用startCreatingAlarm()函數
private void startCreatingAlarm() {
// Set the "selected" alarm as null, and we'll create the new one when the timepicker
// comes back.
mSelectedAlarm = null;
AlarmUtils.showTimeEditDialog(getChildFragmentManager(),
null, AlarmClockFragment.this, DateFormat.is24HourFormat(getActivity()));
}
/packages/apps/DeskClock/src/com/android/deskclock/AlarmUtils.java:
public static void showTimeEditDialog(FragmentManager manager, final Alarm alarm,TimePickerDialog.OnTimeSetListener listener, boolean is24HourMode) {
...
TimePickerDialog dialog = TimePickerDialog.newInstance(listener,
hour, minutes, is24HourMode);
dialog.setThemeDark(true);
// Make sure the dialog isn't already added.
manager.executePendingTransactions();
final FragmentTransaction ft = manager.beginTransaction();
final Fragment prev = manager.findFragmentByTag(FRAG_TAG_TIME_PICKER);
if (prev != null) {
ft.remove(prev);
}
ft.commit();
if (dialog != null && !dialog.isAdded()) {
dialog.show(manager, FRAG_TAG_TIME_PICKER);
}
}
打開了一個時間選擇的Dialog,dialog事件監聽器是由AlarmClockFragment傳入的TimePickerDialog.OnTimeSetListener,回調函數爲onTimeSet。
再回到AlarmClockFragment.java中:
/**
* AlarmClock application.
*/
public class AlarmClockFragment extends DeskClockFragment implements
LoaderManager.LoaderCallbacks<Cursor>,
TimePickerDialog.OnTimeSetListener,
View.OnTouchListener
{
...
// Callback used by TimePickerDialog
@Override
public void onTimeSet(RadialPickerLayout view, int hourOfDay, int minute) {
if (mSelectedAlarm == null) {
// If mSelectedAlarm is null then we're creating a new alarm.
Alarm a = new Alarm();
a.alert = RingtoneManager.getActualDefaultRingtoneUri(getActivity(),
RingtoneManager.TYPE_ALARM);
if (a.alert == null) {
a.alert = Uri.parse("content://settings/system/alarm_alert");
}
a.hour = hourOfDay;
a.minutes = minute;
a.enabled = true;
asyncAddAlarm(a);
} else {
mSelectedAlarm.hour = hourOfDay;
mSelectedAlarm.minutes = minute;
mSelectedAlarm.enabled = true;
mScrollToAlarmId = mSelectedAlarm.id;
asyncUpdateAlarm(mSelectedAlarm, true);
mSelectedAlarm = null;
}
}
...
}
在onTimeSet函數中 asyncAddAlarm(a);
private void asyncAddAlarm(final Alarm alarm) {
final Context context = AlarmClockFragment.this.getActivity().getApplicationContext();
final AsyncTask<Void, Void, AlarmInstance> updateTask =
new AsyncTask<Void, Void, AlarmInstance>() {
@Override
public synchronized void onPreExecute() {
...
}
@Override
protected AlarmInstance doInBackground(Void... parameters) {
if (context != null && alarm != null) {
ContentResolver cr = context.getContentResolver();
// Add alarm to db
Alarm newAlarm = Alarm.addAlarm(cr, alarm);
mScrollToAlarmId = newAlarm.id;
// Create and add instance to db
if (newAlarm.enabled) {
return setupAlarmInstance(context, newAlarm);
}
}
return null;
}
@Override
protected void onPostExecute(AlarmInstance instance) {
...
}
};
updateTask.execute();
}
asyncAddAlarm函數中是一個AsyncTask後臺任務,主要關係doInBackground函數。
通過Content Provider在數據庫添加了一個數據。
然後調用了
private static AlarmInstance setupAlarmInstance(Context context, Alarm alarm) {
ContentResolver cr = context.getContentResolver();
AlarmInstance newInstance = alarm.createInstanceAfter(Calendar.getInstance());
newInstance = AlarmInstance.addInstance(cr, newInstance);
// Register instance to state manager
AlarmStateManager.registerInstance(context, newInstance, true);
return newInstance;
}
在/packages/apps/DeskClock/src/com/android/deskclock/provider/Alarm.java中:
public static Alarm addAlarm(ContentResolver contentResolver, Alarm alarm) {
ContentValues values = createContentValues(alarm);
Uri uri = contentResolver.insert(CONTENT_URI, values);
alarm.id = getId(uri);
return alarm;
}
在setupAlarmInstance中,在AlarmStateManager註冊了一個實例:
// Register instance to state manager
AlarmStateManager.registerInstance(context, newInstance, true);
然後我們來查看:
/packages/apps/DeskClock/src/com/android/deskclock/alarms/AlarmStateManager.java
/**
* This registers the AlarmInstance to the state manager. This will look at the instance
* and choose the most appropriate state to put it in. This is primarily used by new
* alarms, but it can also be called when the system time changes.
*
* Most state changes are handled by the states themselves, but during major time changes we
* have to correct the alarm instance state. This means we have to handle special cases as
* describe below:
*
* <ul>
* <li>Make sure all dismissed alarms are never re-activated</li>
* <li>Make sure firing alarms stayed fired unless they should be auto-silenced</li>
* <li>Missed instance that have parents should be re-enabled if we went back in time</li>
* <li>If alarm was SNOOZED, then show the notification but don't update time</li>
* <li>If low priority notification was hidden, then make sure it stays hidden</li>
* </ul>
*
* If none of these special case are found, then we just check the time and see what is the
* proper state for the instance.
*
* @param context application context
* @param instance to register
*/
public static void registerInstance(Context context, AlarmInstance instance,
boolean updateNextAlarm)
{
...
// Fix states that are time sensitive
if (currentTime.after(missedTTL)) {
// Alarm is so old, just dismiss it
setDismissState(context, instance);
} else if (currentTime.after(alarmTime)) {
setMissedState(context, instance);
} else if (instance.mAlarmState == AlarmInstance.SNOOZE_STATE) {
// We only want to display snooze notification and not update the time,
// so handle showing the notification directly
AlarmNotifications.showSnoozeNotification(context, instance);
scheduleInstanceStateChange(context, instance.getAlarmTime(),
instance, AlarmInstance.FIRED_STATE);
}
...
}
在scheduleInstanceStateChange函數中:
private static void scheduleInstanceStateChange(Context context, Calendar time,
AlarmInstance instance, int newState) {
long timeInMillis = time.getTimeInMillis();
Log.v("Scheduling state change " + newState + " to instance " + instance.mId +
" at " + AlarmUtils.getFormattedTime(context, time) + " (" + timeInMillis + ")");
Intent stateChangeIntent = createStateChangeIntent(context, ALARM_MANAGER_TAG, instance,
newState);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, instance.hashCode(),
stateChangeIntent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if (Utils.isKitKatOrLater()) {
am.setExact(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
} else {
am.set(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
}
}
可以看出AlarmStateManager是系統鬧鐘APP用來管理鬧鐘狀態的類。
我們可以看到AlarmManager的setExact和set函數。
if (Utils.isKitKatOrLater()) {
am.setExact(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
} else {
am.set(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
}
這裏是RTC_WAKEUP, 這就保證了即使系統睡眠了,都能喚醒,鬧鐘工作。
綜上,可以看出系統鬧鐘APPAlarmManager和開發應用層APP使用的是同樣的AlarmManager。
接下來具體分析AlarmManager的實現即可。
框架層
Java層
在/frameworks/base/core/java/android/app/AlarmManager.java中:
public class AlarmManager
{
private static final String TAG = "AlarmManager";
/**
* Alarm time in {@link System#currentTimeMillis System.currentTimeMillis()}
* (wall clock time in UTC), which will wake up the device when
* it goes off.
*/
public static final int RTC_WAKEUP = 0;
/**
* Alarm time in {@link System#currentTimeMillis System.currentTimeMillis()}
* (wall clock time in UTC). This alarm does not wake the
* device up; if it goes off while the device is asleep, it will not be
* delivered until the next time the device wakes up.
*/
public static final int RTC = 1;
/**
* Alarm time in {@link android.os.SystemClock#elapsedRealtime
* SystemClock.elapsedRealtime()} (time since boot, including sleep),
* which will wake up the device when it goes off.
*/
public static final int ELAPSED_REALTIME_WAKEUP = 2;
/**
* Alarm time in {@link android.os.SystemClock#elapsedRealtime
* SystemClock.elapsedRealtime()} (time since boot, including sleep).
* This alarm does not wake the device up; if it goes off while the device
* is asleep, it will not be delivered until the next time the device
* wakes up.
*/
public static final int ELAPSED_REALTIME = 3;
...
/**
* TBW: new 'exact' alarm that must be delivered as nearly as possible
* to the precise time specified.
*/
public void setExact(int type, long triggerAtMillis, PendingIntent operation) {
setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, operation, null);
}
/** @hide */
public void set(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
PendingIntent operation, WorkSource workSource) {
setImpl(type, triggerAtMillis, windowMillis, intervalMillis, operation, workSource);
}
private void setImpl(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
PendingIntent operation, WorkSource workSource) {
if (triggerAtMillis < 0) {
/* NOTYET
if (mAlwaysExact) {
// Fatal error for KLP+ apps to use negative trigger times
throw new IllegalArgumentException("Invalid alarm trigger time "
+ triggerAtMillis);
}
*/
triggerAtMillis = 0;
}
try {
mService.set(type, triggerAtMillis, windowMillis, intervalMillis, operation,
workSource);
} catch (RemoteException ex) {
}
}
...
}
主要分爲4中模式:
-
RTC_WAKEUP:UTC時間,會在設備關閉時喚醒它
-
RTC:UTC時間,此警報不會喚醒設備;如果它在設備處於休眠狀態時關閉,則在下次設備喚醒之前不會發送。
-
ELAPSED_REALTIME_WAKEUP:開機後的時間,包括睡眠,會在設備關閉時喚醒它
-
ELAPSED_REALTIME:開機後的時間,包括睡眠;如果它在設備處於休眠狀態時關閉,則在下次設備喚醒之前不會發送。
setExact和set均調用了setImpl函數,setImpl函數中核心是:
mService.set(type, triggerAtMillis, windowMillis, intervalMillis, operation,
workSource);
查看mService的定義:
private final IAlarmManager mService;
在/frameworks/base/core/java/android/app/IAlarmManager.aidl:
/**
* System private API for talking with the alarm manager service.
*
* {@hide}
*/
interface IAlarmManager {
/** windowLength == 0 means exact; windowLength < 0 means the let the OS decide */
void set(int type, long triggerAtTime, long windowLength,
long interval, in PendingIntent operation, in WorkSource workSource);
void setTime(long millis);
void setTimeZone(String zone);
void remove(in PendingIntent operation);
}
可以看到是一個aidl文件,要用到進程間通信技術。
查找具體實現,在/frameworks/base/services/java/com/android/server/AlarmManagerService.java:
class AlarmManagerService extends IAlarmManager.Stub {
...
@Override
public void set(int type, long triggerAtTime, long windowLength, long interval,
PendingIntent operation, WorkSource workSource) {
if (workSource != null) {
mContext.enforceCallingPermission(
android.Manifest.permission.UPDATE_DEVICE_STATS,
"AlarmManager.set");
}
set(type, triggerAtTime, windowLength, interval, operation, false, workSource);
}
public void set(int type, long triggerAtTime, long windowLength, long interval,
PendingIntent operation, boolean isStandalone, WorkSource workSource) {
...
final long nowElapsed = SystemClock.elapsedRealtime();
final long triggerElapsed = convertToElapsed(triggerAtTime, type);
final long maxElapsed;
if (windowLength == AlarmManager.WINDOW_EXACT) {
maxElapsed = triggerElapsed;
} else if (windowLength < 0) {
maxElapsed = maxTriggerTime(nowElapsed, triggerElapsed, interval);
} else {
maxElapsed = triggerElapsed + windowLength;
}
synchronized (mLock) {
if (DEBUG_BATCH) {
Slog.v(TAG, "set(" + operation + ") : type=" + type
+ " triggerAtTime=" + triggerAtTime + " win=" + windowLength
+ " tElapsed=" + triggerElapsed + " maxElapsed=" + maxElapsed
+ " interval=" + interval + " standalone=" + isStandalone);
}
setImplLocked(type, triggerAtTime, triggerElapsed, maxElapsed,
interval, operation, isStandalone, true, workSource);
}
}
private void setImplLocked(int type, long when, long whenElapsed, long maxWhen, long interval,PendingIntent operation, boolean isStandalone, boolean doValidate,
WorkSource workSource) {
...
if (reschedule) {
rescheduleKernelAlarmsLocked();
}
...
}
private void rescheduleKernelAlarmsLocked() {
// Schedule the next upcoming wakeup alarm. If there is a deliverable batch
// prior to that which contains no wakeups, we schedule that as well.
if (mAlarmBatches.size() > 0) {
final Batch firstWakeup = findFirstWakeupBatchLocked();
final Batch firstBatch = mAlarmBatches.get(0);
if (firstWakeup != null && mNextWakeup != firstWakeup.start) {
mNextWakeup = firstWakeup.start;
setLocked(ELAPSED_REALTIME_WAKEUP, firstWakeup.start);
}
if (firstBatch != firstWakeup && mNextNonWakeup != firstBatch.start) {
mNextNonWakeup = firstBatch.start;
setLocked(ELAPSED_REALTIME, firstBatch.start);
}
}
}
private void setLocked(int type, long when)
{
if (mDescriptor != -1)
{
// The kernel never triggers alarms with negative wakeup times
// so we ensure they are positive.
long alarmSeconds, alarmNanoseconds;
if (when < 0) {
alarmSeconds = 0;
alarmNanoseconds = 0;
} else {
alarmSeconds = when / 1000;
alarmNanoseconds = (when % 1000) * 1000 * 1000;
}
set(mDescriptor, type, alarmSeconds, alarmNanoseconds);
}
else
{
Message msg = Message.obtain();
msg.what = ALARM_EVENT;
mHandler.removeMessages(ALARM_EVENT);
mHandler.sendMessageAtTime(msg, when);
}
}
...
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);
...
}
通過上述代碼,AlarmManagerService的set函數最終調用了native的set函數進行設置。
同時,注意到AlarmManagerService中含有一個AlarmThread內部類:
private class AlarmThread extends Thread
{
public AlarmThread()
{
super("AlarmManager");
}
public void run()
{
ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
while (true)
{
int result = waitForAlarm(mDescriptor);
triggerList.clear();
if ((result & TIME_CHANGED_MASK) != 0) {
if (DEBUG_BATCH) {
Slog.v(TAG, "Time changed notification from kernel; rebatching");
}
remove(mTimeTickSender);
rebatchAllAlarms();
mClockReceiver.scheduleTimeTickEvent();
Intent intent = new Intent(Intent.ACTION_TIME_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
| Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
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 (WAKEUP_STATS) {
if ((result & IS_WAKEUP_MASK) != 0) {
long newEarliest = nowRTC - RECENT_WAKEUP_PERIOD;
int n = 0;
for (WakeupEvent event : mRecentWakeups) {
if (event.when > newEarliest) break;
n++; // number of now-stale entries at the list head
}
for (int i = 0; i < n; i++) {
mRecentWakeups.remove();
}
recordWakeupAlarms(mAlarmBatches, nowELAPSED, nowRTC);
}
}
triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);
rescheduleKernelAlarmsLocked();
// now deliver the alarm intents
for (int i=0; i<triggerList.size(); i++) {
Alarm alarm = triggerList.get(i);
try {
if (localLOGV) Slog.v(TAG, "sending alarm " + alarm);
alarm.operation.send(mContext, 0,
mBackgroundIntent.putExtra(
Intent.EXTRA_ALARM_COUNT, alarm.count),
mResultReceiver, mHandler);
...
} catch (PendingIntent.CanceledException e) {
if (alarm.repeatInterval > 0) {
// This IntentSender is no longer valid, but this
// is a repeating alarm, so toss the hoser.
remove(alarm.operation);
}
} catch (RuntimeException e) {
Slog.w(TAG, "Failure sending alarm.", e);
}
}
}
}
}
}
AlarmManagerService 中 AlarmThread 會一直調用waitForAlarm輪詢,會返回一個值 。如果打開失敗就直接返回,成功就會做一些動作,通過執行triggerAlarmsLocked,把幾種類型的鬧鐘列表中符合要求的 alarm 添加到 triggerList 中,然後用 alarm.operation.send 發送消息,調起小鬧鐘程序
Native層
/frameworks/base/services/jni/com_android_server_AlarmManagerService.cpp
namespace android {
static jint android_server_AlarmManagerService_setKernelTimezone(JNIEnv* env, jobject obj, jint fd, jint minswest)
{
struct timezone tz;
tz.tz_minuteswest = minswest;
tz.tz_dsttime = 0;
int result = settimeofday(NULL, &tz);
if (result < 0) {
ALOGE("Unable to set kernel timezone to %d: %s\n", minswest, strerror(errno));
return -1;
} else {
ALOGD("Kernel timezone updated to %d minutes west of GMT\n", minswest);
}
return 0;
}
static jint android_server_AlarmManagerService_init(JNIEnv* env, jobject obj)
{
return open("/dev/alarm", O_RDWR);
}
static void android_server_AlarmManagerService_close(JNIEnv* env, jobject obj, jint fd)
{
close(fd);
}
static void android_server_AlarmManagerService_set(JNIEnv* env, jobject obj, jint fd, jint type, jlong seconds, jlong nanoseconds)
{
struct timespec ts;
ts.tv_sec = seconds;
ts.tv_nsec = nanoseconds;
int result = ioctl(fd, ANDROID_ALARM_SET(type), &ts);
if (result < 0)
{
ALOGE("Unable to set alarm to %lld.%09lld: %s\n", seconds, nanoseconds, strerror(errno));
}
}
static jint android_server_AlarmManagerService_waitForAlarm(JNIEnv* env, jobject obj, jint fd)
{
int result = 0;
do
{
result = ioctl(fd, ANDROID_ALARM_WAIT);
} while (result < 0 && errno == EINTR);
if (result < 0)
{
ALOGE("Unable to wait on alarm: %s\n", strerror(errno));
return 0;
}
return result;
}
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},
};
int register_android_server_AlarmManagerService(JNIEnv* env)
{
return jniRegisterNativeMethods(env, "com/android/server/AlarmManagerService",
sMethods, NELEM(sMethods));
}
} /* namespace android */
android_server_AlarmManagerService_set中關鍵語句:
int result = ioctl(fd, ANDROID_ALARM_SET(type), &ts);
在計算機中,ioctl(input/output control)是一個專用於設備輸入輸出操作的系統調用,該調用傳入一個跟設備有關的請求碼,系統調用的功能完全取決於請求碼。
接下來我們需要查看內核層的處理方式,ANDROID_ALARM_SET爲關鍵字查找。
內核層
在/drivers/rtc/alarm-dev.c:
static long alarm_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
...
case ANDROID_ALARM_SET(0):
if (copy_from_user(&new_alarm_time, (void __user *)arg,
sizeof(new_alarm_time))) {
rv = -EFAULT;
goto err1;
}
from_old_alarm_set:
spin_lock_irqsave(&alarm_slock, flags);
pr_alarm(IO, "alarm %d set %ld.%09ld\n", alarm_type,
new_alarm_time.tv_sec, new_alarm_time.tv_nsec);
alarm_enabled |= alarm_type_mask;
alarm_start_range(&alarms[alarm_type],
timespec_to_ktime(new_alarm_time),
timespec_to_ktime(new_alarm_time));
spin_unlock_irqrestore(&alarm_slock, flags);
if (ANDROID_ALARM_BASE_CMD(cmd) != ANDROID_ALARM_SET_AND_WAIT(0)
&& cmd != ANDROID_ALARM_SET_AND_WAIT_OLD)
break;
case ANDROID_ALARM_WAIT:
spin_lock_irqsave(&alarm_slock, flags);
pr_alarm(IO, "alarm wait\n");
if (!alarm_pending && wait_pending) {
wake_unlock(&alarm_wake_lock);
wait_pending = 0;
}
spin_unlock_irqrestore(&alarm_slock, flags);
rv = wait_event_interruptible(alarm_wait_queue, alarm_pending);
if (rv)
goto err1;
spin_lock_irqsave(&alarm_slock, flags);
rv = alarm_pending;
wait_pending = 1;
alarm_pending = 0;
spin_unlock_irqrestore(&alarm_slock, flags);
break;
...
case ANDROID_ALARM_SET_RTC:
if (copy_from_user(&new_rtc_time, (void __user *)arg,
sizeof(new_rtc_time))) {
rv = -EFAULT;
goto err1;
}
rv = alarm_set_rtc(new_rtc_time);
spin_lock_irqsave(&alarm_slock, flags);
alarm_pending |= ANDROID_ALARM_TIME_CHANGE_MASK;
wake_up(&alarm_wait_queue);
spin_unlock_irqrestore(&alarm_slock, flags);
if (rv < 0)
goto err1;
break;
...
default:
rv = -EINVAL;
goto err1;
}
err1:
return rv;
}
調用了alarm_start_range 設置鬧鐘, alarm_set_rtc設置RTC
這兩個函數在 android_alarm.h 聲明,在 alarm.c 裏實現
/include/linux/android_alarm.h:
#ifndef _LINUX_ANDROID_ALARM_H
#define _LINUX_ANDROID_ALARM_H
#include <linux/ioctl.h>
#include <linux/time.h>
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 */
};
#ifdef __KERNEL__
#include <linux/ktime.h>
#include <linux/rbtree.h>
/*
* The alarm interface is similar to the hrtimer interface but adds support
* for wakeup from suspend. It also adds an elapsed realtime clock that can
* be used for periodic timers that need to keep runing while the system is
* suspended and not be disrupted when the wall time is set.
*/
/**
* struct alarm - the basic alarm structure
* @node: red black tree node for time ordered insertion
* @type: alarm type. rtc/elapsed-realtime/systemtime, wakeup/non-wakeup.
* @softexpires: the absolute earliest expiry time of the alarm.
* @expires: the absolute expiry time.
* @function: alarm expiry callback function
*
* The alarm structure must be initialized by alarm_init()
*
*/
struct alarm {
struct rb_node node;
enum android_alarm_type type;
ktime_t softexpires;
ktime_t expires;
void (*function)(struct alarm *);
};
void alarm_init(struct alarm *alarm,
enum android_alarm_type type, void (*function)(struct alarm *));
void alarm_start_range(struct alarm *alarm, ktime_t start, ktime_t end);
int alarm_try_to_cancel(struct alarm *alarm);
int alarm_cancel(struct alarm *alarm);
ktime_t alarm_get_elapsed_realtime(void);
/* set rtc while preserving elapsed realtime */
int alarm_set_rtc(const struct timespec ts);
#endif
enum android_alarm_return_flags {
ANDROID_ALARM_RTC_WAKEUP_MASK = 1U << ANDROID_ALARM_RTC_WAKEUP,
ANDROID_ALARM_RTC_MASK = 1U << ANDROID_ALARM_RTC,
ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP_MASK =
1U << ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP,
ANDROID_ALARM_ELAPSED_REALTIME_MASK =
1U << ANDROID_ALARM_ELAPSED_REALTIME,
ANDROID_ALARM_SYSTEMTIME_MASK = 1U << ANDROID_ALARM_SYSTEMTIME,
ANDROID_ALARM_TIME_CHANGE_MASK = 1U << 16
};
/* Disable alarm */
#define ANDROID_ALARM_CLEAR(type) _IO('a', 0 | ((type) << 4))
/* Ack last alarm and wait for next */
#define ANDROID_ALARM_WAIT _IO('a', 1)
#define ALARM_IOW(c, type, size) _IOW('a', (c) | ((type) << 4), size)
/* Set alarm */
#define ANDROID_ALARM_SET(type) ALARM_IOW(2, type, struct timespec)
#define ANDROID_ALARM_SET_AND_WAIT(type) ALARM_IOW(3, type, struct timespec)
#define ANDROID_ALARM_GET_TIME(type) ALARM_IOW(4, type, struct timespec)
#define ANDROID_ALARM_SET_RTC _IOW('a', 5, struct timespec)
#define ANDROID_ALARM_BASE_CMD(cmd) (cmd & ~(_IOC(0, 0, 0xf0, 0)))
#define ANDROID_ALARM_IOCTL_TO_TYPE(cmd) (_IOC_NR(cmd) >> 4)
#endif
/drivers/rtc/alarm.c:
/**
* alarm_start_range - (re)start an alarm
* @alarm: the alarm to be added
* @start: earliest expiry time
* @end: expiry time
*/
void alarm_start_range(struct alarm *alarm, ktime_t start, ktime_t end)
{
unsigned long flags;
spin_lock_irqsave(&alarm_slock, flags);
alarm->softexpires = start;
alarm->expires = end;
alarm_enqueue_locked(alarm);
spin_unlock_irqrestore(&alarm_slock, flags);
}
...
/**
* alarm_set_rtc - set the kernel and rtc walltime
* @new_time: timespec value containing the new time
*/
int alarm_set_rtc(struct timespec new_time)
{
...
ret = rtc_set_time(alarm_rtc_dev, &rtc_new_rtc_time);
...
return ret;
}
static int alarm_suspend(struct platform_device *pdev, pm_message_t state)
static int alarm_resume(struct platform_device *pdev)
/drivers/rtc/interface.c:
int rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm)
{
int err;
err = rtc_valid_tm(tm);
if (err != 0)
return err;
err = mutex_lock_interruptible(&rtc->ops_lock);
if (err)
return err;
if (!rtc->ops)
err = -ENODEV;
else if (rtc->ops->set_time)
err = rtc->ops->set_time(rtc->dev.parent, tm);
else if (rtc->ops->set_mmss) {
unsigned long secs;
err = rtc_tm_to_time(tm, &secs);
if (err == 0)
err = rtc->ops->set_mmss(rtc->dev.parent, secs);
} else
err = -EINVAL;
mutex_unlock(&rtc->ops_lock);
return err;
}
EXPORT_SYMBOL_GPL(rtc_set_time);
在 rtc->ops->set_time(rtc->dev.parent, tm)中set_time 就看到具體的是那個RTC芯片,這裏不再繼續往下分析。
-
alarm.c 文件實現的是所有 alarm 設備的通用性操作,它創建了一個設備 class
-
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 鬧鐘功能的基礎上虛擬了多個軟件鬧鐘。
alarm.c 裏面實現了 alarm_suspend alarm_resume 函數。如果不需要喚醒系統,設置鬧鐘並不會往rtc 芯片的寄存器上寫數據,通過上層寫到設備文件/dev/alarm 裏面,AlarmThread 會不停的去輪尋下一個時間有沒有鬧鐘,直接從設備文件 /dev/alarm 裏面讀取。
如果需要喚醒系統,alarm 的alarm_suspend就會寫到下層的rtc芯片的寄存器上去, 然後即使系統suspend之後,鬧鐘通過rtc 也能喚醒系統。
綜上:Alarm 鬧鐘是 android 系統中在標準 RTC 驅動上開發的一個新的驅動,提供了一個定時器,用於把設備從睡眠狀態喚醒,當然因爲它是依賴 RTC 驅動的,所以它同時還可以爲系統提供一個掉電下還能運行的實時時鐘。
當系統斷電時,主板上的 rtc 芯片將繼續維持系統的時間,這樣保證再次開機後系統的時間不會錯誤。當系統開始時,內核從 RTC 中讀取時間來初始化系統時間,關機時便又將系統時間寫回到 rtc 中,關機階段將有主板上另外的電池來供應 rtc 計時。Android 中的 Alarm在設備處於睡眠模式時仍保持活躍,它可以設置來喚醒設備。
歡迎關注我的公衆號,持續分析優質技術文章