概述
Linux内核之时间系统
- 1、Linux时间系统
- (1)CMOS时钟
- (2)系统时钟
- (3)节拍数(jiffies)
- (4)墙上时间(xtime)
- 2、重要数据结构
- (1)struct tk_read_base
- (2)struct timekeeper
- (3)内核中表示时间的数据结构
- 3、Linux常用的关于时间的命令
1、Linux时间系统
Linux系统中有两个时钟源,一个叫做RTC,另一个叫做系统时钟。时钟运作机制如下图:
(1)CMOS时钟
RTC(Real Time Clock,实时时钟)也叫做CMOS时钟,它独立于操作系统,由PC主板上的一块纽扣电池供电,当操作系统关机的时候,用它来记录时间,当系统启动时,内核通过读取RTC来初始化墙上时间,它为计算机提供一个计时标准,是最原始最底层的时钟数据。(后面介绍墙上时间)
(2)系统时钟
系统时钟是由操作系统控制PC主板上的定时/计数芯片来工作的,它依赖CMOS时钟而启动,初始化后由操作系统完全管理,操作系统通过OS时钟提供给应用程序所有和时间有关的服务。系统时钟产生于PC主板上的定时/计数芯片,常见的有8253/8254可编程定时/计数芯片,其工作原理是由晶振、电容等组成的振荡电路,产生脉冲(高低电平),这些脉冲输入到中断控制器上,产生中断信号,触发时钟中断,由时钟中断服务程序维持OS时钟正常工作。
系统时钟记录的时间也就是我们常见的系统时间,它是以“时钟节拍”为单位的,时钟中断的频率(节拍率)决定了一个时钟节拍的长短。节拍率是通过静态预处理定义的,也就是Hz(赫兹),Linux内核版本4.19中是这样定义的:
#ifndef __ASM_GENERIC_PARAM_H
#define __ASM_GENERIC_PARAM_H
#include <uapi/asm-generic/param.h>
# 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)
(3)节拍数(jiffies)
jiffies是Linux内核中的一个全局变量,用来记录自系统启动以来产生的节拍总数。启动时内核将该变量初始化为0,此后每次时钟中断jiffies的值+1,每一秒钟中断次数HZ,jiffies一秒内增加HZ。
- 将秒转换为jiffies可采用公式:seconds*HZ
- jiffies转换为秒可采用公式:jiffies/HZ
- 系统运行时间(s) = 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,然后才执行实际的比较。
(4)墙上时间(xtime)
墙上时间就是实际时间,实际时间的获取是在开机后,内核初始化时从RTC读取的。内核读取这个时间后就将其放入内核中的 xtime 变量中,并且在系统的运行中不断更新这个值。内核中并不常用墙上时间,主要是方便用户空间的程序获取当前时间。它的精度可以达到纳秒级别,因为xtime实际上是一个内存中的变量,它的访问速度非常快,内核大部分时间都是使用xtime来获得当前时间信息。Linux中的xtime记录的是自1970年1月1日0时到当前时刻所经历的纳秒数。
2、重要数据结构
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.19includelinuxtimekeeper_internal.h
下有这样两个数据结构:
(1)struct tk_read_base
读取时间的基本结构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:timekeeping当前使用的时钟源
这个clock应该是系统中最优的那个,如果有好过当前clocksource注册入系统,那么clocksource模块会通知timekeeping模块来切换clocksource。
- mask:非64位时钟的二进制补码减法的位掩码
- cycle_last:最后一次更新的时钟周期值
- mult :(经过NTP调整)数学换算的乘数
- shift:数学换算的移位值
- xtime_nsec:用于读出纳秒的偏移量
- base:用于读取ktime_t(纳秒)的基本时间
- base_real:用于读出实际时间的基本纳秒值
- base_real:用于快速NMI安全访问器,以允许读取时钟
此结构在64位上的大小为56字节,连同一个seqcount占用64字节缓存。
这个结构体与timekeeper结构是分开的,因为它也被使用用于快速NMI安全访问器。
base_real用于快速NMI安全访问器以允许从任何上下文读取实际时间。
(2)struct timekeeper
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
};
其中:
- tkr_mono:CLOCK_MONOTONIC的读出基本结构
- tkr_raw:CLOCK_MONOTONIC_RAW的读出基本结构
- xtime_sec:当前的CLOCK_REALTIME时间,以秒为单位
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)距离的那个时间点值。
- ktime_sec:当前的CLOCK_MONOTONIC时间,以秒为单位
- wall_to_monotonic:从CLOCK_REALTIME到CLOCK_MONOTONIC偏移
CLOCK_MONOTONIC类型的系统时钟。这种系统时钟并没有像墙上时钟一样定义一个相对于linux epoch的值,这个成员定义了monotonic clock到real time clock的偏移,也就是说,这里的wall_to_monotonic和offs_real需要加上real time clock的时间值才能得到monotonic clock的时间值。wall_to_monotonic和offs_real的意思是一样的,不过时间的格式不一样,用在不同的场合,以便获取性能的提升。
- offs_real:偏移时钟单调->时钟实时
- offs_boot:偏移时钟单调->时钟启动时间
- offs_tai:偏移时钟单调->时钟tai
- tai_offset:当前UTC到TAI的偏移量,以秒为单位
CLOCK_TAI类型的系统时钟。TAI(international atomic time)是原子钟,在时间的基本概念文档中,UTC就是base TAI的,也就是说用铯133的振荡频率来定义秒的那个时钟,CLOCK_TAI类型的系统时钟就是完完全全使用铯133的振荡频率来定义秒的那个时钟。
- clock_was_set_seq:时钟被设置事件的序号
- cs_was_changed_seq:时钟源更改事件的序列号
- next_leap_ktime:待处理的leap秒的CLOCK_MONOTONIC时间值
- raw_sec:CLOCK_MONOTONIC_RAW时间以秒为单位
- cycle_interval:一个NTP间隔中的时钟周期数
- xtime_interval:一个NTP中的时钟移位纳秒数间隔
- xtime_remainder:四舍五入时还剩纳秒
- cycle_interval:一个NTP间隔中的时钟周期数
- raw_interval:每个NTP间隔累积的偏移原始毫微秒
- ntp_error:ntp中的累积时间和NTP时间之间的差偏移了纳秒
- ntp_error_shift:时钟移位纳秒和ntp移位纳秒之间的转换
- last_warning:警告速率限制器(DEBUG_TIMEKEEPING)
- underflow_seen:下溢警告标志(DEBUG_TIMEKEEPING)
- overflow_seen:溢出警告标志(DEBUG_TIMEKEEPING)
(3)内核中表示时间的数据结构
在内核版本4.19linux-4.19includeuapilinuxtime.h
中有这样几个表示时间的数据结构,源码如下:
#ifndef _STRUCT_TIMESPEC
#define _STRUCT_TIMESPEC
struct 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()等。
3、Linux常用的关于时间的命令
命令如下,结果如图:
(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
来查看。。。
最后
以上就是光亮面包为你收集整理的Linux内核之时间系统1、Linux时间系统2、重要数据结构3、Linux常用的关于时间的命令的全部内容,希望文章能够帮你解决Linux内核之时间系统1、Linux时间系统2、重要数据结构3、Linux常用的关于时间的命令所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复