Android12 AlarmManagerService底层解析及开机闹钟

最近项目中需要为一个硬件实现定时开机的功能,借此机会把AlarmManagerService从上层到底层的流程给梳理了一遍。

首先在应用层只能使用AlarmManager与AlarmManagerService进行通信,就不在此赘述了。

AlarmManagerService

现在来看看AlarmManagerService的源码,在Android 12中其位置为frameworks\base\apex\jobscheduler\service\java\com\android\server\alarm。 AlarmManager 通过aidl与AlarmManagerService通信。因此AlarmManagerService会返回IAlarmManager的IBinder。

一般接触得最多的是set、setTime和setTimeZone,其作用分别是设置闹钟,设置时间,设置时区。

setTime和setTimeZone比较简单,经过层层方法跳转,最终调用的是jni方法的setKernelTime和setKernelTimezone。

而set比较复杂,经过set -> setImpl -> setImplLocked -> setImplLocked -> rescheduleKernelAlarmsLocked -> setLocked层层方法的权限、条件检查,最终走到:

java 复制代码
private void setLocked(int type, long when) {
    // AlarmManagerService是否已经初始化
    if (mInjector.isAlarmDriverPresent()) {
        // 初始化则调用AlarmManagerService.setAlarm
        mInjector.setAlarm(type, when);
    } else {
        // 未初始化则放到handler里延迟一段时间继续处理
        Message msg = Message.obtain();
        msg.what = AlarmHandler.ALARM_EVEN
        mHandler.removeMessages(msg.what);
        mHandler.sendMessageAtTime(msg, when);
    }
}

AlarmManagerService.setAlarm方法最终调用的是jni方法的set。至此所涉及的方法都进入jni。

AlarmManagerService的jni实现在frameworks\base\apex\jobscheduler\service\jni\com_android_server_alarm_AlarmManagerService.cpp。

AlarmManagerService.cpp

jni 的方法定义如下

c++ 复制代码
static const JNINativeMethod sMethods[] = {
    /* name, signature, funcPtr */
    {"init", "()J", (void*)android_server_alarm_AlarmManagerService_init},
    {"close", "(J)V", (void*)android_server_alarm_AlarmManagerService_close},
    {"set", "(JIJJ)I", (void*)android_server_alarm_AlarmManagerService_set},
    {"waitForAlarm", "(J)I", (void*)android_server_alarm_AlarmManagerService_waitForAlarm},
    {"setKernelTime", "(JJ)I", (void*)android_server_alarm_AlarmManagerService_setKernelTime},
    {"setKernelTimezone", "(JI)I", (void*)android_server_alarm_AlarmManagerService_setKernelTimezone},
    {"getNextAlarm", "(JI)J", (void*)android_server_alarm_AlarmManagerService_getNextAlarm},
};

jni的方法本身都只是做了一层入参的检查过滤,最终调用AlarmImpl的对应方法。

其中setKernelTime对应的是AlarmImpl.setTime,最终调用的是ioctl(fd, RTC_SET_TIME, &rtc):

c++ 复制代码
int AlarmImpl::setTime(struct timeval *tv)
{
    // 调用settimeofday将时间写入到系统
    if (settimeofday(tv, NULL) == -1) {
        ALOGV("settimeofday() failed: %s", strerror(errno));
        return -1;
    }

    // 连接rtc设备
    android::base::unique_fd fd{open(rtc_dev.c_str(), O_RDWR)};
    if (!fd.ok()) {
        ALOGE("Unable to open %s: %s", rtc_dev.c_str(), strerror(errno));
        return -1;
    }

    struct tm tm;
    // 转换为utc时间
    if (!gmtime_r(&tv->tv_sec, &tm)) {
        ALOGV("gmtime_r() failed: %s", strerror(errno));
        return -1;
    }

    // 构造为rtc标准的时间结构
    struct rtc_time rtc = {};
    rtc.tm_sec = tm.tm_sec;
    rtc.tm_min = tm.tm_min;
    rtc.tm_hour = tm.tm_hour;
    rtc.tm_mday = tm.tm_mday;
    rtc.tm_mon = tm.tm_mon;
    rtc.tm_year = tm.tm_year;
    rtc.tm_wday = tm.tm_wday;
    rtc.tm_yday = tm.tm_yday;
    rtc.tm_isdst = tm.tm_isdst;
    // 通过ioctl方法将时间设置到硬件
    if (ioctl(fd, RTC_SET_TIME, &rtc) == -1) {
        ALOGV("RTC_SET_TIME ioctl failed: %s", strerror(errno));
        return -1;
    }

    return 0;
}

ioctl会执行到对应驱动目录drivers\rtc下dev.c的rtc_dev_ioctl方法,根据第二个入参(此处为RTC_SET_TIME)来决定要执行的方法。

