在進行重要的時間運算的時候,比如自己實現定時器,不能夠使用time/gettimeofday,建議使用TSC或jiffies。
1 問題
1.1現象1
應用(ePDG)中有許多的定時器,這些定時器通過一個隊列和一個線程進行管理。定時器加入到隊列時,用time/gettimeofday獲取當前時間(記爲timer_start),加上用戶傳入的延時參數(delay)會,得到定時器應該被觸發的時間(timer_start+delay)。線程每隔10ms,用time/gettimeofday獲取當前時間(curr_time),檢查隊列上的定時器的觸發時間是否小於當前時間,即滿足如下條件的定時器應該被觸發
(timer_start+delay)<curr_time
在系統運行過程中,用date命令修改OS時間後,發現定時器不會在指定的時間內被觸發。
1.2 現象2
應用(ePDG)會計算一個用戶在線時長。計算用戶在線時長非常簡單,用戶上線時調用time()函數獲取當前時間(logon_time),用戶下線是調用time()函數獲取當前時間(logout_time),logout_time-logon_time即用戶的在線時長。
用戶上線以後,用date命令修改OS時間後,發現用戶的在線時長明顯與實際不符。
2 分析
由於time/gettimeofday函數獲取的是OS wall時間,即我們看到的系統時間。如果修改wall 時間,time/gettimeofday函數獲取到的時間是會隨之變化的。所以,在不同的時間點調用time/gettimeofday函數獲得當前時間,然後相減獲得時間間隔的方法是不對的。
3 解決方案
使用TSC或jiffies來計算時間間隔。
4 關於時間
4.1 實際時間/wall時間
實際時間(牆上時間)定義在文件kernel/timer.c中:
struct timespec xtime;
timespec數據結構定義在文件<linux/time.h>中,形式如下:
structtimespec{
time_t tv_sec; /* 秒 */
longtv_nsec; /* 納秒 */
};
其中,xtime.tv_sec以秒爲單位,存放着自1970年7月1日以來經過的時間。xtime.tv_nsec記錄了自上一秒開始經過的納秒數。xtime是用過time interrupt來更新的。從用戶空間取得牆上時間的主要接口是gettimeofday/time。
實時時鐘(RTC)是用來持久存放系統時間的設備,即便系統關閉後,它可以靠主板上的微型電池提供的電力保持系統的計時。系統啓動時,內核通過讀取RTC來初始化WallTime,並存放在xtime變量中,這是RTC最主要的作用。我們可以通過date命令修改實時時鐘。
4.2 jiffies
HZ
Linux核心每隔固定週期會發出timerinterrupt (IRQ 0),即每隔固定時間間隔調用一次時間中斷。HZ是用來定義每一秒有幾次timer interrupts。可以通過/proc/HZ查看HZ的值。
TICK
Tick是HZ的倒數,意即timerinterrupt每發生一次中斷的時間。如HZ爲250時,tick爲4毫秒(millisecond)。
jiffies
在<linux/jiffies.h>,定義了Jiffies爲Linux核心變數(32位元變數,unsignedlong),它被用來紀錄系統自開機以來,已經過多少的tick,在linux內核中jiffies遠比xtime重要。每發生一次timer interrupt,Jiffies變數會被加一。由於時間中斷是通過底層的硬件實現的,所以通過jiffies獲取時間間隔不會隨着wall時間改變而改變。
4.3 TSC
TSC(time stamp counter)記錄自啓動以來處理器消耗的時鐘週期數,它是intel CPU提供的一個計數器。它在每個時鐘週期到來時,該計數器自動加一。因爲 TSC 隨着處理器週期速率的變化而變化,所以它提供了非常高的精確度。它經常被用來分析和檢測代碼。TSC 的值可以通過 rdtsc 指令來讀取。TSC 的節拍還可以轉換爲秒,轉換方法是將其除以 CPU 的時鐘速率(可以從內核變量 cpu_khz 獲取)。