歡迎光臨
每天分享高質量文章

Linux時間子系統之:timekeeping

一、前言

timekeeping模組是一個提供時間服務的基礎模組。Linux核心提供各種time line,real time clock,monotonic clock、monotonic raw clock等,timekeeping模組就是負責跟蹤、維護這些timeline的,並且向其他模組(timer相關模組、使用者空間的時間服務等)提供服務,而timekeeping模組維護timeline的基礎是基於clocksource模組和tick模組。透過tick模組的tick事件,可以週期性的更新time line,透過clocksource模組、可以獲取tick之間更精準的時間資訊。

本文熟悉介紹timekeeping的一些基礎概念,接著會介紹該模組初始化的過程,此後會從上至下介紹該模組提供的服務、該模組如何和tick模組互動以及如何和clocksource模組互動,最後介紹電源管理相關的內容。

二、timekeeper核心資料定義

1、struct timekeeper資料結構解析

舊的內核定義了很多零散的全域性變數來管理linux kernel中的各種系統clock,現在,內核定義的struct timekeeper資料結構來管理各種系統時鐘的跟蹤以及控制,定義如下:

struct timekeeper {
struct clocksource    *clock;------------------------(1)

u32            mult;-----------------------------(2)
u32            shift;

cycle_t            cycle_interval; -----------------------(3)
cycle_t            cycle_last;
u64            xtime_interval;
s64            xtime_remainder;
u32            raw_interval;

    s64            ntp_error;
u32            ntp_error_shift;

    u64            xtime_sec;---------------------------(4)
u64            xtime_nsec;

    struct timespec        wall_to_monotonic; -------------------(5)
ktime_t            offs_real;
struct timespec        total_sleep_time; ------記錄系統睡眠時間
ktime_t            offs_boot; ------------記錄系統boot time

struct timespec        raw_time; -----------------------(6)

s32            tai_offset; ---------------------------(7)
ktime_t            offs_tai;

};

(1)timekeeper當前使用的clocksource。這個clock應該系統中最優的那個,如果有好過當前clocksource註冊入系統,那麼clocksource模組會通知timekeeping模組來切換clocksource。

(2)clock source的cycle值和納秒轉換的facotr,概念和clocksource的mult和shift一致。

(3)NTP相關的成員,這裡不詳述了,實在是對NTP沒有興趣。

(4)CLOCK_REALTIME型別的系統時鐘(其實就是牆上時鐘)。我們都知道,時間就像是一條直線(line),不知道起點,也不知道終點,因此我們稱之time line。time line有很多種,和如何定義0值的時間以及用什麼樣的刻度來度量時間相關。人類熟悉的牆上時間和linux kernel中定義的CLOCK_REALTIME都是用來描述time line的,只不過時間原點和如何度量time line上兩點距離的刻度不一樣。對於人類的時間,0值是耶穌誕生的時間點;對於CLOCK_REALTIME,0值是linux epoch,即1970年1月1日…。對於牆上時間,在度量的時候雖然也是基於秒的,但是人類做了grouping,因此使用了年月日時分秒的概念。這裡的秒數是相對與當前分鐘值內的秒數。對於linux世界中的CLOCK_REALTIME time,直接使用秒以及納秒在當前秒內的偏移來表示。
因此,這裡xtime_sec用秒這個的刻度單位來度量CLOCK_REALTIME time line上,時間原點到當前點的距離值。當然xtime_sec是一個對current time point的取整值,為了更好的精度,還需要一個納秒錶示的offset,也就是xtime_nsec。
不過為了核心內部計算精度(核心對時間的計算是基於cycle的),並不是儲存了時間的納秒偏移值,而是儲存了一個shift之後的值,因此,使用者看來,當前時間點的值應該是距離時間原點xtime_sec + (xtime_nsec << shift)距離的那個時間點值