setKernelTimezone只是对当前系统时间根据传入的时区进行简单的加减,所以不会涉及到变更硬件时间的操作:

c++ 复制代码
static jint android_server_alarm_AlarmManagerService_setKernelTime(JNIEnv*, jobject, jlong nativeData, jlong millis)
{
    AlarmImpl *impl = reinterpret_cast<AlarmImpl *>(nativeData);

    if (millis <= 0 || millis / 1000LL >= std::numeric_limits<time_t>::max()) {
        return -1;
    }

    struct timeval tv;
    tv.tv_sec = (millis / 1000LL);
    tv.tv_usec = ((millis % 1000LL) * 1000LL);

    ALOGD("Setting time of day to sec=%ld", tv.tv_sec);

    // 根据时区调整系统的时间
    int ret = impl->setTime(&tv);
    if (ret < 0) {
        ALOGW("Unable to set rtc to %ld: %s", tv.tv_sec, strerror(errno));
        ret = -1;
    }
    return ret;
}

set方法会调用AlarmImpl.set,AlarmImpl.set直接调用timerfd_settime(fds[type], TFD_TIMER_ABSTIME, &spec, NULL),创建一个系统层级的定时器,因此关机之后就失去作用了:

c++ 复制代码
int AlarmImpl::set(int type, struct timespec *ts)
{
    if (static_cast<size_t>(type) > ANDROID_ALARM_TYPE_COUNT) {
        errno = EINVAL;
        return -1;
    }

    // tv_nsec和tv_sec都为 0 就会关闭闹钟,所以tv_nsec设置为 1 使闹钟仍能执行
    if (!ts->tv_nsec && !ts->tv_sec) {
        ts->tv_nsec = 1;
    }

    // 创建一个spec结构,并将ts的值复制到结构spec的it_value属性里
    struct itimerspec spec;
    memset(&spec, 0, sizeof(spec));
    memcpy(&spec.it_value, ts, sizeof(spec.it_value));

    // TFD_TIMER_ABSTIME意味着绝对时间,根据传入时间的年月日时分秒来设置闹钟,超过该时间则闹钟过期
    // 可选参数有:
    // 0 相对时间,则当前时间 + 传入时间
    // TFD_TIMER_CANCEL_ON_SET 同样是绝对时间,但当时间被重新设置时会取消闹钟
    return timerfd_settime(fds[type], TFD_TIMER_ABSTIME, &spec, NULL);
}

驱动

其中setKernelTime对应的是AlarmImpl.setTime除了设置一个系统时间外(settimeofday),还会调用对应的驱动将系统时间设置到硬件中,以此保证关机再开启时间仍是正常流动。

在驱动中需要关注的是文件是dev.c、interface.c和class.c

class.c提供了诸如硬件分配、注册、反注册等等一些最基础的操作。interface.c主要定义了在class.c上的一些基本操作接口。dev.c则提供了更贴近应用层的一些操作函数,如ioctl方法最终的指向则定义在该文件下的rtc_dev_fops处:

c 复制代码
static const struct file_operations rtc_dev_fops = {
    .owner        = THIS_MODULE,
    .llseek        = no_llseek,
    .read        = rtc_dev_read,
    .poll        = rtc_dev_poll,
    .unlocked_ioctl    = rtc_dev_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl    = rtc_dev_compat_ioctl,
#endif
    .open        = rtc_dev_open,
    .release    = rtc_dev_release,
    .fasync        = rtc_dev_fasync,
};

图示可以如此理解:

graph LR node(alarmservice.cpp)-->node1(dev.c)-->node2(interface.c)-->node3(class.c)

因此AlarmImpl.setTime最后调用了ioctl(fd, RTC_SET_TIME, &rtc)。会执行dev.c的rtc_dev_ioctl,并根据RTC_SET_TIME执行其case分支的:

c 复制代码
case RTC_SET_TIME:
    mutex_unlock(&rtc->ops_lock);

    if (copy_from_user(&tm, uarg, sizeof(tm)))
        return -EFAULT;

    return rtc_set_time(rtc, &tm);

rtc_set_time的方法定义在interface.c处,需要关注的位置是:

c 复制代码
...
if (!rtc->ops)
    err = -ENODEV;
    // ops是否有set_time方法
else if (rtc->ops->set_time)
    // 执行ops的set_time方法
    err = rtc->ops->set_time(rtc->dev.parent, tm);
else
    err = -EINVAL;
...

rtc->ops在class.c的devm_rtc_device_register中进行初始化,则在rtc设备注册的时候会创建一个rtc_device结构并将实际的rtc设备赋值到属性ops下,因此这里的ops可以理解为操作对应的驱动文件:

c 复制代码
struct rtc_device *devm_rtc_device_register(struct device *dev,
                        const char *name,
                        const struct rtc_class_ops *ops,
                        struct module *owner)
{
    struct rtc_device *rtc;
    int err;

