博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux内核之时间系统
阅读量:3784 次
发布时间:2019-05-22

本文共 6674 字,大约阅读时间需要 22 分钟。

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 
# 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.19\include\linux\timekeeper_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.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()等。

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来查看。。。

转载地址:http://fvutn.baihongyu.com/

你可能感兴趣的文章
简述一下HTTP的状态码
查看>>
20210227vulhub靶场之环境配置---无法获得靶机IP的疑难解决方式(可以解决VBox和VMware不兼容问题)
查看>>
20210226web渗透学习之SSRF总结
查看>>
2021-06-01web渗透学习之sqlserver提权(转)
查看>>
大数据之Flume
查看>>
关于高可用配置hbase中出现的问题:Name or service not known
查看>>
centOs7下hadoop3.2.2namenode故障不自动转移
查看>>
在高可用的hive下执行bin/schematool -dbType mysql -initSchema报错
查看>>
hbase配置高可用
查看>>
linux下卸载和安装mysql
查看>>
在初始化namenode时:java.net.NoRouteToHostException: 没有到主机的路由;
查看>>
hive-hbase
查看>>
浅谈scala-API的基础概念及简单例子
查看>>
spark的历史服务器配置
查看>>
spark的API操作
查看>>
SparkSql
查看>>
SparkRdd-scala版本
查看>>
spark常见算子
查看>>
scala符号初体验
查看>>
kafka生产者常用参数含义
查看>>