Linux时间子系统之三:时间的维护者:timekeeper
專題文檔匯總目錄
Notes:
原文地址:Linux時(shí)間子系統(tǒng)之三:時(shí)間的維護(hù)者:timekeeper
?
本系列文章的前兩節(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)。
1.? 時(shí)間的種類
內(nèi)核管理著多種時(shí)間,它們分別是:
- RTC時(shí)間
- wall time:墻上時(shí)間
- monotonic time
- raw monotonic time
- boot 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 | No |
| boot time | 高 | 快 | Yes | Yes |
2.? struct timekeeper
內(nèi)核用timekeeper結(jié)構(gòu)來(lái)組織與時(shí)間相關(guān)的數(shù)據(jù),它的定義如下:
struct timekeeper {/* Current clocksource used for timekeeping. */struct clocksource *clock;/* NTP adjusted clock multiplier */u32 mult;/* The shift value of the current clocksource. */int shift;/* Number of clock cycles in one NTP interval. */cycle_t cycle_interval;/* Number of clock shifted nano seconds in one NTP interval. */u64 xtime_interval;/* shifted nano seconds left over when rounding cycle_interval */s64 xtime_remainder;/* Raw nano seconds accumulated per NTP interval. */u32 raw_interval;/* Clock shifted nano seconds remainder not stored in xtime.tv_nsec. */u64 xtime_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;/* The current time */struct timespec xtime;/** wall_to_monotonic is what we need to add to xtime (or xtime corrected* for sub jiffie times) to get to monotonic time. Monotonic is pegged* at zero at system boot time, so wall_to_monotonic will be negative,* however, we will ALWAYS keep the tv_nsec part positive so we can use* the usual normalization.** wall_to_monotonic is moved after resume from suspend for the* monotonic time not to jump. We need to add total_sleep_time to* wall_to_monotonic to get the real boot based time offset.** - wall_to_monotonic is no longer the boot time, getboottime must be* used instead.*/struct timespec wall_to_monotonic;/* time spent in suspend */struct timespec total_sleep_time;/* The raw monotonic time for the CLOCK_MONOTONIC_RAW posix clock. */struct timespec raw_time;/* Offset clock monotonic -> clock realtime */ktime_t offs_real;----------------------------------------------------------------------monotonic-realtime的差值,一般未負(fù)數(shù)。/* Offset clock monotonic -> clock boottime */ktime_t offs_boot;----------------------------------------------------------------------boottime-monotonic的差值。/* Seqlock for all timekeeper values */seqlock_t lock; }?
?
其中的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) {struct timespec tomono, sleep;unsigned int seq;s64 nsecs;WARN_ON(timekeeping_suspended);do {seq = read_seqbegin(&timekeeper.lock);*ts = timekeeper.xtime;--------------------------------walltimetomono = timekeeper.wall_to_monotonic;-----------------monotonic timesleep = timekeeper.total_sleep_time;-------------------sleep timensecs = timekeeping_get_ns();} while (read_seqretry(&timekeeper.lock, seq));set_normalized_timespec(ts, ts->tv_sec + tomono.tv_sec + sleep.tv_sec,(s64)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;?
3.? 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);if (!timespec_valid_strict(&now)) {pr_warn("WARNING: Persistent clock returned invalid value!\n"" Check your CMOS/BIOS settings.\n");now.tv_sec = 0;now.tv_nsec = 0;}read_boot_clock(&boot);if (!timespec_valid_strict(&boot)) {pr_warn("WARNING: Boot clock returned invalid value!\n"" Check your CMOS/BIOS settings.\n");boot.tv_sec = 0;boot.tv_nsec = 0;}seqlock_init(&timekeeper.lock);ntp_init();--------------------------------------------對(duì)鎖和ntp進(jìn)行必要的初始化write_seqlock_irqsave(&timekeeper.lock, flags);clock = clocksource_default_clock();-------------------獲取默認(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)if (clock->enable)clock->enable(clock);timekeeper_setup_internals(clock);timekeeper.xtime.tv_sec = now.tv_sec;----------------利用RTC的當(dāng)前時(shí)間,初始化xtime,raw_time,wall_to_monotonic等字段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);update_rt_offset();--------------------------------初始化代表實(shí)時(shí)時(shí)間和monotonic時(shí)間之間偏移量的offs_real字段,total_sleep_time字段初始化為0timekeeper.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) {int err = -ENODEV;struct rtc_time tm;struct timespec tv = {.tv_nsec = NSEC_PER_SEC >> 1,};struct rtc_device *rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE);--------找到rtc設(shè)備if (rtc == NULL) {pr_err("%s: unable to open rtc device (%s)\n",__FILE__, CONFIG_RTC_HCTOSYS_DEVICE);goto err_open;}err = rtc_read_time(rtc, &tm);---------------------------------------------讀取rtc時(shí)間到tmif (err) {dev_err(rtc->dev.parent,"hctosys: unable to read the hardware clock\n");goto err_read;}err = rtc_valid_tm(&tm);if (err) {dev_err(rtc->dev.parent,"hctosys: invalid date/time\n");goto err_invalid;}rtc_tm_to_time(&tm, &tv.tv_sec);---------------------------------------rtc時(shí)間轉(zhuǎn)換成timespec時(shí)間do_settimeofday(&tv);--------------------------------------------------設(shè)置walltimedev_info(rtc->dev.parent,"setting system clock to ""%d-%02d-%02d %02d:%02d:%02d UTC (%u)\n",tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,tm.tm_hour, tm.tm_min, tm.tm_sec,(unsigned int) tv.tv_sec);err_invalid: err_read:rtc_class_close(rtc);err_open:rtc_hctosys_ret = err;return err; }?
?
4.? 時(shí)間的更新
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ì)的代碼就不貼了。
5.? 獲取時(shí)間
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)
?
關(guān)于timekeeping的補(bǔ)充
1. 更新walltime
更新walltime有幾個(gè)通道,用戶空間通過(guò)stime/settimeofday;內(nèi)核do_timer()更新jiffies的時(shí)候通過(guò)update_wall_time()。
因?yàn)閠imekeeper.wall_to_monotonic依賴于timekeeper.xtime,所以每次更新xtime的時(shí)候都要考慮wall_to_monotonic。
int do_settimeofday(const struct timespec *tv) { ...timekeeping_forward_now();---------------------------------------更新時(shí)間ts_delta.tv_sec = tv->tv_sec - timekeeper.xtime.tv_sec;ts_delta.tv_nsec = tv->tv_nsec - timekeeper.xtime.tv_nsec;-------計(jì)算調(diào)整的xtime差值timekeeper.wall_to_monotonic =timespec_sub(timekeeper.wall_to_monotonic, ts_delta);----將settimeofday的修改差值反映到wall_to_monotonic以達(dá)到保證monotonic遞增的目的。 ... }?
NTP調(diào)整walltime,do_adjtimex-->timekeeping_inject_offset。
int timekeeping_inject_offset(struct timespec *ts) { ...timekeeping_forward_now();tmp = timespec_add(timekeeper.xtime, *ts);if (!timespec_valid_strict(&tmp)) {ret = -EINVAL;goto error;}timekeeper.xtime = timespec_add(timekeeper.xtime, *ts);timekeeper.wall_to_monotonic =timespec_sub(timekeeper.wall_to_monotonic, *ts); ... }?
?
2. 更新total_sleep_time
維護(hù)total_sleep_time的地方有兩處:一是通過(guò)RTC,在rtc_resume的時(shí)候通過(guò)timekeeping_inject_sleeptime();另一是通過(guò)timekeeping功能維護(hù)。
2.1 RTC維護(hù)sleeptime
RTC用于維護(hù)系統(tǒng)suspend時(shí)間通過(guò)rtc_suspend/rtc_resume。
static int __init rtc_init(void) { ...rtc_class->suspend = rtc_suspend;rtc_class->resume = rtc_resume; ...}
?
在rtc_suspend中保存old_rtc和old_system,然后在rec_resume中計(jì)算sleep_time。
static int rtc_resume(struct device *dev) { .../* snapshot the current rtc and system time at resume */getnstimeofday(&new_system);rtc_read_time(rtc, &tm);if (rtc_valid_tm(&tm) != 0) {pr_debug("%s: bogus resume time\n", dev_name(&rtc->dev));return 0;}rtc_tm_to_time(&tm, &new_rtc.tv_sec);new_rtc.tv_nsec = 0;if (new_rtc.tv_sec < old_rtc.tv_sec) {pr_debug("%s: time travel!\n", dev_name(&rtc->dev));return 0;}/* calculate the RTC time delta (sleep time)*/sleep_time = timespec_sub(new_rtc, old_rtc);/** Since these RTC suspend/resume handlers are not called* at the very end of suspend or the start of resume,* some run-time may pass on either sides of the sleep time* so subtract kernel run-time between rtc_suspend to rtc_resume* to keep things accurate.*/sleep_time = timespec_sub(sleep_time,timespec_sub(new_system, old_system));if (sleep_time.tv_sec >= 0)timekeeping_inject_sleeptime(&sleep_time);return 0; }?
?
2.2 timekeeping維護(hù)sleeptime
timekeeping的suspend/resume維護(hù)了sleeptime:
/*** timekeeping_resume - Resumes the generic timekeeping subsystem.** This is for the generic clocksource timekeeping.* xtime/wall_to_monotonic/jiffies/etc are* still managed by arch specific suspend/resume code.*/ static void timekeeping_resume(void) {unsigned long flags;struct timespec ts;read_persistent_clock(&ts);---------------------------------------------------在resume再次讀取persistent時(shí)間clocksource_resume();---------------------------------------------------------resume clocksource_list上的設(shè)備write_seqlock_irqsave(&timekeeper.lock, flags);if (timespec_compare(&ts, &timekeeping_suspend_time) > 0) {ts = timespec_sub(ts, timekeeping_suspend_time);__timekeeping_inject_sleeptime(&ts);--------------------------------------計(jì)算suspend前后的時(shí)間差值,作為sleeptime,并更新到timekeeper.total_sleep_time。}/* re-base the last cycle value */timekeeper.clock->cycle_last = timekeeper.clock->read(timekeeper.clock);timekeeper.ntp_error = 0;timekeeping_suspended = 0;timekeeping_update(false);write_sequnlock_irqrestore(&timekeeper.lock, flags);touch_softlockup_watchdog();clockevents_notify(CLOCK_EVT_NOTIFY_RESUME, NULL);--------------------------resume clockevents設(shè)備/* Resume hrtimers */hrtimers_resume();-----------------------------------------------------------打開(kāi)hrtimers。 }static int timekeeping_suspend(void) {unsigned long flags;struct timespec delta, delta_delta;static struct timespec old_delta;-----------------------------------------注意此變量為static,在timekeeping_suspend被執(zhí)行過(guò)程中會(huì)保持上一次調(diào)用值。read_persistent_clock(&timekeeping_suspend_time);----------------------------讀取當(dāng)前persistent時(shí)鐘計(jì)數(shù)到timekeeping_suspend_time中。write_seqlock_irqsave(&timekeeper.lock, flags);timekeeping_forward_now();timekeeping_suspended = 1;/** To avoid drift caused by repeated suspend/resumes,* which each can add ~1 second drift error,* try to compensate so the difference in system time* and persistent_clock time stays close to constant.*/delta = timespec_sub(timekeeper.xtime, timekeeping_suspend_time);delta_delta = timespec_sub(delta, old_delta);if (abs(delta_delta.tv_sec) >= 2) {/** if delta_delta is too large, assume time correction* has occured and set old_delta to the current delta.*/old_delta = delta;} else {/* Otherwise try to adjust old_system to compensate */timekeeping_suspend_time =timespec_add(timekeeping_suspend_time, delta_delta);}write_sequnlock_irqrestore(&timekeeper.lock, flags);clockevents_notify(CLOCK_EVT_NOTIFY_SUSPEND, NULL);----------------------suspend clockevent設(shè)備clocksource_suspend();---------------------------------------------------將相關(guān)clocksource拉入suspend狀態(tài)return 0; }/* sysfs resume/suspend bits for timekeeping */ static struct syscore_ops timekeeping_syscore_ops = {.resume = timekeeping_resume,.suspend = timekeeping_suspend, };?
?
?
2.3 如何更新total_sleep_time
timekeeping_inject_sleeptime-->__timekeeping_inject_sleeptime更新timekeeper.total_sleep_time。
/*** __timekeeping_inject_sleeptime - Internal function to add sleep interval* @delta: pointer to a timespec delta value** Takes a timespec offset measuring a suspend interval and properly* adds the sleep offset to the timekeeping variables.*/ static void __timekeeping_inject_sleeptime(struct timespec *delta) {if (!timespec_valid_strict(delta)) {printk(KERN_WARNING "__timekeeping_inject_sleeptime: Invalid ""sleep delta value!\n");return;}timekeeper.xtime = timespec_add(timekeeper.xtime, *delta);-------------------------xtime需要加上睡眠時(shí)間timekeeper.wall_to_monotonic =timespec_sub(timekeeper.wall_to_monotonic, *delta);------------------------由于xtime加上了睡眠時(shí)間,但是monotonic不包括睡眠時(shí)間,所以wall_to_monotonic需要減去睡眠時(shí)間。update_sleep_time(timespec_add(timekeeper.total_sleep_time, *delta));--------------累積睡眠時(shí)間,更新到timekeeper.total_sleep_time, }?
?
?
3. cpu suspend對(duì)時(shí)間的影響
static void timekeeping_resume(void) {unsigned long flags;struct timespec ts;read_persistent_clock(&ts);clocksource_resume();write_seqlock_irqsave(&timekeeper.lock, flags);if (timespec_compare(&ts, &timekeeping_suspend_time) > 0) {ts = timespec_sub(ts, timekeeping_suspend_time);__timekeeping_inject_sleeptime(&ts);}/* re-base the last cycle value */timekeeper.clock->cycle_last = timekeeper.clock->read(timekeeper.clock);timekeeper.ntp_error = 0;timekeeping_suspended = 0;timekeeping_update(false);write_sequnlock_irqrestore(&timekeeper.lock, flags);touch_softlockup_watchdog();clockevents_notify(CLOCK_EVT_NOTIFY_RESUME, NULL);printk("arnoldlu %s timekeeping_suspend_time=%ld.%09ld\n", __func__, timekeeping_suspend_time.tv_sec, timekeeping_suspend_time.tv_nsec);/* Resume hrtimers */hrtimers_resume(); }static int timekeeping_suspend(void) {unsigned long flags;struct timespec delta, delta_delta;static struct timespec old_delta;read_persistent_clock(&timekeeping_suspend_time);write_seqlock_irqsave(&timekeeper.lock, flags);timekeeping_forward_now();timekeeping_suspended = 1;/** To avoid drift caused by repeated suspend/resumes,* which each can add ~1 second drift error,* try to compensate so the difference in system time* and persistent_clock time stays close to constant.*/delta = timespec_sub(timekeeper.xtime, timekeeping_suspend_time);delta_delta = timespec_sub(delta, old_delta);if (abs(delta_delta.tv_sec) >= 2) {/** if delta_delta is too large, assume time correction* has occured and set old_delta to the current delta.*/old_delta = delta;} else {/* Otherwise try to adjust old_system to compensate */timekeeping_suspend_time =timespec_add(timekeeping_suspend_time, delta_delta);}write_sequnlock_irqrestore(&timekeeper.lock, flags);printk("arnoldlu %s timekeeping_suspend_time=%ld.%09ld\n", __func__, timekeeping_suspend_time.tv_sec, timekeeping_suspend_time.tv_nsec);clockevents_notify(CLOCK_EVT_NOTIFY_SUSPEND, NULL);clocksource_suspend();return 0; }?
?
persistent時(shí)間讀取-->關(guān)閉tick-->打開(kāi)tick-->persistent時(shí)間讀取
?
總結(jié)
以上是生活随笔為你收集整理的Linux时间子系统之三:时间的维护者:timekeeper的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Redis五大数据结构
- 下一篇: 《数据结构与抽象:Java语言描述(原书