本系列文章的前兩節(jié)討論了用于計(jì)時(shí)的時(shí)鐘源:clocksource,以及內(nèi)核內(nèi)部時(shí)間的一些表示方法,但是對于真實(shí)的用戶來說,我們感知的是真實(shí)世界的真實(shí)時(shí)間,也就是所謂的墻上時(shí)間,clocksource只能提供一個(gè)按給定頻率不停遞增的周期計(jì)數(shù),如何把它和真實(shí)的墻上時(shí)間相關(guān)聯(lián)?本節(jié)的內(nèi)容正是要討論這一點(diǎn)。
1. ?時(shí)間的種類
內(nèi)核管理著多種時(shí)間,它們分別是:
RTC時(shí)間
wall time:墻上時(shí)間
monotonic time
raw monotonic time
boot time:總啟動時(shí)間
RTC時(shí)間? 在PC中,RTC時(shí)間又叫CMOS時(shí)間,它通常由一個(gè)專門的計(jì)時(shí)硬件來實(shí)現(xiàn),軟件可以讀取該硬件來獲得年月日、時(shí)分秒等時(shí)間信息,而在嵌入式系統(tǒng)中,有使用專門的RTC芯片,也有直接把RTC集成到Soc芯片中,讀取Soc中的某個(gè)寄存器即可獲取當(dāng)前時(shí)間信息。一般來說,RTC是一種可持續(xù)計(jì)時(shí)的,也就是說,不管系統(tǒng)是否上電,RTC中的時(shí)間信息都不會丟失,計(jì)時(shí)會一直持續(xù)進(jìn)行,硬件上通常使用一個(gè)后備電池對RTC硬件進(jìn)行單獨(dú)的供電。因?yàn)镽TC硬件的多樣性,開發(fā)者需要為每種RTC時(shí)鐘硬件提供相應(yīng)的驅(qū)動程序,內(nèi)核和用戶空間通過驅(qū)動程序訪問RTC硬件來獲取或設(shè)置時(shí)間信息。
xtime? xtime和RTC時(shí)間一樣,都是人們?nèi)粘K褂玫膲ι蠒r(shí)間,只是RTC時(shí)間的精度通常比較低,大多數(shù)情況下只能達(dá)到毫秒級別的精度,如果是使用外部的RTC芯片,訪問速度也比較慢,為此,內(nèi)核維護(hù)了另外一個(gè)wall time時(shí)間:xtime,取決于用于對xtime計(jì)時(shí)的clocksource,它的精度甚至可以達(dá)到納秒級別,因?yàn)閤time實(shí)際上是一個(gè)內(nèi)存中的變量,它的訪問速度非常快,內(nèi)核大部分時(shí)間都是使用xtime來獲得當(dāng)前時(shí)間信息。xtime記錄的是自1970年1月1日24時(shí)到當(dāng)前時(shí)刻所經(jīng)歷的納秒數(shù)。
monotonic time? 該時(shí)間自系統(tǒng)開機(jī)后就一直單調(diào)地增加,它不像xtime可以因用戶的調(diào)整時(shí)間而產(chǎn)生跳變,不過該時(shí)間不計(jì)算系統(tǒng)休眠的時(shí)間,也就是說,系統(tǒng)休眠時(shí),monotoic時(shí)間不會遞增。
raw monotonic time? 該時(shí)間與monotonic時(shí)間類似,也是單調(diào)遞增的時(shí)間,唯一的不同是:raw monotonic time“更純凈”,他不會受到NTP時(shí)間調(diào)整的影響,它代表著系統(tǒng)獨(dú)立時(shí)鐘硬件對時(shí)間的統(tǒng)計(jì)。
boot time? 與monotonic時(shí)間相同,不過會累加上系統(tǒng)休眠的時(shí)間,它代表著系統(tǒng)上電后的總時(shí)間。
時(shí)間種類精度(統(tǒng)計(jì)單位)訪問速度累計(jì)休眠時(shí)間受NTP調(diào)整的影響RTC低慢YesYesxtime高快YesYesmonotonic高快NoYesraw monotonic高快NoNoboot time高快YesYes
2. ?struct timekeeper
內(nèi)核用timekeeper結(jié)構(gòu)來組織與時(shí)間相關(guān)的數(shù)據(jù),它的定義如下:
[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字段就是上面所說的墻上時(shí)間,它是一個(gè)timespec結(jié)構(gòu)的變量,它記錄了自1970年1月1日以來所經(jīng)過的時(shí)間,因?yàn)槭莟imespec結(jié)構(gòu),所以它的精度可以達(dá)到納秒級,當(dāng)然那要取決于系統(tǒng)的硬件是否支持這一精度。
內(nèi)核除了用xtime表示墻上的真實(shí)時(shí)間外,還維護(hù)了另外一個(gè)時(shí)間:monotonic time,可以把它理解為自系統(tǒng)啟動以來所經(jīng)過的時(shí)間,該時(shí)間只能單調(diào)遞增,可以理解為xtime雖然正常情況下也是遞增的,但是畢竟用戶可以主動向前或向后調(diào)整墻上時(shí)間,從而修改xtime值。但是monotonic時(shí)間不可以往后退,系統(tǒng)啟動后只能不斷遞增。奇怪的是,內(nèi)核并沒有直接定義一個(gè)這樣的變量來記錄monotonic時(shí)間,而是定義了一個(gè)變量wall_to_monotonic,記錄了墻上時(shí)間和monotonic時(shí)間之間的偏移量,當(dāng)需要獲得monotonic時(shí)間時(shí),把xtime和wall_to_monotonic相加即可,因?yàn)槟J(rèn)啟動時(shí)monotonic時(shí)間為0,所以實(shí)際上wall_to_monotonic的值是一個(gè)負(fù)數(shù),它和xtime同一時(shí)間被初始化,請參考timekeeping_init函數(shù)。
計(jì)算monotonic時(shí)間要去除系統(tǒng)休眠期間花費(fèi)的時(shí)間,內(nèi)核用total_sleep_time記錄休眠的時(shí)間,每次休眠醒來后重新累加該時(shí)間,并調(diào)整wall_to_monotonic的值,使其在系統(tǒng)休眠醒來后,monotonic時(shí)間不會發(fā)生跳變。因?yàn)閣all_to_monotonic值被調(diào)整。所以如果想獲取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字段用來表示真正的硬件時(shí)間,也就是上面所說的raw monotonic time,它不受時(shí)間調(diào)整的影響,monotonic時(shí)間雖然也不受settimeofday的影響,但會受到ntp調(diào)整的影響,但是raw_time不受ntp的影響,他真的就是開完機(jī)后就單調(diào)地遞增。xtime、monotonic-time和raw_time可以通過用戶空間的clock_gettime函數(shù)獲得,對應(yīng)的ID參數(shù)分別是 CLOCK_REALTIME、CLOCK_MONOTONIC、CLOCK_MONOTONIC_RAW。
clock字段則指向了目前timekeeper所使用的時(shí)鐘源,xtime,monotonic time和raw time都是基于該時(shí)鐘源進(jìn)行計(jì)時(shí)操作,當(dāng)有新的精度更高的時(shí)鐘源被注冊時(shí),通過timekeeping_notify函數(shù),change_clocksource函數(shù)將會被調(diào)用,timekeeper.clock字段將會被更新,指向新的clocksource。
早期的內(nèi)核版本中,xtime、wall_to_monotonic、raw_time其實(shí)是定義為全局靜態(tài)變量,到我目前的版本(V3.4.10),這幾個(gè)變量被移入到了timekeeper結(jié)構(gòu)中,現(xiàn)在只需維護(hù)一個(gè)timekeeper全局靜態(tài)變量即可:
[cpp]?view plain?copy
static?struct?timekeeper?timekeeper;??
3. ?timekeeper的初始化
timekeeper的初始化由timekeeping_init完成,該函數(shù)在start_kernel的初始化序列中被調(diào)用,timekeeping_init首先從RTC中獲取當(dāng)前時(shí)間:
[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進(jìn)行必要的初始化:
[cpp]?view plain?copy
seqlock_init(&timekeeper.lock);??
ntp_init();??
接著獲取默認(rèn)的clocksource,如果平臺沒有重新實(shí)現(xiàn)clocksource_default_clock函數(shù),默認(rèn)的clocksource就是基于jiffies的clocksource_jiffies,然后通過timekeeper_setup_inernals內(nèi)部函數(shù)把timekeeper和clocksource進(jìn)行關(guān)聯(lián):
[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的當(dāng)前時(shí)間,初始化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);??
最后,初始化代表實(shí)時(shí)時(shí)間和monotonic時(shí)間之間偏移量的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字段因?yàn)槭潜4嬖趦?nèi)存中,系統(tǒng)掉電后無法保存時(shí)間信息,所以每次啟動時(shí)都要通過timekeeping_init從RTC中同步正確的時(shí)間信息。其中,read_persistent_clock和read_boot_clock是平臺級的函數(shù),分別用于獲取RTC硬件時(shí)間和啟動時(shí)的時(shí)間,不過值得注意到是,到目前為止(我的代碼樹基于3.4版本),ARM體系中,只有tegra和omap平臺實(shí)現(xiàn)了read_persistent_clock函數(shù)。如果平臺沒有實(shí)現(xiàn)該函數(shù),內(nèi)核提供了一個(gè)默認(rèn)的實(shí)現(xiàn):
[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這個(gè)內(nèi)核配置項(xiàng),打開該配置后,driver/rtc/hctosys.c將會編譯到系統(tǒng)中,由rtc_hctosys函數(shù)通過do_settimeofday在系統(tǒng)初始化時(shí)完成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. ?時(shí)間的更新
xtime一旦初始化完成后,timekeeper就開始獨(dú)立于RTC,利用自身關(guān)聯(lián)的clocksource進(jìn)行時(shí)間的更新操作,根據(jù)內(nèi)核的配置項(xiàng)的不同,更新時(shí)間的操作發(fā)生的頻度也不盡相同,如果沒有配置NO_HZ選項(xiàng),通常每個(gè)tick的定時(shí)中斷周期,do_timer會被調(diào)用一次,相反,如果配置了NO_HZ選項(xiàng),可能會在好幾個(gè)tick后,do_timer才會被調(diào)用一次,當(dāng)然傳入的參數(shù)是本次更新離上一次更新時(shí)相隔了多少個(gè)tick周期,系統(tǒng)會保證在clocksource的max_idle_ns時(shí)間內(nèi)調(diào)用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變量被相應(yīng)地累加,然后在update_wall_time中完成xtime等時(shí)間的更新操作,更新時(shí)間的核心操作就是讀取關(guān)聯(lián)clocksource的計(jì)數(shù)值,累加到xtime等字段中,其中還設(shè)計(jì)ntp時(shí)間的調(diào)整等代碼,詳細(xì)的代碼就不貼了。
5. ?獲取時(shí)間
timekeeper提供了一系列的接口用于獲取各種時(shí)間信息。
void getboottime(struct timespec *ts); ? ?獲取系統(tǒng)啟動時(shí)刻的實(shí)時(shí)時(shí)間
void get_monotonic_boottime(struct timespec *ts);??? ?獲取系統(tǒng)啟動以來所經(jīng)過的時(shí)間,包含休眠時(shí)間
ktime_t ktime_get_boottime(void);? ?獲取系統(tǒng)啟動以來所經(jīng)過的c時(shí)間,包含休眠時(shí)間,返回ktime類型
ktime_t ktime_get(void); ? ?獲取系統(tǒng)啟動以來所經(jīng)過的c時(shí)間,不包含休眠時(shí)間,返回ktime類型
void ktime_get_ts(struct timespec *ts) ; ??獲取系統(tǒng)啟動以來所經(jīng)過的c時(shí)間,不包含休眠時(shí)間,返回timespec結(jié)構(gòu)
unsigned long get_seconds(void); ? ?返回xtime中的秒計(jì)數(shù)值
struct timespec current_kernel_time(void); ? ?返回內(nèi)核最后一次更新的xtime時(shí)間,不累計(jì)最后一次更新至今clocksource的計(jì)數(shù)值
void getnstimeofday(struct timespec *ts); ? ?獲取當(dāng)前時(shí)間,返回timespec結(jié)構(gòu)
void do_gettimeofday(struct timeval *tv); ? ?獲取當(dāng)前時(shí)間,返回timeval結(jié)構(gòu)
?
評論
查看更多