本文来自依云's Blog,转载请注明。
每一次,系统从挂起状态恢复,系统日志里总会多这么几行:
systemd[1]: Time has been changed crond[324]: time disparity of 698 minutes detected
一个来自 systemd,一个来自 dcron,都是说系统时间改变了。那么它们是怎么知道系统时间改变的呢?
dcron 的代码很少,所以很快就可以找到。因为 dcron 每一次的睡眠时长它自己知道,所以当它再次从睡眠状态醒来,发现时间变化特别大时,它就会察觉到。也就是说,小的变化它会察觉不到的。
systemd 呢?这家伙一直在使用 Linux 新加特性,比如上次发现的 prctl 的 PR_SET_CHILD_SUBREAPER 功能。这次它也没有让我失望,它使用了 timerfd 的一个鲜为人知的标志位——TFD_TIMER_CANCEL_ON_SET。timerfd 是 Linux 2.6.25 引入的特性,而TFD_TIMER_CANCEL_ON_SET这个标志位则据说 Linux 3.0 引入的,但是到目前为止(man-pages 3.61),手册里没有提到它,系统头文件里也没有它。
这个标志位是干什么的呢?其实很简单,是当系统时钟被重设时向程序发送通知,包括通过系统调用设置系统时间,以及系统从硬件时钟更新时间时。当事件发生时,在该 timerfd 上的读取操作会返回 -1 表示失败,而 errno 被设置成ECANCELED。下边是一个简单的演示程序,在系统时间变化时打印一条消息:
#include<unistd.h>
#include<sys/timerfd.h>
#include<stdbool.h>
#include<stdint.h>
#include<errno.h>
#include<stdlib.h>
#include<stdio.h>
#define TIME_T_MAX (time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1)
#ifndef TFD_TIMER_CANCEL_ON_SET
# define TFD_TIMER_CANCEL_ON_SET (1 << 1)
#endif
int main(int argc, char **argv){
int fd;
struct itimerspec its = {
.it_value.tv_sec = TIME_T_MAX,
};
fd = timerfd_create(CLOCK_REALTIME, TFD_CLOEXEC);
if(fd < 0){
perror("timerfd_create");
exit(1);
}
if(timerfd_settime(fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET,
&its, NULL) < 0) {
perror("timerfd_settime");
exit(1);
}
uint64_t exp;
ssize_t s;
while(true){
s = read(fd, &exp, sizeof(uint64_t));
if(s == -1 && errno == ECANCELED){
printf("time changed.\n");
}else{
printf("meow? s=%zd, exp=%lu\n", s, exp);
}
}
return 0;
}
编译并运行该程序,然后拿 date 命令设置时间试试吧 =w= 当然记得用虚拟机哦,因为系统时间乱掉的时候会发生不好的事情喵~
date 091508002012

Mar 15, 2014 07:51:26 PM
meow?
Mar 15, 2014 08:21:26 PM
You clock has reached its maximum time value?
Apr 02, 2014 12:16:39 AM
这是不是全面的吧,我这没有这个问题呢
Apr 02, 2014 08:50:50 PM
哪里有什么问题?我只是发现了 Linux 的一个新特性而已啦。