(5)CLOCK_MONOTONIC型別的系統時鐘。這種系統時鐘並沒有象牆上時鐘一樣定義一個相對於linux epoch的值,這個成員定義了monotonic clock到real time clock的偏移,也就是說,這裡的wall_to_monotonic和offs_real需要加上real time clock的時間值才能得到monotonic clock的時間值。當然,從這裡成員的名字就看出來了。wall_to_monotonic和offs_real的意思是一樣的,不過時間的格式不一樣,用在不同的場合,以便獲取效能的提升。

(6)CLOCK_MONOTONIC_RAW型別的系統時鐘

(7)CLOCK_TAI型別的系統時鐘。TAI(international atomic time)是原子鐘,在時間的基本概念檔案中,我們說過,UTC就是base TAI的,也就是說用銫133的振蕩頻率來定義秒的那個時鐘,當然UTC還有考慮leap second以便方便廣大人民群眾。CLOCK_TAI型別的系統時鐘就是完完全全使用銫133的振蕩頻率來定義秒的那個時鐘,不向人類妥協。

2、全域性變數

static struct timekeeper timekeeper;
static DEFINE_RAW_SPINLOCK(timekeeper_lock);
static seqcount_t timekeeper_seq;

static struct timekeeper shadow_timekeeper;

timekeeper維護了系統的所有的clock。一個全域性變數(共享資源)沒有鎖保護怎麼行,timekeeper_lock和timekeeper_seq都是用來保護timekeeper的,用在不同的場合。

shadow_timekeeper主要用在更新系統時間的過程中。在update_wall_time中,首先將時間調整值設定到shadow_timekeeper中,然後一次性的copy到真正的那個timekeeper中。這樣的設計主要是可以減少持有timekeeper_seq鎖的時間(在更新系統時間的過程中),不過需要註意的是:在其他的過程中(非update_wall_time),需要sync shadow timekeeper。

三、timekeeping初始化

timekeeping初始化的程式碼位於timekeeping_init函式中,在系統初始化的時候(start_kernel)會呼叫該函式進行timekeeping的初始化。

1、從persistent clock獲取當前的時間值

timekeeping模組中支援若干種system clock,這些system clock的資料儲存在ram中,一旦斷電,資料就丟失了。因此,在系加電啟動後,會從persistent clock中中取出當前時間值(例如RTC,RTC有battery供電,因此係統斷電也可以儲存資料),根據情況初始化各種system clock。具體程式碼如下:

    read_persistent_clock(&now;);----------------------(1)
if (!timespec_valid_strict(&now;)) {---------------------(2)
now.tv_sec = 0;
now.tv_nsec = 0;
} else if (now.tv_sec || now.tv_nsec)
persistent_clock_exist = true; ---------------------(3)

    read_boot_clock(&boot;);-----------概念同上
if (!timespec_valid_strict(&boot;)) {
boot.tv_sec = 0;
boot.tv_nsec = 0;
}

(1)read_persistent_clock是一個和architecture相關的函式,具體如何支援可以看具體的architecture相關的程式碼實現。對於ARM,其實現在linux/arch/arm/kernel/time.c檔案中。該函式的功能就是從系統中的HW clock(例如RTC)中獲取時間資訊。

(2)timespec_valid_strict用來校驗一個timespec是否是有效。如何判斷從RTC獲取的值是有效的呢?要滿足timespec中的秒數值要大於等於0,小於KTIME_SEC_MAX,納秒值要小於NSEC_PER_SEC(10^9)。KTIME_SEC_MAX這個宏定義了ktime_t這種型別的資料可以表示的最大的秒數值,從RTC中讀出的秒數值當然不能大於它,KTIME_SEC_MAX定義如下:

#define KTIME_MAX            ((s64)~((u64)1 << 63))
#if (BITS_PER_LONG == 64)
# define KTIME_SEC_MAX            (KTIME_MAX / NSEC_PER_SEC)
#else
# define KTIME_SEC_MAX            LONG_MAX
#endif

ktime_t這種資料型別佔據了64 bit的size,對於64 bit的CPU和32 bit CPU上是不一樣的,64 bit的CPU上定義為一個signed long long,該值直接表示了納秒值。對於32bit CPU而言,64 bit的資料分成兩個signed int型別,分別表示秒數和納秒數。

