本文来自依云'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 的一个新特性而已啦。