Linux时间子系统之三:时间的维护者:timekeeper
專題文檔匯總目錄
Notes:
原文地址:Linux時間子系統(tǒng)之三:時間的維護者:timekeeper
?
本系列文章的前兩節(jié)討論了用于計時的時鐘源:clocksource,以及內核內部時間的一些表示方法,但是對于真實的用戶來說,我們感知的是真實世界的真實時間,也就是所謂的墻上時間,clocksource只能提供一個按給定頻率不停遞增的周期計數,如何把它和真實的墻上時間相關聯?本節(jié)的內容正是要討論這一點。
1.? 時間的種類
內核管理著多種時間,它們分別是:
- RTC時間
- wall time:墻上時間
- monotonic time
- raw monotonic time
- boot time:總啟動時間
RTC時間? 在PC中,RTC時間又叫CMOS時間,它通常由一個專門的計時硬件來實現,軟件可以讀取該硬件來獲得年月日、時分秒等時間信息,而在嵌入式系統(tǒng)中,有使用專門的RTC芯片,也有直接把RTC集成到Soc芯片中,讀取Soc中的某個寄存器即可獲取當前時間信息。一般來說,RTC是一種可持續(xù)計時的,也就是說,不管系統(tǒng)是否上電,RTC中的時間信息都不會丟失,計時會一直持續(xù)進行,硬件上通常使用一個后備電池對RTC硬件進行單獨的供電。因為RTC硬件的多樣性,開發(fā)者需要為每種RTC時鐘硬件提供相應的驅動程序,內核和用戶空間通過驅動程序訪問RTC硬件來獲取或設置時間信息。
xtime? xtime和RTC時間一樣,都是人們日常所使用的墻上時間,只是RTC時間的精度通常比較低,大多數情況下只能達到毫秒級別的精度,如果是使用外部的RTC芯片,訪問速度也比較慢,為此,內核維護了另外一個wall time時間:xtime,取決于用于對xtime計時的clocksource,它的精度甚至可以達到納秒級別,因為xtime實際上是一個內存中的變量,它的訪問速度非常快,內核大部分時間都是使用xtime來獲得當前時間信息。xtime記錄的是自1970年1月1日24時到當前時刻所經歷的納秒數。
monotonic time? 該時間自系統(tǒng)開機后就一直單調地增加,它不像xtime可以因用戶的調整時間而產生跳變,不過該時間不計算系統(tǒng)休眠的時間,也就是說,系統(tǒng)休眠時,monotoic時間不會遞增。
raw monotonic time? 該時間與monotonic時間類似,也是單調遞增的時間,唯一的不同是:raw monotonic time“更純凈”,他不會受到NTP時間調整的影響,它代表著系統(tǒng)獨立時鐘硬件對時間的統(tǒng)計。
boot time? 與monotonic時間相同,不過會累加上系統(tǒng)休眠的時間,它代表著系統(tǒng)上電后的總時間。
| 時間種類 | 精度(統(tǒng)計單位) | 訪問速度 | 累計休眠時間 | 受NTP調整的影響 |
| RTC | 低 | 慢 | Yes | Yes |
| xtime | 高 | 快 | Yes | Yes |
| monotonic | 高 | 快 | No | Yes |
| raw monotonic | 高 | 快 | No | No |
| boot time | 高 | 快 | Yes | Yes |
2.? struct timekeeper
內核用timekeeper結構來組織與時間相關的數據,它的定義如下:
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的差值,一般未負數。/* Offset clock monotonic -> clock boottime */ktime_t offs_boot;----------------------------------------------------------------------boottime-monotonic的差值。/* Seqlock for all timekeeper values */seqlock_t lock; }?
?
其中的xtime字段就是上面所說的墻上時間,它是一個timespec結構的變量,它記錄了自1970年1月1日以來所經過的時間,因為是timespec結構,所以它的精度可以達到納秒級,當然那要取決于系統(tǒng)的硬件是否支持這一精度。
內核除了用xtime表示墻上的真實時間外,還維護了另外一個時間:monotonic time,可以把它理解為自系統(tǒng)啟動以來所經過的時間,該時間只能單調遞增,可以理解為xtime雖然正常情況下也是遞增的,但是畢竟用戶可以主動向前或向后調整墻上時間,從而修改xtime值。但是monotonic時間不可以往后退,系統(tǒng)啟動后只能不斷遞增。奇怪的是,內核并沒有直接定義一個這樣的變量來記錄monotonic時間,而是定義了一個變量wall_to_monotonic,記錄了墻上時間和monotonic時間之間的偏移量,當需要獲得monotonic時間時,把xtime和wall_to_monotonic相加即可,因為默認啟動時monotonic時間為0,所以實際上wall_to_monotonic的值是一個負數,它和xtime同一時間被初始化,請參考timekeeping_init函數。
計算monotonic時間要去除系統(tǒng)休眠期間花費的時間,內核用total_sleep_time記錄休眠的時間,每次休眠醒來后重新累加該時間,并調整wall_to_monotonic的值,使其在系統(tǒng)休眠醒來后,monotonic時間不會發(fā)生跳變。因為wall_to_monotonic值被調整。所以如果想獲取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字段用來表示真正的硬件時間,也就是上面所說的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其實是定義為全局靜態(tài)變量,到我目前的版本(V3.4.10),這幾個變量被移入到了timekeeper結構中,現在只需維護一個timekeeper全局靜態(tài)變量即可:
static struct timekeeper timekeeper;?
3.? timekeeper的初始化
timekeeper的初始化由timekeeping_init完成,該函數在start_kernel的初始化序列中被調用,timekeeping_init首先從RTC中獲取當前時間:
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();--------------------------------------------對鎖和ntp進行必要的初始化write_seqlock_irqsave(&timekeeper.lock, flags);clock = clocksource_default_clock();-------------------獲取默認的clocksource,如果平臺沒有重新實現clocksource_default_clock函數,默認的clocksource就是基于jiffies的clocksource_jiffies,然后通過timekeeper_setup_inernals內部函數把timekeeper和clocksource進行關聯if (clock->enable)clock->enable(clock);timekeeper_setup_internals(clock);timekeeper.xtime.tv_sec = now.tv_sec;----------------利用RTC的當前時間,初始化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();--------------------------------初始化代表實時時間和monotonic時間之間偏移量的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字段因為是保存在內存中,系統(tǒng)掉電后無法保存時間信息,所以每次啟動時都要通過timekeeping_init從RTC中同步正確的時間信息。其中,read_persistent_clock和read_boot_clock是平臺級的函數,分別用于獲取RTC硬件時間和啟動時的時間,不過值得注意到是,到目前為止(我的代碼樹基于3.4版本),ARM體系中,只有tegra和omap平臺實現了read_persistent_clock函數。如果平臺沒有實現該函數,內核提供了一個默認的實現:
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平臺是如何初始化xtime的?答案就是CONFIG_RTC_HCTOSYS這個內核配置項,打開該配置后,driver/rtc/hctosys.c將會編譯到系統(tǒng)中,由rtc_hctosys函數通過do_settimeofday在系統(tǒng)初始化時完成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設備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時間到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時間轉換成timespec時間do_settimeofday(&tv);--------------------------------------------------設置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.? 時間的更新
xtime一旦初始化完成后,timekeeper就開始獨立于RTC,利用自身關聯的clocksource進行時間的更新操作,根據內核的配置項的不同,更新時間的操作發(fā)生的頻度也不盡相同,如果沒有配置NO_HZ選項,通常每個tick的定時中斷周期,do_timer會被調用一次,相反,如果配置了NO_HZ選項,可能會在好幾個tick后,do_timer才會被調用一次,當然傳入的參數是本次更新離上一次更新時相隔了多少個tick周期,系統(tǒng)會保證在clocksource的max_idle_ns時間內調用do_timer,以防止clocksource的溢出:
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);??? 獲取系統(tǒng)啟動時刻的實時時間
- void get_monotonic_boottime(struct timespec *ts);???? 獲取系統(tǒng)啟動以來所經過的時間,包含休眠時間
- ktime_t ktime_get_boottime(void);?? 獲取系統(tǒng)啟動以來所經過的c時間,包含休眠時間,返回ktime類型
- ktime_t ktime_get(void);??? 獲取系統(tǒng)啟動以來所經過的c時間,不包含休眠時間,返回ktime類型
- void ktime_get_ts(struct timespec *ts) ;?? 獲取系統(tǒng)啟動以來所經過的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結構
?
關于timekeeping的補充
1. 更新walltime
更新walltime有幾個通道,用戶空間通過stime/settimeofday;內核do_timer()更新jiffies的時候通過update_wall_time()。
因為timekeeper.wall_to_monotonic依賴于timekeeper.xtime,所以每次更新xtime的時候都要考慮wall_to_monotonic。
int do_settimeofday(const struct timespec *tv) { ...timekeeping_forward_now();---------------------------------------更新時間ts_delta.tv_sec = tv->tv_sec - timekeeper.xtime.tv_sec;ts_delta.tv_nsec = tv->tv_nsec - timekeeper.xtime.tv_nsec;-------計算調整的xtime差值timekeeper.wall_to_monotonic =timespec_sub(timekeeper.wall_to_monotonic, ts_delta);----將settimeofday的修改差值反映到wall_to_monotonic以達到保證monotonic遞增的目的。 ... }?
NTP調整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
維護total_sleep_time的地方有兩處:一是通過RTC,在rtc_resume的時候通過timekeeping_inject_sleeptime();另一是通過timekeeping功能維護。
2.1 RTC維護sleeptime
RTC用于維護系統(tǒng)suspend時間通過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中計算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維護sleeptime
timekeeping的suspend/resume維護了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時間clocksource_resume();---------------------------------------------------------resume clocksource_list上的設備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);--------------------------------------計算suspend前后的時間差值,作為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設備/* Resume hrtimers */hrtimers_resume();-----------------------------------------------------------打開hrtimers。 }static int timekeeping_suspend(void) {unsigned long flags;struct timespec delta, delta_delta;static struct timespec old_delta;-----------------------------------------注意此變量為static,在timekeeping_suspend被執(zhí)行過程中會保持上一次調用值。read_persistent_clock(&timekeeping_suspend_time);----------------------------讀取當前persistent時鐘計數到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設備clocksource_suspend();---------------------------------------------------將相關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需要加上睡眠時間timekeeper.wall_to_monotonic =timespec_sub(timekeeper.wall_to_monotonic, *delta);------------------------由于xtime加上了睡眠時間,但是monotonic不包括睡眠時間,所以wall_to_monotonic需要減去睡眠時間。update_sleep_time(timespec_add(timekeeper.total_sleep_time, *delta));--------------累積睡眠時間,更新到timekeeper.total_sleep_time, }?
?
?
3. cpu suspend對時間的影響
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時間讀取-->關閉tick-->打開tick-->persistent時間讀取
?
總結
以上是生活随笔為你收集整理的Linux时间子系统之三:时间的维护者:timekeeper的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis五大数据结构
- 下一篇: 《数据结构与抽象:Java语言描述(原书