(3)設定persistent_clock_exist flag,說明系統中存在RTC的硬體模組,timekeeping模組會和RTC模組進行互動。例如:在suspend的時候,如果該flag是true的話,RTC driver不能sleep,因為timekeeping模組還需要在resume的時候透過RTC的值恢復其時間值呢。

2、為timekeeping模組設定default的clock source

clock = clocksource_default_clock();--------------------(1)
if (clock->enable)
clock->enable(clock);-----enalbe default clocksource
tk_setup_internals(tk, clock);------------------------(2)

(1)在timekeeping初始化的時候,很難選擇一個最好的clock source,因為很有可能最好的那個還沒有初始化呢。因此,這裡的策略就是採用一個在timekeeping初始化時一定是ready的clock source,也就是基於jiffies 的那個clocksource。clocksource_default_clock定義在kernel/time/jiffies.c,是一個weak symble,如果你願意也可以重新定義clocksource_default_clock這個函式。不過,要保證在timekeeping初始化的時候是ready的。

(2)建立default clocksource和timekeeping夥伴關係。

3、初始化real time clock、monotonic clock和monotonic raw clock

tk_set_xtime(tk, &now;);--------------------------(1)
tk->raw_time.tv_sec = 0;--------------------------(2)
tk->raw_time.tv_nsec = 0;
if (boot.tv_sec == 0 && boot.tv_nsec == 0)
boot = tk_xtime(tk); ---如果沒有獲取到有效的booting time,那麼就選擇當前的real time clock

set_normalized_timespec(&tmp;, -boot.tv_sec, -boot.tv_nsec);----------(3)
tk_set_wall_to_mono(tk, tmp);

tmp.tv_sec = 0;
tmp.tv_nsec = 0;
tk_set_sleep_time(tk, tmp);------初始化sleep time為0

(1)根據從RTC中獲取的時間值來初始化timekeeping中的real time clock,如果沒有獲取到正確的RTC時間值,那麼預設的real time(wall time)就是linux epoch。

(2)monotonic raw clock被設定為從0開始。

(3)啟動時將monotonic clock設定為負的real time clock,timekeeper並沒有直接儲存monotonic clock,而是儲存了一個wall_to_monotonic的值,這個值類似offset,real time clock加上這個offset就可以得到monotonic clock。因此,初始化的時間點上,monotonic clock實際上等於0(如果沒有獲取到有效的booting time)。當系統執行之後,real time clock+ wall_to_monotonic是系統的uptime,而real time clock+ wall_to_monotonic + sleep time也就是系統的boot time。

四、獲取和設定當前系統時鐘的時間值

1、獲取monotonic clock的時間值:ktime_get和ktime_get_ts

ktime_t ktime_get(void)
{
struct timekeeper *tk = &timekeeper;
unsigned int seq;
s64 secs, nsecs;

    do {
seq = read_seqcount_begin(&timekeeper;_seq);
secs = tk->xtime_sec + tk->wall_to_monotonic.tv_sec;-----獲取monotonic clock的秒值
nsecs = timekeeping_get_ns(tk) + tk->wall_to_monotonic.tv_nsec; ---獲取納秒值

    } while (read_seqcount_retry(&timekeeper;_seq, seq));

return ktime_add_ns(ktime_set(secs, 0), nsecs);----傳回一個ktime型別的時間值
}

一般而言,timekeeping模組是在tick到來的時候更新各種系統時鐘的時間值,ktime_get呼叫很有可能發生在兩次tick之間,這時候,僅僅依靠當前系統時鐘的值精度就不甚理想了,畢竟那個時間值是per tick更新的。因此,為了獲得高精度,ns值的獲取是透過timekeeping_get_ns完成的,該函式獲取了real time clock的當前時刻的納秒值,而這是透過上一次的tick時候的real time clock的時間值(xtime_nsec)加上當前時刻到上一次tick之間的delta時間值計算得到的。

