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.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_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來查看。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章