    rtc = devm_rtc_allocate_device(dev);
    if (IS_ERR(rtc))
        return rtc;
    
    rtc->ops = ops;

    err = __rtc_register_device(owner, rtc);
    if (err)
        return ERR_PTR(err);

    return rtc;
}

开机闹钟

在dev.c的rtc_dev_ioctl中可以看到,其实已经提供了设置rtc闹钟的上层实现RTC_WKALM_SET和RTC_ALM_SET,这里我们使用RTC_WKALM_SET,至于为什么不用RTC_ALM_SET,则可以参考其case下的注释:

c 复制代码
/* RTC_ALM_SET alarms may be up to 24 hours in the future.
 * Rather than expecting every RTC to implement "don't care"
 * for day/month/year fields, just force the alarm to have
 * the right values for those fields.
 *
 * 主要是RTC_WKALM_SET会自动使能闹钟中断
 * RTC_WKALM_SET should be used instead.  Not only does it
 * eliminate the need for a separate RTC_AIE_ON call, it
 * doesn't have the "alarm 23:59:59 in the future" race.
 *
 * NOTE:  some legacy code may have used invalid fields as
 * wildcards, exposing hardware "periodic alarm" capabilities.
 * Not supported here.
 */

先在IAlarmManager.aidl、AlarmManager.java和AlarmManagerService.java中增加对应的接口供应用层调用:

java 复制代码
// IAlarmManager.aidl
boolean setPowerOnAlarm(long millis);
long getPowerOnAlarm();

// AlarmManager.java
public void setPowerOnAlarm(long millis) {
    try {
        mService.setPowerOnAlarm(millis);
    } catch (RemoteException ex) {
        throw ex.rethrowFromSystemServer();
    }
}

public long getPowerOnAlarm() {
    try {
        mService.getPowerOnAlarm();
    } catch (RemoteException ex) {
        throw ex.rethrowFromSystemServer();
    }
    return -1;
}

AlarmManagerService.java中分多个部分添加