ktime_get_ts的概念和ktime_get是一樣的,只不過傳回的時間值格式不一樣而已。

2、獲取real time clock的時間值:ktime_get_real和ktime_get_real_ts

這兩個函式的具體邏輯動作和獲取monotonic clock的時間值函式是完全一樣的,大家可以自己看程式碼分析。這裡稍微提一下另外一個函式:current_kernel_time,程式碼如下:

static inline struct timespec tk_xtime(struct timekeeper *tk)
{
struct timespec ts;

    ts.tv_sec = tk->xtime_sec;
ts.tv_nsec = (long)(tk->xtime_nsec >> tk->shift);
return ts;
}

struct timespec current_kernel_time(void)
{
struct timekeeper *tk = &timekeeper;
struct timespec now;
unsigned long seq;

    do {
seq = read_seqcount_begin(&timekeeper;_seq);

        now = tk_xtime(tk);
} while (read_seqcount_retry(&timekeeper;_seq, seq));

    return now;
}

上面的程式碼並沒有呼叫clocksource的read函式獲取tick之間的delta時間值,因此current_kernel_time是一個粗略版本的real time clock,精度低於ktime_get_real,不過效能要好些。類似的,monotonic clock也有一個get_monotonic_coarse函式,概念類似current_kernel_time。

3、獲取boot clock的時間值:ktime_get_boottime和get_monotonic_boottime

ktime_t ktime_get_boottime(void)
{
struct timespec ts;

    get_monotonic_boottime(&ts;);
return timespec_to_ktime(ts);
}

boot clock這個系統時鐘和monotonic clock有什麼不同?monotonic clock是從一個固定點開始作為epoch,對於linux,就是啟動的時間點,因此,monotonic clock是一個從0開始增加的clock,並且不接受使用者的setting,看起來好象適合boot clock是一致的,不過它們之間唯一的差別是對系統進入suspend的處理,對於monotonic clock,它是不記錄系統睡眠時間的,因此monotonic clock得到的是一個system uptime。而boot clock計算睡眠時間,直到系統reboot。

ktime_get_boottime傳回ktime的時間值,get_monotonic_boottime函式傳回timespec格式的時間值。

4、獲取TAI clock的時間值:ktime_get_clocktai和timekeeping_clocktai

原子鐘和real time clock(UTC)是類似的,只是有一個偏移而已,記錄在tai_offset中。程式碼非常簡單,大家自己閱讀即可。ktime_get_clocktai傳回ktime的時間值,而timekeeping_clocktai傳回timespec格式的時間值。

5、設定wall time clock

int do_settimeofday(const struct timespec *tv)
{

……

    timekeeping_forward_now(tk);---更新timekeeper至當前時間

    xt = tk_xtime(tk);
ts_delta.tv_sec = tv->tv_sec – xt.tv_sec;
ts_delta.tv_nsec = tv->tv_nsec – xt.tv_nsec; ----計算delta

    tk_set_wall_to_mono(tk, timespec_sub(tk->wall_to_monotonic, ts_delta)); --不調mono clock

    tk_set_xtime(tk, tv); ---調整wall time clock

    timekeeping_update(tk, TK_CLEAR_NTP | TK_MIRROR | TK_CLOCK_WAS_SET); --更tk

……
}

五、和clocksource模組的互動

除了直接呼叫clocksource的read函式之外,timekeeping和clocksource主要的互動就是change clocksource的操作了。當系統中有更高精度的clocksource的時候,會呼叫timekeeping_notify函式通知timekeeping模組進行clock source的切換,程式碼如下:

int timekeeping_notify(struct clocksource *clock)
{
struct timekeeper *tk = &timekeeper;

    if (tk->clock == clock)----新的clocksource和舊的一樣,不需要切換
return 0;
stop_machine(change_clocksource, clock, NULL);
tick_clock_notify();----通知tick模組,具體在其他檔案中描述
return tk->clock == clock ? 0 : -1;
}

