很久之前寫的一個流程文檔,從上層介面一直調用到核心的過程,最近同事跟我要,我看了下又在整理了下,純屬個人分析(不過都運行驗證過),不對的請大牛指出
Alarm 調用流程,alarm的流程實現了從上層應用一直到下面driver的調用流程,下面簡單闡述:
涉及代碼;
./packages/apps/DeskClock/src/com/android/deskclock/Alarms.java
./frameworks/base/core/java/android/app/AlarmManager.java
./frameworks/base/services/java/com/android/server/AlarmManagerService.java
./frameworks/base/services/jni/com_android_server_AlarmManagerService.cpp
./kernel/kernel/drivers/rtc/alarm-dev.c
./kernel/kernel/include/linux/android_alarm.h
./kernel/kernel/drivers/rtc/alarm.c
./kernel/kernel/drivers/rtc/interface.c
./kernel/kernel/drivers/rtc/rtc-pcf8563.c
./packages/apps/DeskClock/src/com/android/deskclock/AlarmReceiver.java
./kernel/arch/arm/configs/mmp2_android_defconfig
./kernel/kernel/kernel/.config
點擊Clock 應用程式,然後設定新鬧鐘,會調到 Alarms.java 裡面的
public static long setAlarm(Context context, Alarm alarm) {
....
setNextAlert(context);
....
}
然後這裡面也會調用到
public static void setNextAlert(final Context context) {
if (!enableSnoozeAlert(context)) {
Alarm alarm = calculateNextAlert(context); //new 一個新的alarm
if (alarm != null) {
enableAlert(context, alarm, alarm.time);
} else {
disableAlert(context);
}
}
}
然後繼續調用到
private static void enableAlert(Context context, final Alarm alarm, final long atTimeInMillis) {
.......
am.set(AlarmManager.RTC_WAKEUP, atTimeInMillis, sender); //這裡是RTC_WAKEUP, 這就保證了即使系統睡眠了,都能喚醒,鬧鐘工作(android平台關機鬧鐘好像不行)
.....
}
然後就調用到了AlarmManager.java 裡面方法
public void set(int type, long triggerAtTime, PendingIntent operation) {
try {
mService.set(type, triggerAtTime, operation);
} catch (RemoteException ex) {
}
}
然後就調用到了AlarmManagerService.java 裡面方法
public void set(int type, long triggerAtTime, PendingIntent operation) {
setRepeating(type, triggerAtTime, 0, operation);
}
然後繼續調用
public void setRepeating(int type, long triggerAtTime, long interval,
PendingIntent operation) {
.....
synchronized (mLock) {
Alarm alarm = new Alarm();
alarm.type = type;
alarm.when = triggerAtTime;
alarm.repeatInterval = interval;
alarm.operation = operation;
// Remove this alarm if already scheduled.
removeLocked(operation);
if (localLOGV) Slog.v(TAG, "set: " + alarm);
int index = addAlarmLocked(alarm);
if (index == 0) {
setLocked(alarm);
}
}
}
然後就調用到
private void setLocked(Alarm alarm)
{
......
set(mDescriptor, alarm.type, alarmSeconds, alarmNanoseconds); //mDescriptor 這裡的檔案是 /dev/alarm
.....
}
這裡就調用到jni了
private native void set(int fd, int type, long seconds, long nanoseconds);
這就調用到了com_android_server_AlarmManagerService.cpp 裡面
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},
};
set 對應的是android_server_AlarmManagerService_set, 具體是
static void android_server_AlarmManagerService_set(JNIEnv* env, jobject obj, jint fd, jint type, jlong seconds, jlong nanoseconds)
{
#if HAVE_ANDROID_OS
struct timespec ts;
ts.tv_sec = seconds;
ts.tv_nsec = nanoseconds;
int result = ioctl(fd, ANDROID_ALARM_SET(type), &ts);
if (result < 0)
{
LOGE("Unable to set alarm to %lld.%09lld: %s\n", seconds, nanoseconds, strerror(errno));
}
#endif
}
然後ioctl 就調用到了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;
/* fall though */
....
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;
....
}
然後這邊就調用到了alarm_start_range 設定鬧鐘, alarm_set_rtc 設定RTC
這兩個函數在 android_alarm.h 聲明,在 alarm.c 裡實現
這是android_alarm.h 裡面的聲明
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);
下面看alarm.c 裡面實現:
int alarm_set_rtc(struct timespec new_time)
{
....
ret = rtc_set_time(alarm_rtc_dev, &rtc_new_rtc_time);
....
}
alarm.c 裡面實現了 alarm_suspend alarm_resume 函數
就是如果系統沒有suspend的時候,設定鬧鐘並不會往rtc 晶片的寄存器上寫資料,因為不需要喚醒系統,所以鬧鐘資料時間什麼的就通過上層寫到裝置檔案/dev/alarm
裡面就可以了,AlarmThread 會不停的去輪尋下一個時間有沒有鬧鐘,直接從裝置檔案 /dev/alarm 裡面讀取
第二種,系統要是進入susupend的話,alarm 的alarm_suspend 就會寫到下層的rtc晶片的寄存器上去, 然後即使系統suspend之後,鬧鐘通過rtc 也能喚醒系統
這裡就調用到了interface.c 裡面 //這裡面 int rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm) 差不多 也是跟下面一樣
int rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm)
{
....
err = rtc->ops->set_time(rtc->dev.parent, tm);
....
}
然後set_time 就看到具體的是那個RTC晶片,這邊我們是rtc-pcf8563.c
static const struct rtc_class_ops pcf8563_rtc_ops = {
.read_time = pcf8563_rtc_read_time,
.set_time = pcf8563_rtc_set_time,
.read_alarm = pcf8563_rtc_read_alarm,
.set_alarm = pcf8563_rtc_set_alarm,
};
然後就到了
static int pcf8563_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
unsigned char buf[TIME_NUM];
int ret;
ret = data_calc(buf, tm, TIME_NUM);
if (ret < 0)
goto out;
ret = i2c_smbus_write_i2c_block_data(pcf8563_info->client, PCF8563_RTC_SEC, TIME_NUM, buf); //這邊就調用i2c統一介面,往pcf8563rtc晶片寄存器裡面寫出資料
out:
return ret;
}
到此,鬧鐘時間就已經寫到rtc 晶片的寄存器裡面,第二個參數就是寄存器的名字,後面的buf就是要寫入的時間,rtc晶片是額外供電的,所以系統suspend之後,系統kernel都關了,但是rtc裡面還有電,寄存器裡面資料還是有的(掉電就會遺失資料),所以鬧鐘到了,通過硬體中斷機制就可以喚醒系統。
上面那個rtc下面有幾十個rtc晶片驅動代碼,沒有結構基本一樣,都有基本操作函數,註冊函數,都是對各自晶片上特有的寄存器操作,為什麼調用的是pcf8563rtc呢?這個要看你系統用的是那個晶片,這個可以通過./kernel/kernel/kernel/.config 查看,這邊的pcf8563rtc 是當前系統正在使用的晶片型號
# CONFIG_RTC_DRV_ISL1208 is not set
# CONFIG_RTC_DRV_X1205 is not set
CONFIG_RTC_DRV_PCF8563=y
# CONFIG_RTC_DRV_PCF8583 is not set
# CONFIG_RTC_DRV_M41T80 is not set
下面是系統喚醒之後,鬧鐘怎麼工作的流程,簡單闡述
系統沒有suspend的話直接走下面流程,如果suspend的話會被RTC喚醒,然後還是走下面的流程
private class AlarmThread extends Thread
{
public AlarmThread()
{
super("AlarmManager");
}
public void run()
{
while (true)
{
int result = waitForAlarm(mDescriptor); //這裡調用jni調用static jint android_server_AlarmManagerService_waitForAlarm,主要還是對 /dev/alarm 操作
....
Alarm alarm = it.next();
try {
if (localLOGV) Slog.v(TAG, "sending alarm " + alarm);
alarm.operation.send(mContext, 0,
mBackgroundIntent.putExtra(
Intent.EXTRA_ALARM_COUNT, alarm.count),
mResultReceiver, mHandler);
....
}
}
}
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;
}
return result;
#endif
}
AlarmManagerService 裡面有個AlarmThread 會一直輪詢 /dev/alarm檔案,如果開啟失敗就直接返回,成功就會做一些動作,比如尋找時間最近的
alarm,比如睡眠被鬧鐘喚醒的時候,這邊就發一個intent出去,然後在AlarmReceiver.java裡面彈出裡面會收到就會調用下面的
context.startActivity(alarmAlert);
然後彈出alarm 這個介面
Class c = AlarmAlert.class;
其中public class AlarmAlert extends AlarmAlertFullScreen 所以系統睡眠之後被alarm喚醒彈出的alarm就是這邊start的
public class AlarmReceiver extends BroadcastReceiver {
/** If the alarm is older than STALE_WINDOW, ignore. It
is probably the result of a time or timezone change */
private final static int STALE_WINDOW = 30 * 60 * 1000;
@Override
public void onReceive(Context context, Intent intent) {
.........
Intent alarmAlert = new Intent(context, c);
alarmAlert.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
alarmAlert.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_NO_USER_ACTION);
context.startActivity(alarmAlert);
........
}
到這裡alarm 就顯示出來了
我個人添加的log,驗證了流程,suspend和不suspend的時候alarm的區別跟上面說的一樣