Android Alarm機制分析

從應用層到內核層,簡單分析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)是一個專用於設備輸入輸出操作的系統調用,該調用傳入一個跟設備有關的請求碼,系統調用的功能完全取決於請求碼。

參考ioctl介紹博文

接下來我們需要查看內核層的處理方式,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在設備處於睡眠模式時仍保持活躍,它可以設置來喚醒設備。

歡迎關注我的公衆號,持續分析優質技術文章
歡迎關注我的公衆號

發佈了107 篇原創文章 · 獲贊 21 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章