stop_machine從字面上就可以知道是停掉了所有cpu上的任務(這個machine都不能對外提供服務了),只是執行一個函式,在這個場景下是change_clocksource。(為何不直接呼叫change_clocksource而是使用stop_machine這樣的大招?現在還在思考中……)。change_clocksource主要執行的步驟包括:

(1)呼叫timekeeping_forward_now函式。就要更換新的clocksource了,就是舊clocksource最後再發揮一次作用。呼叫舊的clocksource的read函式,將最後的這段時間間隔(當前到上次read)加到real time system clock以及minitonic raw system clock上去。

(2)呼叫tk_setup_internals函式設定新的clocksource,disable舊的clocksource。tk_setup_internals函式程式碼如下:

static void tk_setup_internals(struct timekeeper *tk, struct clocksource *clock)
{
cycle_t interval;
u64 tmp, ntpinterval;
struct clocksource *old_clock;

    old_clock = tk->clock;
tk->clock = clock;---更換為新的clocksource
tk->cycle_last = clock->cycle_last = clock->read(clock); ----更新last cycle值

    tmp = NTP_INTERVAL_LENGTH;---NTP interval設定的納秒數
tmp <<= clock->shift;
ntpinterval = tmp;----計算remainder的時候會用到
tmp += clock->mult/2;
do_div(tmp, clock->mult);------將NTP interval的納秒值轉成新clocksource的cycle值
if (tmp == 0)
tmp = 1;

    interval = (cycle_t) tmp;
tk->cycle_interval = interval; ---設定新的NTP interval的cycle值

    tk->xtime_interval = (u64) interval * clock->mult;----將NTP interval的cycle值轉成ns
tk->xtime_remainder = ntpinterval – tk->xtime_interval;---計算remainder
tk->raw_interval =
((u64) interval * clock->mult) >> clock->shift; -----NTP interval的ns值

     if (old_clock) {------xtime_nsec儲存的是不是實際的ns值而是一個沒有執行shift版本的
int shift_change = clock->shift – old_clock->shift;
if (shift_change < 0)-----如果新舊的shift值不一樣,那麼當前的xtime_nsec要修正
tk->xtime_nsec >>= -shift_change;
else
tk->xtime_nsec <<= shift_change;
}
tk->shift = clock->shift; -----更換新的shift factor

    tk->ntp_error = 0;
tk->ntp_error_shift = NTP_SCALE_SHIFT – clock->shift;

    tk->mult = clock->mult;-----更換新的mult factor
}

由於更換了新的clocksource,一般而言,新舊clocksource的工作引數不一樣,就要就導致timekeeper的一些內部的資料成員要進行更新,例如NTP interval、multi和shift facotr數值等。

(3)呼叫timekeeping_update函式。由於更新了clocksource,因此timekeeping模組要更新其內部資料。TK_CLEAR_NTP控制clear 舊的NTP的狀態資料。TK_MIRROR用來更新shadow timekeeper,主要是為了保持和real timekeeper同步。TK_CLOCK_WAS_SET用在paravirtual clock場景中,這裡就不詳細描述了。

六、和tick device模組的介面

1、periodic tick

當系統採用periodic tick機制的時候,tick device模組會在週期性tick到來的時候,呼叫tick_periodic來進行下麵的動作:

(1)如果是global tick,需要呼叫do_timer來修改jiffies,計算系統負荷。

(2)如果是global tick,需要呼叫update_wall_time來更新系統時間。timekeeping模組是按照自己的節奏來更新系統時間的,更新一般是發生在週期性tick到來的時候。如果HZ=100的話,那麼每10ms就會有一個tick事件(clockevent事件),跟的太緊,會浪費CPU,跟的太鬆會損失一些精度。timekeeper中的cycle_interval成員就是週期性tick的cycle interval,如果距離上次的更新還不到一個tick的時間,那麼就不再更新系統時間,直接退出。

(3)呼叫update_process_times和profile_tick,分別更新行程時間和進行核心剖析相關的操作。

