RTC(Real Time Clock),用于关机时继续计算系统日期和时间。是基于硬件的功能。也可以RTC做Alarm来设置power on/off。
Linux 提供了三种用户空间调用接口。对于笔者所用的平台,在其中对应的路径为:
SYSFS接口:/sys/class/rtc/rtc0/
PROCFS接口: /proc/driver/rtc
IOCTL接口: /dev/rtc0
凡事皆有因果,本次分析从因开始,由上至下,由因至果。
android层对时间的设置在settings系统应用中实现。
packages/apps/Settings/src/com/android/settings/DateTimeSettings.java
Android提供了两种方式对Android系统时间进行设置更新
1.网络校时
2.手动校时
这里先不对这两种校时方式的实现作分析,本文目的旨在rtc,那就顺着系统默认的设置,通过网络校时来分析。
网络校时android层最终是通过NetworkTimeUpdateService.java这个类来实现,咱直奔中心。
幕后操作则是onPollNetworkTimeUnderWakeLock方法。(先不管这个方法是在哪里被谁调用,如何调用的,记得本文目的是rtc)
//frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java
private void onPollNetworkTimeUnderWakeLock(int event) {
......
SystemClock.setCurrentTimeMillis(ntp);
......
}
这里为了更快的进入主题,笔者直接将该方法的其他有用没用的都省略了~~~。真是刺激呀。
可以看到android层设置更新时间的起始就是通过 SystemClock.setCurrentTimeMillis 开始的。
这样我们跟去看看它的实现。
//frameworks/base/core/java/android/os/SystemClock.java
/**
* Sets the current wall time, in milliseconds. Requires the calling
* process to have appropriate permissions.
*
* @return if the clock was successfully set to the specified time.
*/
public static boolean setCurrentTimeMillis(long millis) {
IBinder b = ServiceManager.getService(Context.ALARM_SERVICE);
IAlarmManager mgr = IAlarmManager.Stub.asInterface(b);
if (mgr == null) {
return false;
}
try {
return mgr.setTime(millis);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to set RTC", e);
} catch (SecurityException e) {
Slog.e(TAG, "Unable to set RTC", e);
}
return false;
}
这里注释里提到一个wall time,翻译为墙上时间。这里解释下墙上时间。
系统启动时,内核通过读取RTC来初始化内核时钟,又叫墙上时间,该时间放在struct timespec xtime变量中。
回到上面,setCurrentTimeMillis仅仅作了一件事。那就是mgr.setTime(millis);
这个mgr是什么,往上看。
1.首先取得ALARM_SERVICE的binder对象。
2.通过binder对象得到IAlarmManager的接口方法。
那么,这就走到了IAlarmManager.setTime里去了。
不过这里IAlarmManager很明显是个接口类。
//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(String callingPackage, int type, long triggerAtTime, long windowLength,
long interval, int flags, in PendingIntent operation, in IAlarmListener listener,
String listenerTag, in WorkSource workSource, in AlarmManager.AlarmClockInfo alarmClock);
boolean setTime(long millis);
void setTimeZone(String zone);
void remove(in PendingIntent operation, in IAlarmListener listener);
long getNextWakeFromIdleTime();
AlarmManager.AlarmClockInfo getNextAlarmClock(int userId);
// update the uids being synchronized by network socket request manager
void updateBlockedUids(int uid, boolean isBlocked);
}
既然是接口,那么得有人去实现它。那就是–看注释,哈哈,告诉读者一个有意思的,在你没有相关知识,或者经验的时候,比如不知道谁调用实现了这个接口类,那么开源代码中的注释是非常有用的信息。这里就说到了它和 alarm manager service(后称AMS,注意区分ActivityManagerService) 进行talk 。
不过这里要注意了,这里的注释是talk。并不是实现。不过有点可以肯定。最终setTime在AMS中肯定也有,你也可以直接去看,不过这里笔者还是按照正常流程进行分析。
既然AMS和用来和它talk的,那么AMS很明显结合命名看它就是服务端,那么作为binder通讯,实现IAlarmManager肯定是客户端。还记得上面讲到setCurrentTimeMillis方法里的实现吗?没错第二步就是得到IAlarmManager的客户端。那现在就可以直接到AMS里去看setTime的实现了。在过去的时候,这里提一句。可能有些人在看到IAlarmManager会想到AlarmManager,这里可以抽点时间过去看看。AlarmManager.java中也会有setTime的实现。
//frameworks/base/core/java/android/app/AlarmManager.java
/**
* Set the system wall clock time.
* Requires the permission android.permission.SET_TIME.
*
* @param millis time in milliseconds since the Epoch
*/
public void setTime(long millis) {
try {
mService.setTime(millis);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
可以看到AlarmManager.setTime 最终是调用了mService.setTime。注意了,这里的mService就是IAlarmManager对象。这样看。它也算是个binder的客户端了。这里其实是为了给上层提供接口通过AlarmManager.java调用到AMS中setTime方法。这里暂且说到这,夹杂了些许binder的相关知识。不理解的可自行百度。
继续回到AMS中的setTime。
//frameworks/base/services/core/java/com/android/server/AlarmManagerService.java
private final IBinder mService = new IAlarmManager.Stub() {
@Override
public void set(String callingPackage,
int type, long triggerAtTime, long windowLength, long interval, int flags,
PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock) {
.......
}
}
@Override
public boolean setTime(long millis) {
......
return setKernelTime(mNativeData, millis) == 0;
}
@Override
public void setTimeZone(String tz) {
.......
}
@Override
public void remove(PendingIntent operation, IAlarmListener listener) {
......
}
@Override
public long getNextWakeFromIdleTime() {
.......
}
@Override
public AlarmManager.AlarmClockInfo getNextAlarmClock(int userId) {
.......
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
.......
}
@Override
/* updates the blocked uids, so if a wake lock is acquired to only fire
* alarm for it, it can be released.
*/
public void updateBlockedUids(int uid, boolean isBlocked) {
.......
}
};
这里可以看到在AMS内部实现了IAlarmManager 的 IBinder对象 mService。
那么它就实现了IAlarmManager接口所定义的方法。找到setTime。在setTime中最终调用了setKernelTime()。
在AMS中可以看到,这个setKernelTime方法是个native方式,看来最终到了jni层。
//frameworks/base/services/core/jni/com_android_server_AlarmManagerService.cpp
static jint android_server_AlarmManagerService_setKernelTime(JNIEnv*, jobject, jlong nativeData, jlong millis)
{
AlarmImpl *impl = reinterpret_cast(nativeData);
struct timeval tv;
int ret;
if (millis <= 0 || millis / 1000LL >= INT_MAX) {
return -1;
}
tv.tv_sec = (time_t) (millis / 1000LL);
tv.tv_usec = (suseconds_t) ((millis % 1000LL) * 1000LL);
ALOGD("Setting time of day to sec=%d\n", (int) tv.tv_sec);
ret = impl->setTime(&tv);
if(ret < 0) {
ALOGW("Unable to set rtc to %ld: %s\n", tv.tv_sec, strerror(errno));
ret = -1;
}
return ret;
}
在native setKernelTime 方法中又调用了impl->setTime,这可真是百转千绕啊。
这个impl从代码中可以看到是AlarmImpl对象。它的类声明实现就在本类中com_android_server_AlarmManagerService.cpp
//frameworks/base/services/core/jni/com_android_server_AlarmManagerService.cpp
class AlarmImpl
{
public:
AlarmImpl(int *fds, size_t n_fds);
virtual ~AlarmImpl();
virtual int set(int type, struct timespec *ts) = 0;
virtual int clear(int type, struct timespec *ts) = 0;
virtual int setTime(struct timeval *tv) = 0;
virtual int waitForAlarm() = 0;
protected:
int *fds;
size_t n_fds;
};
class AlarmImplAlarmDriver : public AlarmImpl
{
public:
AlarmImplAlarmDriver(int fd) : AlarmImpl(&fd, 1) { }
int set(int type, struct timespec *ts);
int clear(int type, struct timespec *ts);
int setTime(struct timeval *tv);
int waitForAlarm();
};
class AlarmImplTimerFd : public AlarmImpl
{
public:
AlarmImplTimerFd(int fds[N_ANDROID_TIMERFDS], int epollfd, int rtc_id) :
AlarmImpl(fds, N_ANDROID_TIMERFDS), epollfd(epollfd), rtc_id(rtc_id) { }
~AlarmImplTimerFd();
int set(int type, struct timespec *ts);
int clear(int type, struct timespec *ts);
int setTime(struct timeval *tv);
int waitForAlarm();
private:
int epollfd;
int rtc_id;
};
可以看到AlarmImpl的声明完全是个抽象类,得必须有子类去实现它,而在现在实现它的有两个子类。AlarmImplAlarmDriver和AlarmImplTimerFd ,都是公有继承。
而这两个子类都实现了setTime。那最终是走了哪里呢?
因为笔者C++不是太懂,这里直接跑系统加日志最终是走了AlarmImplTimerFd::setTime。
这里留个坑,为什么走了了这条路,还请知道的朋友记得在评论区告知。感谢~
//frameworks/base/services/core/jni/com_android_server_AlarmManagerService.cpp
int AlarmImplTimerFd::setTime(struct timeval *tv)
{
struct rtc_time rtc;
struct tm tm, *gmtime_res;
int fd;
int res;
res = settimeofday(tv, NULL);
if (res < 0) {
ALOGV("settimeofday() failed: %s\n", strerror(errno));
return -1;
}
if (rtc_id < 0) {
ALOGV("Not setting RTC because wall clock RTC was not found");
errno = ENODEV;
return -1;
}
android::String8 rtc_dev = String8::format("/dev/rtc%d", rtc_id);
fd = open(rtc_dev.string(), O_RDWR);
if (fd < 0) {
ALOGV("Unable to open %s: %s\n", rtc_dev.string(), strerror(errno));
return res;
}
gmtime_res = gmtime_r(&tv->tv_sec, &tm);
if (!gmtime_res) {
ALOGV("gmtime_r() failed: %s\n", strerror(errno));
res = -1;
goto done;
}
memset(&rtc, 0, sizeof(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;
res = ioctl(fd, RTC_SET_TIME, &rtc);
if (res < 0)
ALOGV("RTC_SET_TIME ioctl failed: %s\n", strerror(errno));
done:
close(fd);
return res;
}
这里可以看到AlarmImplTimerFd中的setTime里作了这么几件事。
1.调用settimeofday;
2.取得要操作的rtcX设备并打开它,笔者这里是/dev/rtc0;
3.通过ioctl对打开的rtc0设备进行操作。
ps:笔者这里是高通的平台,在调试过程中发现,当走到ioctl这步的时候,笔者发现这里总是失败。报错无效参数。最终发现是ker驱动中。没有打开对rtc的属性set。但是在笔者打开相应的设置后,发现一运行到这步,系统就死机挂掉了。询问上级厂商,给的回复是qcom平台的rtc就是这样的设定。只能Read不能Write。
那么这里问题就来了,既然qcom平台没有使用Android默认的rtc设置方式,那么肯定也有自己的rtc设置方式吧。先留个疑问,在分析rtc read后释疑。
到目前为止,除开ker层从上往下的set流程都分析完毕。jni中通过对设备文件的ioctl将操作传递到ker层。
ker层的rtc分析另开博客分析。
这里再分析下从rtc read时间的流程。从上述setTime来看。是通过ioctl来实现的。但在整个流程分析下来并没有发现和read相关的实现(笔者的平台是这样的)。那么系统是从何处执行read操作的呢?
要知道ker层提供的read接口就是通过ioctl接口。那么笔者在源代码根目录开始了地毯式搜索。最终在vendor目录下找到了一丝线索。
vendor/qcom/proprietary/time-services/* 【后面再开一篇简单分析下这个私有代码,因为是私有代码,没有开源,所以分析的时候可能上的代码不全,敬请谅解】
vendor目录通常是厂商定制的一些代码。
在该目录下的time_daemon_qmi.c找到了对/dev/rtc0的ioctl操作。需要注意的是,这里有两个地方对/dev/rtc0进行了open和ioctl。这里又留了一个坑。
//vendor/qcom/proprietary/time-services/time_daemon_qmi.c
static int rtc_get(int64_t *msecs)
{
......
fd = open("/dev/rtc0", O_RDONLY);
......
rc = ioctl(fd,RTC_RD_TIME,&rtc_tm);
......
}
static int ats_rtc_init(time_genoff_ptr ptime_genoff)
{
......
fd = open("/dev/rtc0", O_RDONLY);
......
rc = ioctl(fd,RTC_RD_TIME,&rtc_tm);
......
}
分析完qcom的rtc read,有没有想到一个问题,为啥源生代码中没有看到rtc read 相关,从app到jni 都没看到rtc read 相关的代码,这点有点疑问,知道的大佬还请在评论区留言告知。感谢!!!
前面花了大篇幅跟踪了rtc的设置流程,跟到ams settime ioctl的时候,出现了问题。那么Android系统总的要有settime这个功能,3.2中分析了rtc read,同样的,在本平台中。set也是在该模块中实现。通过了广播触发的方式。下面看代码,因为是高通私有代码这里只做简要分析
//vendor/qcom/proprietary/time-services/src/com/qualcomm/timeservice/TimeServiceBroadcastReceiver.java
public class TimeServiceBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "TimeService";
public native void setTimeServicesUserTime(long millis); //本地方法
static { System.loadLibrary("TimeService"); } //加载本地库TimeService
@Override
public void onReceive(Context context, Intent intent) {
if ((Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) ||
(Intent.ACTION_DATE_CHANGED.equals(intent.getAction()))) { //可以看到这里就是触发设置的广播
Log.d(TAG, "Received" + intent.getAction() + " intent. " +
"Current Time is " + System.currentTimeMillis());
setTimeServicesUserTime(System.currentTimeMillis());
} else {
Log.w(TAG, "Received Unexpected Intent " + intent.toString());
}
}
}
TimeServiceBroadcastReceiver作了这么几件事。
1.声明本地native方法。
2.加载包含刚刚声明的方法的本地库。
3.实现BroadcastReceiver接口。监听Intent.ACTION_TIME_CHANGED和Intent.ACTION_DATE_CHANGED。可以推测当时间和日期改变的时候,将会触发将当前时间进行设置。
给setTimeServicesUserTime传值的时间是通过System.currentTimeMillis()得到的。这里应该还记得上面分析rtc 网络校时的时候,在NetworkTimeUpdateService中onPollNetworkTimeUnderWakeLock方法中调用了SystemClock.setCurrentTimeMillis(ntp);进行校时后的时间设置。那么在校时后,时间日期改变,这里广播也会监听到,从而这里获取到的当前时间就是当前的正确时间,那么通过setTimeServicesUserTime将正确时间进行设置。qcom rtc settime就完成了。
setTimeServicesUserTime的具体实现不进行分析了。
因果分析完毕,因学艺不精,留了一些坑,望知道的大佬评论指教。关于rtc,还有两部分需要单独开博客分析,这里记录下:
1.ker层rtc框架和qcom rtc 驱动分析。
2.time-services模块分析。(这个不会太详细)
另外,前面分析了那么多,字多眼花。附上一张图看的舒心点,我个人就更喜欢看图~~~
Qcom平台RTC驱动分析
Linux内核中_IO,_IOR,_IOW,_IOWR宏的用法与解析
Android RTC 自下而上分析
Freescale i.MX6平台Android4.4.3之外部硬件RTC自动同步网络时间调试经验
GMT、UTC、DST、CST时区代表的意义