Linux時間子系統之時間的表示

2021-10-05 22:16:45 字數 3564 閱讀 7075

在linux核心中,為了相容原有的**,或者符合某種規範,並且還要滿足當前精度日益提高的要求,實現了多種與時間相關但用於不同目的的資料結構:

核心用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;
一般情況下,無論在32位或64位機器上,我們都可以直接訪問jiffies全域性變數,但如果要獲得jiffies_64全域性變數,則需要呼叫get_jiffies_64函式。對於64位系統來說,兩者一樣,而且jiffies被申明成了volatile的且是cache對齊的,因此只需要直接返回jiffies就好了:

static inline u64 get_jiffies_64(void)

而對於32位系統來說,由於其對64位讀寫不是原子的,所以還需要持有jiffies_lock讀順序鎖:

u64 get_jiffies_64(void)

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);

timespec由秒和納秒組成,其定義如下(**位於include/uapi/linux/time.h):

struct timespec ;
timespec還有乙個64位的擴充套件結構,其定義如下(**位於include/linux/time64.h):

typedef __s64 time64_t;

......

struct timespec64 ;

這個結構體中的變數定義和timespec一樣,只不過tv_sec的型別一定是64位無符號數。所以,也就是說在64位系統上,timespec和timespec64結構體是一模一樣的。

在linux的時間子系統內,一般使用ktime_t來表示時間,其定義如下(**位於include/linux/ktime.h):

typedef s64	ktime_t;
就是乙個非常簡單的64位帶符號整數,表示的時間單位是納秒。

gettimeofday和settimeofday函式使用timeval作為時間單位:

struct timeval ;
所以,這個結構體其實和timespec結構體大同小異,tv_sec存的值是一樣的,而只需要將timespec中的tv_nsec除以1000就是timeval中的tv_usec。

linux時間子系統(九)

3.4.3 模擬tick事件 當系統切換到高精度模式後,tick device被高精度定時器系統接管,不再定期地產生tick事件。核心在3.0.30版本中還沒有徹底的廢除jiffies機制,系統還是依賴定期到來的tick事件,完成程序排程和時間更新等操作,大量存在的低精度定時器仍然依賴於jiffie...

linux時間子系統(六)

3.1.4 定時器處理 static inline void run timers struct tvec base base wake up base wait for running timer spin unlock irq base lock static int cascade struc...

linux時間子系統(三)

2.2.3 timekeeper初始化 void init timekeeping init void set normalized timespec wall to monotonic,boot.tv sec,boot.tv nsec total sleep time.tv sec 0 total...