2、dynamic tick

TODO

七、timekeeping模的電源管理

1、初始化

static struct syscore_ops timekeeping_syscore_ops = {
.resume        = timekeeping_resume,
.suspend    = timekeeping_suspend,
};

static int __init timekeeping_init_ops(void)
{
register_syscore_ops(&timekeeping;_syscore_ops);
return 0;
}

device_initcall(timekeeping_init_ops);

在系統初始化的過程中,會呼叫 timekeeping_init_ops來註冊和timekeeping相關的system core operations。在舊的核心中,這部分的功能是透過sysdev class和sysdev實現的。透過sysdev class和sysdev實現的suspend和resume看起來比較笨重而且效率低,因此新的核心為某些core subsystem設計了新的基於syscore_ops 的介面。而註冊的這些callback函式會在系統suspend和resume的時候,在適當的時機執行(在system suspend過程中,syscore suspend的執行非常的靠後,在那些普通的匯流排裝置之後,對應的,system resume過程中,非常早的醒來進入工作狀態)。當然,這屬於電源管理子系統的內容,這篇文章就不描述了,大家可以參考suspend_enter函式。

2、suspned 回呼函式

static int timekeeping_suspend(void)
{
struct timekeeper *tk = &timekeeper;
unsigned long flags;
struct timespec        delta, delta_delta;
static struct timespec    old_delta;

    read_persistent_clock(&timekeeping;_suspend_time); ------------(1)
if (timekeeping_suspend_time.tv_sec || timekeeping_suspend_time.tv_nsec)
persistent_clock_exist = true;

    raw_spin_lock_irqsave(&timekeeper;_lock, flags);
write_seqcount_begin(&timekeeper;_seq);
timekeeping_forward_now(tk);----------------------(2)
timekeeping_suspended = 1; ----------------------(3)

    delta = timespec_sub(tk_xtime(tk), timekeeping_suspend_time);-------(4)
delta_delta = timespec_sub(delta, old_delta);
if (abs(delta_delta.tv_sec)  >= 2) {
old_delta = delta;
} else {
timekeeping_suspend_time =
timespec_add(timekeeping_suspend_time, delta_delta);
}

    timekeeping_update(tk, TK_MIRROR);----更新shadow timekeeper
write_seqcount_end(&timekeeper;_seq);
raw_spin_unlock_irqrestore(&timekeeper;_lock, flags);

    clockevents_notify(CLOCK_EVT_NOTIFY_SUSPEND, NULL);--------(5)
clocksource_suspend();---suspend系統中所有的clocksource裝置
clockevents_suspend(); ---suspend系統中所有的clockevent裝置

    return 0;
}

(1)一般而言,在整機suspend之後,clocksource和clockevent所依賴的底層硬體會被推入深度睡眠甚至是斷電狀態(當然,也有一些例外,有些clocksource會標記CLOCK_SOURCE_SUSPEND_NONSTOP flag),這時候,有些有計時能力的硬體(persistent clock),例如RTC,仍然是running狀態。雖然RTC的精度不是很好,但是time keeping的動作在suspend中的時候也要繼續,需要記錄這一段時間的流逝。因此,這裡呼叫read_persistent_clock將suspend時間點資訊記錄到timekeeping_suspend_time變數中。persistent_clock_exist變數標識系統中是否有RTC的硬體,按理說應該在timekeeping初始化的時候設定,不過也有可能在那個時刻,系統中RTC驅動還沒有初始化,因此,如果這裡能得到一個有效的時間值的話,也相應的更新persistent_clock_exist變數。

(2)timekeeping subsystem馬上就睡下去了,臨睡前,最後一次更新timekeeper的系統時鐘的資料,此後,底層的硬體會停掉,硬體counter和硬體timer都會停止工作了。

(3)標記timekeeping subsystem進入suspend過程。在這個過程中的獲取時間操作應該被禁止。

