本系列文章的前兩節討論了用于計時的時鐘源:clocksource,以及內核內部時間的一些表示方法,但是對于真實的用戶來說,我們感知的是真實世界的真實時間,也就是所謂的墻上時間,clocksource只能提供一個按給定頻率不停遞增的周期計數,如何把它和真實的墻上時間相關聯?本節的內容正是要討論這一點。
1. ?時間的種類
內核管理著多種時間,它們分別是:
RTC時間
wall time:墻上時間
monotonic time
raw monotonic time
boot time:總啟動時間
RTC時間? 在PC中,RTC時間又叫CMOS時間,它通常由一個專門的計時硬件來實現,軟件可以讀取該硬件來獲得年月日、時分秒等時間信息,而在嵌入式系統中,有使用專門的RTC芯片,也有直接把RTC集成到Soc芯片中,讀取Soc中的某個寄存器即可獲取當前時間信息。一般來說,RTC是一種可持續計時的,也就是說,不管系統是否上電,RTC中的時間信息都不會丟失,計時會一直持續進行,硬件上通常使用一個后備電池對RTC硬件進行單獨的供電。因為RTC硬件的多樣性,開發者需要為每種RTC時鐘硬件提供相應的驅動程序,內核和用戶空間通過驅動程序訪問RTC硬件來獲取或設置時間信息。
xtime? xtime和RTC時間一樣,都是人們日常所使用的墻上時間,只是RTC時間的精度通常比較低,大多數情況下只能達到毫秒級別的精度,如果是使用外部的RTC芯片,訪問速度也比較慢,為此,內核維護了另外一個wall time時間:xtime,取決于用于對xtime計時的clocksource,它的精度甚至可以達到納秒級別,因為xtime實際上是一個內存中的變量,它的訪問速度非??欤瑑群舜蟛糠謺r間都是使用xtime來獲得當前時間信息。xtime記錄的是自1970年1月1日24時到當前時刻所經歷的納秒數。
monotonic time? 該時間自系統開機后就一直單調地增加,它不像xtime可以因用戶的調整時間而產生跳變,不過該時間不計算系統休眠的時間,也就是說,系統休眠時,monotoic時間不會遞增。
raw monotonic time? 該時間與monotonic時間類似,也是單調遞增的時間,唯一的不同是:raw monotonic time“更純凈”,他不會受到NTP時間調整的影響,它代表著系統獨立時鐘硬件對時間的統計。
boot time? 與monotonic時間相同,不過會累加上系統休眠的時間,它代表著系統上電后的總時間。
時間種類精度(統計單位)訪問速度累計休眠時間受NTP調整的影響RTC低慢YesYesxtime高快YesYesmonotonic高快NoYesraw monotonic高快NoNoboot time高快YesYes
2. ?struct timekeeper
內核用timekeeper結構來組織與時間相關的數據,它的定義如下:
[cpp]?view plain?copy
struct?timekeeper?{??
struct?clocksource?*clock;????/*?Current?clocksource?used?for?timekeeping.?*/??
u32?mult;????/*?NTP?adjusted?clock?multiplier?*/??
int?shift;??/*?The?shift?value?of?the?current?clocksource.?*/??
cycle_t?cycle_interval;?/*?Number?of?clock?cycles?in?one?NTP?interval.?*/??
u64?xtime_interval;?/*?Number?of?clock?shifted?nano?seconds?in?one?NTP?interval.?*/??
s64?xtime_remainder;????/*?shifted?nano?seconds?left?over?when?rounding?cycle_interval?*/??
u32?raw_interval;???/*?Raw?nano?seconds?accumulated?per?NTP?interval.?*/??
u64?xtime_nsec;?/*?Clock?shifted?nano?seconds?remainder?not?stored?in?xtime.tv_nsec.?*/??
/*?Difference?between?accumulated?time?and?NTP?time?in?ntp?
*?shifted?nano?seconds.?*/??
s64?ntp_error;??
/*?Shift?conversion?between?clock?shifted?nano?seconds?and?
*?ntp?shifted?nano?seconds.?*/??
int?ntp_error_shift;??
struct?timespec?xtime;??/*?The?current?time?*/??
struct?timespec?wall_to_monotonic;??
struct?timespec?total_sleep_time;???/*?time?spent?in?suspend?*/??
struct?timespec?raw_time;???/*?The?raw?monotonic?time?for?the?CLOCK_MONOTONIC_RAW?posix?clock.?*/??
ktime_t?offs_real;??/*?Offset?clock?monotonic?->?clock?realtime?*/??
ktime_t?offs_boot;??/*?Offset?clock?monotonic?->?clock?boottime?*/??
seqlock_t?lock;?/*?Seqlock?for?all?timekeeper?values?*/??
};??
其中的xtime字段就是上面所說的墻上時間,它是一個timespec結構的變量,它記錄了自1970年1月1日以來所經過的時間,因為是timespec結構,所以它的精度可以達到納秒級,當然那要取決于系統的硬件是否支持這一精度。
內核除了用xtime表示墻上的真實時間外,還維護了另外一個時間:monotonic time,可以把它理解為自系統啟動以來所經過的時間,該時間只能單調遞增,可以理解為xtime雖然正常情況下也是遞增的,但是畢竟用戶可以主動向前或向后調整墻上時間,從而修改xtime值。但是monotonic時間不可以往后退,系統啟動后只能不斷遞增。奇怪的是,內核并沒有直接定義一個這樣的變量來記錄monotonic時間,而是定義了一個變量wall_to_monotonic,記錄了墻上時間和monotonic時間之間的偏移量,當需要獲得monotonic時間時,把xtime和wall_to_monotonic相加即可,因為默認啟動時monotonic時間為0,所以實際上wall_to_monotonic的值是一個負數,它和xtime同一時間被初始化,請參考timekeeping_init函數。
計算monotonic時間要去除系統休眠期間花費的時間,內核用total_sleep_time記錄休眠的時間,每次休眠醒來后重新累加該時間,并調整wall_to_monotonic的值,使其在系統休眠醒來后,monotonic時間不會發生跳變。因為wall_to_monotonic值被調整。所以如果想獲取boot time,需要加入該變量的值:
[cpp]?view plain?copy
void?get_monotonic_boottime(struct?timespec?*ts)??
{??
......??
do?{??
seq?=?read_seqbegin(&timekeeper.lock);??
*ts?=?timekeeper.xtime;??
tomono?=?timekeeper.wall_to_monotonic;??
"color:#ff0000;">sleep?=?timekeeper.total_sleep_time;??
nsecs?=?timekeeping_get_ns();??
}?while?(read_seqretry(&timekeeper.lock,?seq));??
set_normalized_timespec(ts,?ts->tv_sec?+?tomono.tv_sec?+?sleep.tv_sec,??
ts->tv_nsec?+?tomono.tv_nsec?+?sleep.tv_nsec?+?nsecs);??
}??
raw_time字段用來表示真正的硬件時間,也就是上面所說的raw monotonic time,它不受時間調整的影響,monotonic時間雖然也不受settimeofday的影響,但會受到ntp調整的影響,但是raw_time不受ntp的影響,他真的就是開完機后就單調地遞增。xtime、monotonic-time和raw_time可以通過用戶空間的clock_gettime函數獲得,對應的ID參數分別是 CLOCK_REALTIME、CLOCK_MONOTONIC、CLOCK_MONOTONIC_RAW。
clock字段則指向了目前timekeeper所使用的時鐘源,xtime,monotonic time和raw time都是基于該時鐘源進行計時操作,當有新的精度更高的時鐘源被注冊時,通過timekeeping_notify函數,change_clocksource函數將會被調用,timekeeper.clock字段將會被更新,指向新的clocksource。
早期的內核版本中,xtime、wall_to_monotonic、raw_time其實是定義為全局靜態變量,到我目前的版本(V3.4.10),這幾個變量被移入到了timekeeper結構中,現在只需維護一個timekeeper全局靜態變量即可:
[cpp]?view plain?copy
static?struct?timekeeper?timekeeper;??
3. ?timekeeper的初始化
timekeeper的初始化由timekeeping_init完成,該函數在start_kernel的初始化序列中被調用,timekeeping_init首先從RTC中獲取當前時間:
[cpp]?view plain?copy
void?__init?timekeeping_init(void)??
{??
struct?clocksource?*clock;??
unsigned?long?flags;??
struct?timespec?now,?boot;??
read_persistent_clock(&now);??
read_boot_clock(&boot);??
然后對鎖和ntp進行必要的初始化:
[cpp]?view plain?copy
seqlock_init(&timekeeper.lock);??
ntp_init();??
接著獲取默認的clocksource,如果平臺沒有重新實現clocksource_default_clock函數,默認的clocksource就是基于jiffies的clocksource_jiffies,然后通過timekeeper_setup_inernals內部函數把timekeeper和clocksource進行關聯:
[cpp]?view plain?copy
write_seqlock_irqsave(&timekeeper.lock,?flags);??
clock?=?clocksource_default_clock();??
if?(clock->enable)??
clock->enable(clock);??
timekeeper_setup_internals(clock);??
利用RTC的當前時間,初始化xtime,raw_time,wall_to_monotonic等字段:
[cpp]?view plain?copy
timekeeper.xtime.tv_sec?=?now.tv_sec;??
timekeeper.xtime.tv_nsec?=?now.tv_nsec;??
timekeeper.raw_time.tv_sec?=?0;??
timekeeper.raw_time.tv_nsec?=?0;??
if?(boot.tv_sec?==?0?&&?boot.tv_nsec?==?0)?{??
boot.tv_sec?=?timekeeper.xtime.tv_sec;??
boot.tv_nsec?=?timekeeper.xtime.tv_nsec;??
}??
set_normalized_timespec(&timekeeper.wall_to_monotonic,??
-boot.tv_sec,?-boot.tv_nsec);??
最后,初始化代表實時時間和monotonic時間之間偏移量的offs_real字段,total_sleep_time字段初始化為0:
[cpp]?view plain?copy
update_rt_offset();??
timekeeper.total_sleep_time.tv_sec?=?0;??
timekeeper.total_sleep_time.tv_nsec?=?0;??
write_sequnlock_irqrestore(&timekeeper.lock,?flags);??
[cpp]?view plain?copy
}??
xtime字段因為是保存在內存中,系統掉電后無法保存時間信息,所以每次啟動時都要通過timekeeping_init從RTC中同步正確的時間信息。其中,read_persistent_clock和read_boot_clock是平臺級的函數,分別用于獲取RTC硬件時間和啟動時的時間,不過值得注意到是,到目前為止(我的代碼樹基于3.4版本),ARM體系中,只有tegra和omap平臺實現了read_persistent_clock函數。如果平臺沒有實現該函數,內核提供了一個默認的實現:
[cpp]?view plain?copy
void?__attribute__((weak))?read_persistent_clock(struct?timespec?*ts)??
{??
ts->tv_sec?=?0;??
ts->tv_nsec?=?0;??
}??
[cpp]?view plain?copy
void?__attribute__((weak))?read_boot_clock(struct?timespec?*ts)??
{??
ts->tv_sec?=?0;??
ts->tv_nsec?=?0;??
}??
那么,其他ARM平臺是如何初始化xtime的?答案就是CONFIG_RTC_HCTOSYS這個內核配置項,打開該配置后,driver/rtc/hctosys.c將會編譯到系統中,由rtc_hctosys函數通過do_settimeofday在系統初始化時完成xtime變量的初始化:
[cpp]?view plain?copy
static?int?__init?rtc_hctosys(void)???
{???
......???
err?=?rtc_read_time(rtc,?&tm);???
......??
rtc_tm_to_time(&tm,?&tv.tv_sec);???
do_settimeofday(&tv);???
......???
return?err;???
}???
late_initcall(rtc_hctosys);??
4. ?時間的更新
xtime一旦初始化完成后,timekeeper就開始獨立于RTC,利用自身關聯的clocksource進行時間的更新操作,根據內核的配置項的不同,更新時間的操作發生的頻度也不盡相同,如果沒有配置NO_HZ選項,通常每個tick的定時中斷周期,do_timer會被調用一次,相反,如果配置了NO_HZ選項,可能會在好幾個tick后,do_timer才會被調用一次,當然傳入的參數是本次更新離上一次更新時相隔了多少個tick周期,系統會保證在clocksource的max_idle_ns時間內調用do_timer,以防止clocksource的溢出:
?
[cpp]?view plain?copy
void?do_timer(unsigned?long?ticks)??
{??
jiffies_64?+=?ticks;??
update_wall_time();??
calc_global_load(ticks);??
}??
在do_timer中,jiffies_64變量被相應地累加,然后在update_wall_time中完成xtime等時間的更新操作,更新時間的核心操作就是讀取關聯clocksource的計數值,累加到xtime等字段中,其中還設計ntp時間的調整等代碼,詳細的代碼就不貼了。
5. ?獲取時間
timekeeper提供了一系列的接口用于獲取各種時間信息。
void getboottime(struct timespec *ts); ? ?獲取系統啟動時刻的實時時間
void get_monotonic_boottime(struct timespec *ts);??? ?獲取系統啟動以來所經過的時間,包含休眠時間
ktime_t ktime_get_boottime(void);? ?獲取系統啟動以來所經過的c時間,包含休眠時間,返回ktime類型
ktime_t ktime_get(void); ? ?獲取系統啟動以來所經過的c時間,不包含休眠時間,返回ktime類型
void ktime_get_ts(struct timespec *ts) ; ??獲取系統啟動以來所經過的c時間,不包含休眠時間,返回timespec結構
unsigned long get_seconds(void); ? ?返回xtime中的秒計數值
struct timespec current_kernel_time(void); ? ?返回內核最后一次更新的xtime時間,不累計最后一次更新至今clocksource的計數值
void getnstimeofday(struct timespec *ts); ? ?獲取當前時間,返回timespec結構
void do_gettimeofday(struct timeval *tv); ? ?獲取當前時間,返回timeval結構
?
評論
查看更多