本文共 6674 字,大约阅读时间需要 22 分钟。
Linux系统中有两个时钟源,一个叫做RTC,另一个叫做系统时钟。时钟运作机制如下图:
RTC(Real Time Clock,实时时钟)也叫做CMOS时钟,它独立于操作系统,由PC主板上的一块纽扣电池供电,当操作系统关机的时候,用它来记录时间,当系统启动时,内核通过读取RTC来初始化墙上时间,它为计算机提供一个计时标准,是最原始最底层的时钟数据。(后面介绍墙上时间)
系统时钟是由操作系统控制PC主板上的定时/计数芯片来工作的,它依赖CMOS时钟而启动,初始化后由操作系统完全管理,操作系统通过OS时钟提供给应用程序所有和时间有关的服务。系统时钟产生于PC主板上的定时/计数芯片,常见的有8253/8254可编程定时/计数芯片,其工作原理是由晶振、电容等组成的振荡电路,产生脉冲(高低电平),这些脉冲输入到中断控制器上,产生中断信号,触发时钟中断,由时钟中断服务程序维持OS时钟正常工作。
系统时钟记录的时间也就是我们常见的系统时间,它是以“时钟节拍”为单位的,时钟中断的频率(节拍率)决定了一个时钟节拍的长短。节拍率是通过静态预处理定义的,也就是Hz(赫兹),Linux内核版本4.19中是这样定义的:#ifndef __ASM_GENERIC_PARAM_H#define __ASM_GENERIC_PARAM_H#include# undef HZ# define HZ CONFIG_HZ /* Internal kernel timer frequency */# define USER_HZ 100 /* some user interfaces are */# define CLOCKS_PER_SEC (USER_HZ) /* in "ticks" like times() */#endif /* __ASM_GENERIC_PARAM_H */
一个tick代表多长时间,在内核的CONFIG_HZ中定义。比如CONFIG_HZ=200,则一个jiffies对应5ms时间,所以内核基于jiffies的定时器精度也是5ms。(下面介绍jiffies)
jiffies是Linux内核中的一个全局变量,用来记录自系统启动以来产生的节拍总数。启动时内核将该变量初始化为0,此后每次时钟中断jiffies的值+1,每一秒钟中断次数HZ,jiffies一秒内增加HZ。
jiffies用途:计算流逝时间和时间管理。
jiffies 变量总是无符号长整数(Unsigned Long)。因此,在32位体系结构上是32位,在64位体系结构是64位,当 jiffies 的值超过它的最大存放范围后就会发生溢出,它的值会回绕到0。内核提供了这样的宏来帮助解决由于jiffies溢出而造成程序逻辑出错的情况:#define time_after(a,b) \ (typecheck(unsigned long, a) && \ typecheck(unsigned long, b) && \ ((long)((b) - (a)) < 0))#define time_before(a,b) time_after(b,a)#define time_after_eq(a,b) \ (typecheck(unsigned long, a) && \ typecheck(unsigned long, b) && \ ((long)((a) - (b)) >= 0))#define time_before_eq(a,b) time_after_eq(b,a)
在宏time_after中,首先确保两个输入参数a和b的数据类型为unsigned long,然后才执行实际的比较。
墙上时间就是实际时间,实际时间的获取是在开机后,内核初始化时从RTC读取的。内核读取这个时间后就将其放入内核中的 xtime 变量中,并且在系统的运行中不断更新这个值。内核中并不常用墙上时间,主要是方便用户空间的程序获取当前时间。它的精度可以达到纳秒级别,因为xtime实际上是一个内存中的变量,它的访问速度非常快,内核大部分时间都是使用xtime来获得当前时间信息。Linux中的xtime记录的是自1970年1月1日0时到当前时刻所经历的纳秒数。
Linux内核提供各种time line,real time clock,monotonic clock、monotonic raw clock等,timekeeping模块就是负责跟踪、维护这些timeline的,并且向其他模块(timer相关模块、用户空间的时间服务等)提供服务,而timekeeping模块维护timeline的基础是基于clocksource模块和tick模块。通过tick模块的tick事件,可以周期性的更新time line,通过clocksource模块、可以获取tick之间更精准的时间信息。
Linux内核版本4.19中在linux-4.19\include\linux\timekeeper_internal.h
下有这样两个数据结构: 读取时间的基本结构tk_read_base,内核版本4.19源码如下:
struct tk_read_base { struct clocksource *clock; u64 mask; u64 cycle_last; u32 mult; u32 shift; u64 xtime_nsec; ktime_t base; u64 base_real;};
其中:
这个clock应该是系统中最优的那个,如果有好过当前clocksource注册入系统,那么clocksource模块会通知timekeeping模块来切换clocksource。
此结构在64位上的大小为56字节,连同一个seqcount占用64字节缓存。
这个结构体与timekeeper结构是分开的,因为它也被使用用于快速NMI安全访问器。 base_real用于快速NMI安全访问器以允许从任何上下文读取实际时间。
timekeeper结构用于保存计时值,内核版本4.19源码如下:
struct timekeeper { struct tk_read_base tkr_mono; struct tk_read_base tkr_raw; u64 xtime_sec; unsigned long ktime_sec; struct timespec64 wall_to_monotonic; ktime_t offs_real; ktime_t offs_boot; ktime_t offs_tai; s32 tai_offset; unsigned int clock_was_set_seq; u8 cs_was_changed_seq; ktime_t next_leap_ktime; u64 raw_sec; /* The following members are for timekeeping internal use */ u64 cycle_interval; u64 xtime_interval; s64 xtime_remainder; u64 raw_interval; /* The ntp_tick_length() value currently being used. * This cached copy ensures we consistently apply the tick * length for an entire tick, as ntp_tick_length may change * mid-tick, and we don't want to apply that new value to * the tick in progress. */ u64 ntp_tick; /* Difference between accumulated time and NTP time in ntp * shifted nano seconds. */ s64 ntp_error; u32 ntp_error_shift; u32 ntp_err_mult; /* Flag used to avoid updating NTP twice with same second */ u32 skip_second_overflow;#ifdef CONFIG_DEBUG_TIMEKEEPING long last_warning; /* * These simple flag variables are managed * without locks, which is racy, but they are * ok since we don't really care about being * super precise about how many events were * seen, just that a problem was observed. */ int underflow_seen; int overflow_seen;#endif};
其中:
Linux中CLOCK_REALTIME time,直接使用秒以及纳秒在当前秒内的偏移来表示。这里xtime_sec用秒这个的刻度单位来度量CLOCK_REALTIME time line上,时间原点到当前点的距离值。当然xtime_sec是一个对current time point的取整值,为了更好的精度,还需要一个纳秒表示的offset,也就是在刚才那个数据结构tk_read_base结构中的xtime_nsec。不过为了内核内部计算精度(内核对时间的计算是基于cycle的),并不是保存了时间的纳秒偏移值,而是保存了一个shift之后的值,因此,用户看来,当前时间点的值应该是距离时间原点xtime_sec + (xtime_nsec << shift)距离的那个时间点值。
CLOCK_MONOTONIC类型的系统时钟。这种系统时钟并没有像墙上时钟一样定义一个相对于linux epoch的值,这个成员定义了monotonic clock到real time clock的偏移,也就是说,这里的wall_to_monotonic和offs_real需要加上real time clock的时间值才能得到monotonic clock的时间值。wall_to_monotonic和offs_real的意思是一样的,不过时间的格式不一样,用在不同的场合,以便获取性能的提升。
CLOCK_TAI类型的系统时钟。TAI(international atomic time)是原子钟,在时间的基本概念文档中,UTC就是base TAI的,也就是说用铯133的振荡频率来定义秒的那个时钟,CLOCK_TAI类型的系统时钟就是完完全全使用铯133的振荡频率来定义秒的那个时钟。
在内核版本4.19linux-4.19\include\uapi\linux\time.h
中有这样几个表示时间的数据结构,源码如下:
#ifndef _STRUCT_TIMESPEC#define _STRUCT_TIMESPECstruct timespec { __kernel_time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */};#endif
该数据结构来自于POSIX.1b规范,用于在用户态和内核态之间传递时间信息。POSIX还定义了许多API供用户态调用,例如clock_gettime(),clock_settime()等,其中的表示时间的结构都是timespec。
struct timeval { __kernel_time_t tv_sec; /* seconds */ __kernel_suseconds_t tv_usec; /* microseconds */};
该结构也用于用户态和内核态间的时间信息传递。不同于timespec的是它的两个成员分别是秒和微妙,精度要比timespec差。另外一个区别是相关API也不同,timeval相关的API是gettimeofday()等。
命令如下,结果如图:
(1)查看时间和日期date(2)设置时间和日期 将日期设置为2019年11月18日
date -s 11/18/2019
将系统时间设定成下午6点16分26秒
date -s 6:16:26(3)同步网络时间
ntpdate -u 0.asia.pool.ntp.org(4)将当前时间和日期写入BIOS,避免重启后失效
hwclock -w
(5)查看月历
call更多关于时间的操作,可以使用命令
man date
来查看。。。 转载地址:http://fvutn.baihongyu.com/