(4)persistent clock的精度一般沒有那麼好,可能只是以秒的精度在計時。因此,一次suspend/resume的過程中,read persistent clock會引入半秒的誤差。為了防止連續的suspend/resume引起時間偏移,這裡也考慮了real time clock和persistent clock之間的delta值。delta是本次real time clock和persistent clock之間的差值,delta_delta是兩次suspend之間delta的差值,如果delta_delta大於2秒,

(5)呼叫clockevents_notify函式通知clockevent模組系統suspend事件。

3、resume回呼函式

static void timekeeping_resume(void)
{
struct timekeeper *tk = &timekeeper;
struct clocksource *clock = tk->clock;
unsigned long flags;
struct timespec ts_new, ts_delta;
cycle_t cycle_now, cycle_delta;
bool suspendtime_found = false;

    read_persistent_clock(&ts;_new); ------透過persistent clock記錄醒來的時間點

    clockevents_resume();-----------resume系統中所有的clockevent裝置
clocksource_resume(); ----------resume系統中所有的clocksource裝置

cycle_now = clock->read(clock);
if ((clock->flags & CLOCK_SOURCE_SUSPEND_NONSTOP) &&
cycle_now > clock->cycle_last) {----------------------(1)
u64 num, max = ULLONG_MAX;
u32 mult = clock->mult;
u32 shift = clock->shift;
s64 nsec = 0;

        cycle_delta = (cycle_now – clock->cycle_last) & clock->mask; ---本次suspend的時間
do_div(max, mult);
if (cycle_delta > max) {
num = div64_u64(cycle_delta, max);
nsec = (((u64) max * mult) >> shift) * num;
cycle_delta -= num * max;
}
nsec += ((u64) cycle_delta * mult) >> shift; ----將suspend時間從cycle轉換成ns

        ts_delta = ns_to_timespec(nsec);----將suspend時間從ns轉換成timespec
suspendtime_found = true;
} else if (timespec_compare(&ts;_new, &timekeeping;_suspend_time) > 0) {-----(2)
ts_delta = timespec_sub(ts_new, timekeeping_suspend_time);
suspendtime_found = true;
}

    if (suspendtime_found)
__timekeeping_inject_sleeptime(tk, &ts;_delta); ----------------(3)

    tk->cycle_last = clock->cycle_last = cycle_now; ---更新last cycle的值
tk->ntp_error = 0;
timekeeping_suspended = 0; ----標記完成了suspend/resume過程
timekeeping_update(tk, TK_MIRROR | TK_CLOCK_WAS_SET); --更新shadow timerkeeper
write_seqcount_end(&timekeeper;_seq);
raw_spin_unlock_irqrestore(&timekeeper;_lock, flags);

    touch_softlockup_watchdog();

    clockevents_notify(CLOCK_EVT_NOTIFY_RESUME, NULL); ---通知resume資訊到clockevent
hrtimers_resume(); ---高精度timer相關,另文描述
}

(1)如果timekeeper當前的clocksource在suspend的時候沒有stop,那麼有機會使用精度更高的clocksource而不是persistent clock。前提是clocksource沒有上限溢位,因此才有了cycle_now > clock->cycle_last的判斷(不過,這裡要求clocksource應該有一個很長的overflow的時間)。

(2)如果沒有suspend nonstop的clock,也沒有關係,可以用persistent clock的時間值。

(3)呼叫__timekeeping_inject_sleeptime函式,具體如下:

static void __timekeeping_inject_sleeptime(struct timekeeper *tk,  struct timespec *delta)
{
tk_xtime_add(tk, delta);------將suspend的時間加到real time clock上去
tk_set_wall_to_mono(tk, timespec_sub(tk->wall_to_monotonic, *delta));
tk_set_sleep_time(tk, timespec_add(tk->total_sleep_time, *delta));
tk_debug_account_sleep_time(delta);
}

monotonic clock不計sleep時間,因此wall_to_monotonic要減去suspend的時間值。total_sleep_time當然需要加上suspend的時間值。

本文轉自蝸窩科技

已同步到看一看
贊(0)

分享創造快樂