java 复制代码
// IBinder mService
@Override
public boolean setPowerOnAlarm(long millis) {
    getContext().enforceCallingOrSelfPermission(
            "android.permission.SET_TIME",
            "setPowerOnA
    return setPowerOnAlarmImpl(millis);

@Override
public long getPowerOnAlarm() {
    getContext().enforceCallingOrSelfPermission(
            "android.permission.SET_TIME",
            "getPowerOnAlarm");
    long millis = -1;
    synchronized (mLock) {
        millis = mInjector.getPowerOnAlarm();
        Slog.i(TAG, "Reading power on alarm of rtc: " + millis);
    }
    return millis;
}
java 复制代码
// AlarmManagerService.java
private static native int setPowerOnAlarm(long nativeData, long millis);
private static native long getPowerOnAlarm(long nativeData);

boolean setPowerOnAlarmImpl(long millis) {
    synchronized (mLock) {
    	mInjector.setPowerOnAlarm(millis);
        return true;
    }
}
java 复制代码
// class Injector
void setPowerOnAlarm(long millis) {
    if (mNativeData != 0) {
        AlarmManagerService.setPowerOnAlarm(mNativeData, millis);
    }
}

long getPowerOnAlarm() {
    if (mNativeData != 0) {
        return AlarmManagerService.getPowerOnAlarm(mNativeData);
    }
    return -1;
}

最终调用顺序为

mService.setPowerOnAlarm->setPowerOnAlarmImpl->Injector.setPowerOnAlarm

->native setPowerOnAlarm

jni中声明两个方法:

c++ 复制代码
// 设置开机闹钟
{"setPowerOnAlarm", "(JJ)I", (void*)android_server_alarm_AlarmManagerService_setPowerOnAlarm},
// 获取已设置的开机闹钟
{"getPowerOnAlarm", "(J)J", (void*)android_server_alarm_AlarmManagerService_getPowerOnAlarm},

其对应的实现,就是直接调用AlarmImpl的相关方法:

c++ 复制代码
static jint android_server_alarm_AlarmManagerService_setPowerOnAlarm(JNIEnv*, jobject, jlong nativeData, jlong millis)
{
    AlarmImpl *impl = reinterpret_cast<AlarmImpl *>(nativeData);
    
    // 入参的校验,避免设置了不合法的时间
    if (millis <= 0 || millis / 1000LL >= std::numeric_limits<time_t>::max()) {
        return -1;
    }
    
    struct timeval tv;
    tv.tv_sec = (millis / 1000LL);
    tv.tv_usec = ((millis % 1000LL) * 1000LL);
    
    
    int ret = impl->setPowerOnAlarm(&tv);
    if (ret < 0) {
        ALOGW("Unable to set power on alarm rtc to %ld: %s", tv.tv_sec, strerror(errno));
        ret = -1;
    }
    return ret;
}

static jlong android_server_alarm_AlarmManagerService_getPowerOnAlarm(JNIEnv*, jobject, jlong nativeData)
{
    AlarmImpl *impl = reinterpret_cast<AlarmImpl *>(nativeData);
    return impl->getPowerOnAlarm();
}

AlarmImpl中添加实现,这里需要关注setPowerOnAlarm方法中将rtc用rtc_wkalrm包裹一次,并设置其enabled为 1:

c++ 复制代码
int AlarmImpl::setPowerOnAlarm(struct timeval *tv)
{
    android::base::unique_fd fd{open(rtc_dev.c_str(), O_RDWR)};
    if (!fd.ok()) {
        ALOGE("Unable to open %s: %s", rtc_dev.c_str(), strerror(errno));
        return -1;
    }

    struct tm tm;
    if (!gmtime_r(&tv->tv_sec, &tm)) {
        ALOGV("gmtime_r() failed: %s", strerror(errno));
        return -1;
    }
    
    char timeStr[80];
    strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", &tm);

    // 创建rtc_time结构来设置闹钟的时间
    struct rtc_time rtc = {};
    rtc.tm_sec = tm.tm_sec;
    rtc.tm_min = tm.tm_min;
    rtc.tm_hour = tm.tm_hour;
    rtc.tm_mday = tm.tm_mday;
    rtc.tm_mon = tm.tm_mon;
    rtc.tm_year = tm.tm_year;
    rtc.tm_wday = tm.tm_wday;
    rtc.tm_yday = tm.tm_yday;
    rtc.tm_isdst = tm.tm_isdst;
    
    struct rtc_wkalrm alarm = {};
    // 设置闹钟的enabled为 1
    alarm.enabled = 1;
    alarm.time = rtc;
    if (ioctl(fd, RTC_WKALM_SET, &alarm) == -1) {
        ALOGV("RTC_SET_TIME ioctl failed: %s", strerror(errno));
        return -1;
    }

    return 0;
}

long AlarmImpl::getPowerOnAlarm() {
    android::base::unique_fd fd{open(rtc_dev.c_str(), O_RDWR)};
    if (!fd.ok()) {
        ALOGE("Unable to open %s: %s", rtc_dev.c_str(), strerror(errno));
        return -1;
    }
    return ioctl(fd, RTC_WKALM_RD, NULL);
}

alarm.enabled = 1对应的是interface.c中的

c 复制代码
int rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
{
    int err;

    if (!rtc->ops)
        return -ENODEV;
    else if (!rtc->ops->set_alarm)
        return -EINVAL;

    err = rtc_valid_tm(&alarm->time);
    if (err != 0)
        return err;

    err = rtc_valid_range(rtc, &alarm->time);
    if (err)
        return err;

    err = mutex_lock_interruptible(&rtc->ops_lock);
    if (err)
        return err;
    // aie_timer是rtc中的一个定时器
    // 清理掉上一个设置的定时器
    if (rtc->aie_timer.enabled)
        rtc_timer_remove(rtc, &rtc->aie_timer);
    
    // 将闹钟时间转换为内核时间,设置到定时器中
    rtc->aie_timer.node.expires = rtc_tm_to_ktime(alarm->time);
    // 设置为到期只执行一次,周期执行规则留给应用层处理
    rtc->aie_timer.period = 0;
    // 如果没有设置enabled属性为 1,则闹钟不会被添加到rtc的定时器队列中
    // 则闹钟到期后也不会执行对应的方法
    if (alarm->enabled)
        err = rtc_timer_enqueue(rtc, &rtc->aie_timer);

    mutex_unlock(&rtc->ops_lock);

    return err;
}

在驱动的probe初始化中添加以下复位逻辑

c 复制代码
err = pcf8563_get_alarm_mode(client, NULL, &alm_pending);
if (err) {
    dev_err(&client->dev, "%s: read error\n", __func__);
    return err;
}

// 开机后清空闹钟
if (alm_pending)
    pcf8563_set_alarm_mode(client, 0);

参考内容

linux timerfd系列函数总结

Linux下RTC时间的读写分析

相关推荐
晚风叙码5 分钟前
MySQL 数据类型详解:从数值到字符串,一篇讲透
android·mysql·adb
传奇开心果编程29 分钟前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程3 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程5 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程5 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程5 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓5 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程5 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer
supabc1236 小时前
Celium:连接 Windows、Mac、Linux 与 Android,让远程访问和设备管理更简单
android·linux·windows·macos·远程访问·网络管理·celium
Dovis(誓平步青云)6 小时前
导览音频切换太快,旧讲解不能覆盖新展品
android·前端·javascript·ecmascript·音视频·宠物