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