歡迎您光臨本站 註冊首頁

Linux時間子系統之時間的表示示例詳解

←手機掃碼閱讀     zmcjlove @ 2020-05-13 , reply:0

前言
在Linux內核中,為了兼容原有的代碼,或者符合某種規範,並且還要滿足當前精度日益提高的要求,實現了多種與時間相關但用於不同目的的數據結構:
1)jiffies和jiffies_64
內核用jiffies_64全局變量記錄系統自啟動以來經過了多少次Tick。它的聲明如下(代碼位於kernel/time/timer.c中):
__visible u64 jiffies_64 __cacheline_aligned_in_smp = INITIAL_JIFFIES; EXPORT_SYMBOL(jiffies_64);
可以看出來jiffies_64被定義成了64位無符號整數。但是,由於歷史的原因,內核源代碼中還包含了另一個叫做jiffies的變量。jiffies的引用(代碼位於include/linux/jiffies.h中)申明為:
extern u64 __cacheline_aligned_in_smp jiffies_64; extern unsigned long volatile __cacheline_aligned_in_smp __jiffy_arch_data jiffies;
因此,jiffies變量是一個unsigned long類型的全局變量,如果在32位處理器上只有4個字節長(32位)。但是,如果在64位處理器上也有8個字節長(64位),這時候jiffies和jiffies_64兩個全局變量是完全等價的。
但是翻遍所有代碼你也找不到全局變量jiffies的定義,最終在內核的鏈接腳本中(對於Arm64架構來說腳本位於arch/arm64/kernel/vmlinux.lds.S中)找到了下面這行:
jiffies = jiffies_64;
玄機在這裡,原來在鏈接的時候指定了符號jiffies和jiffies_64指向同一個地址。也就是說,在32位機器上,jiffies和jiffies_64的低4個字節是一樣的。
一般情況下,無論在32位或64位機器上,我們都可以直接訪問jiffies全局變量,但如果要獲得jiffies_64全局變量,則需要調用get_jiffies_64函數。對於64位系統來說,兩者一樣,而且jiffies被申明成了volatile的且是Cache對齊的,因此只需要直接返回jiffies就好了:
static inline u64 get_jiffies_64(void) { return (u64)jiffies; }
而對於32位系統來說,由於其對64位讀寫不是原子的,所以還需要持有jiffies_lock讀順序鎖:
u64 get_jiffies_64(void) { unsigned int seq; u64 ret; do { seq = read_seqbegin(&jiffies_lock); ret = jiffies_64; } while (read_seqretry(&jiffies_lock, seq)); return ret; }
jiffies基本上是每一次Tick到來都會加1的,而Tick的週期HZ是由內核編譯選項配置的。在32位系統中,我們假設HZ被設置成了250,那麼每個Tick的週期就是4毫秒,那麼該計數器將在不到200天后達到最大值後溢出。如果HZ被設置的更高,那這個溢出時間會更短。當然,如果在64位系統中,則完全不用考慮這個問題。因此,在用jiffies進行時間比較的時候,需要用系統已經定義好的幾個宏:
time_after(a,b) time_before(a,b) time_after_eq(a,b) time_before_eq(a,b) time_in_range_open(a,b,c) time_is_before_jiffies(a) time_is_after_jiffies(a) time_is_before_eq_jiffies(a) time_is_after_eq_jiffies(a)
為了保險起見,內核也提供了對應的64位版本。這些宏可以有效的解決迴繞問題,不過也不是無限制的。具體是怎麼做到的呢?我們挑一個time_after宏來看看就知道了:
#define time_after(a,b)  (typecheck(unsigned long, a) &&  typecheck(unsigned long, b) &&  ((long)((b) - (a)) < 0))
先是對兩個變量做類型檢查,必須都是unsigned long型的。最重要的是後面,先將兩個無符號長整形相減,然後將他們變成有符號的長整型,再判斷其是否為負數,也就是32位的最高位是否為1。
為什麼這樣可以部分解決所謂迴繞的問題呢?我們可以舉個例子,為了簡單起見,以8位無符號整數為例,其取值範圍是0到255(0xFF)。假設當前時間是250,那麼過5個Tick之後,就是255了,已經到達了能表達的最大值。這時,如果再過一個Tick,也就是6個Tick之後,就將會溢出變成0了。此時,如果簡單的通過對兩個值的比較來判斷哪個時間再後面的話,顯然就要出錯了,因為過了6個Tick之後的時間是0,反而小於當前的時間,這個問題就是所謂的迴繞。但是,如果我們先將這兩個數相減,也就是0-250(0-0xFA),也會產生溢出,最終得到的數剛好是6。但這也是有限制的,兩個比較的時間之間的差值不能超過最大表示範圍的一半。假設現在的時間還是250,而過了128個Tick之後,時間值將變成122,再將兩者相減的話就是122-250(0x86-0xFA),減出來的數字就是128了,此時轉成有符號數就變成負數了,結果就錯了。
另外,jiffies是每個Tick更新一次的,而Tick的週期又是編譯的時候定義好的,所以可以將jiffies的數值轉換成具體過了多少時間,反之亦然。因此,內核提供瞭如下轉換函數:
unsigned int jiffies_to_msecs(const unsigned long j); unsigned int jiffies_to_usecs(const unsigned long j); unsigned long msecs_to_jiffies(const unsigned int m); unsigned long usecs_to_jiffies(const unsigned int u);
2)timespec和timespec64
timespec由秒和納秒組成,其定義如下(代碼位於include/uapi/linux/time.h):
struct timespec { __kernel_time_t tv_sec; long tv_nsec; };
tv_sec:存放自1970年1月1日0時(UTC時間)以來經過的秒數。__kernel_time_t最終定義成了long型,也就是在32位系統上是32位長,而在64位系統上是64位長。
tv_nsec:存放自上一秒開始經過的納秒(ns)數。
timespec還有一個64位的擴展結構,其定義如下(代碼位於include/linux/time64.h):
typedef __s64 time64_t; ...... struct timespec64 { time64_t tv_sec; long tv_nsec; };
這個結構體中的變量定義和timespec一樣,只不過tv_sec的類型一定是64位無符號數。所以,也就是說在64位系統上,timespec和timespec64結構體是一模一樣的。
3)ktime_t
在Linux的時間子系統內,一般使用ktime_t來表示時間,其定義如下(代碼位於include/linux/ktime.h):
typedef s64 ktime_t;
就是一個非常簡單的64位帶符號整數,表示的時間單位是納秒。
4)timeval
gettimeofday和settimeofday函數使用timeval作為時間單位:
struct timeval { __kernel_time_t tv_sec; __kernel_suseconds_t tv_usec; };
tv_sec:存放自1970年1月1日0時(UTC時間)以來經過的秒數。__kernel_time_t最終定義成了long型,也就是在32位系統上是32位長,而在64位系統上是64位長。
tv_usec:__kernel_suseconds_t實際最終也被定義成了long型,存放自上一秒開始經過的微秒(us)數。
所以,這個結構體其實和timespec結構體大同小異,tv_sec存的值是一樣的,而只需要將timespec中的tv_nsec除以1000就是timeval中的tv_usec。


[zmcjlove ] Linux時間子系統之時間的表示示例詳解已經有229次圍觀

http://coctec.com/docs/linux/show-post-234289.html