时钟
-
硬件时钟
- RTC(real time clock),记录wall clock time,硬件对应到/dev/rtc设备文件,读取设备文件可得到硬件时间
- 读取方式
- 通过ioctl
#include <linux/rtc.h>
int ioctl(fd, RTC_request, param); - hwclock命令
- 通过ioctl
- 通常内核在boot以及从低电量中恢复时,会读取RTC更新system time
-
软件时钟
- HZ and jiffies, 由内核维护,对于PC通常HZ配置为 1s / 10ms = 100
- 精度影响select等依赖timeout的系统调用
- HRT(high-resolution timers). Linux 2.6.21开始,内核支持高精度定时器,不受内核jiffy限制,可以达到硬件时钟的精度。
-
外部时钟
- 从网络ntp,原子钟等同步
时间
-
时间类别
- wall clock time => 硬件时间
- real time => 从某个时间点(比如Epoch)开始的系统时间
- sys and user time => 通常指程序在内核态和用户态花的时间
-
时间的表示
- time_t 从Epoch开始的秒数
- calendar time 字符串
- 拆分时间 struct tm
struct tm {
int tm_sec; /* seconds */
int tm_min; /* minutes */
int tm_hour; /* hours */
int tm_mday; /* day of the month */
int tm_mon; /* month */
int tm_year; /* year */
int tm_wday; /* day of the week */
int tm_yday; /* day in the year */
int tm_isdst; /* daylight saving time */
}; - struct timeval/struct timespec
struct timeval { time_t seconds; suseconds_t useconds; } struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ };
系统时间的操作
#include <time.h>
#include <sys/time.h>
// number of seconds since epoch
time_t time(time_t *t)
//参数time_t*
char *ctime(const time_t *timep);
char *ctime_r(const time_t *timep, char *buf);
struct tm *gmtime(const time_t *timep);
struct tm *gmtime_r(const time_t *timep, struct tm *result);
struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);
//参数struct tm*
char *asctime(const struct tm *tm);
char *asctime_r(const struct tm *tm, char *buf);
time_t mktime(struct tm *tm);
int gettimeofday(struct timeval *tv, struct timezone *tz);//如果系统时间调整了会影响
int clock_gettime(clockid_t clk_id, struct timespec *tp);
//将tm按照format处理后放到s
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
//将字符串时间s按照format格式化后放入tm
char *strptime(const char *s, const char *format, struct tm *tm);
定时器
- sleep
unsigned int sleep(unsigned int seconds);
- usleep
int usleep(useconds_t usec);
- nanosleep
int nanosleep(const struct timespec *req, struct timespec *rem);
- alarm
// SIGALARM after seconds
unsigned int alarm(unsigned int seconds);
- timer_create
int timer_create(clockid_t clockid, struct sigevent *sevp,
timer_t *timerid);
- setitimer
- timerfd_create + select/poll/epoll
int timerfd_create(int clockid, int flags);
- select
// struct timeval可以精确到微秒(如果硬件有高精度时钟支持)
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
// struct timespec可以精确到纳秒,但是pselect下次无法修改timeout
int pselect(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, const struct timespec *timeout,
const sigset_t *sigmask);
// 一般能提供周期,延时,时间点触发,但核心还是时间点触发的timer
// 1.call_period => 触发一次重新注册call_at
// 2.call_later => 转换为call_at
// 3.call_at => 时间点触发的timer可以用一个优先级队列保存
- poll
// timeout最小单位ms,并且rounded up to系统时钟的精度
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
// 注意timespec会被转换成ms
int ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *timeout_ts, const sigset_t *sigmask);
- epoll
// timeout最小单位ms,并且rounded up to系统时钟的精度
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
int epoll_pwait(int epfd, struct epoll_event *events,
int maxevents, int timeout,
const sigset_t *sigmask);
eventfd + select/poll/epoll
一个fd可同时负责读接受事件通知和写触发事件通知signaled + select/poll/epoll
借助alarm/setitimer/timer_create等触发的SIGALARM,通过signalfd传递到多路复用中pipe + select/poll/epoll
一端另起线程定时触发,另一端放到多路复用中
分布式系统的时间
扯点其他的东西:-)。时间是个复杂而有意思的东西,在单机上不同处理器不同进程不同线程可以读到同一个系统时钟CLOCK_REALTIME,而且在一定时间范围内t1~t2发生的事件,即使在t1之前,t2之后系统时间与真实时间发生了一定偏移,只要时间戳的相对顺序没乱,那么我们就可以完全确定t1~t2时间戳之间不同线程发生事件的顺序。但是不同机器之间的系统时间总会互相漂移(ntp局域网0.1ms左右,互联网1-50ms左右),导致我们没法直接使用系统时间(google的原子钟也是将一个时间段作为时间点的,只要这个时间段比较小,那么性能应该可以接收),所以需要logic clock以及衍生出来的vector clock或者version number等。
没有全局时钟是分布式系统需要一致性算法的一个重要原因,因为我们没办法根据单机的系统时间戳来判断多台机器之间事件的先后顺序,那么对于一个新的节点,我们要把之前所有的时间atomic broadcast到这个新节点就会出现问题,所以这也是分布式一致性算法(Paxos/Raft/Viewstamp Replication/Zab)解决的一个问题,当然再加上网络的异步,以及无法获知各个节点的全局状态,以及机器crash等各种问题,这些算法往往加入了容错性。