1. 程式人生 > >[linux]linux核心時間管理基礎

[linux]linux核心時間管理基礎

一,linux時間管理基礎
http://blog.csdn.net/droidphone/article/details/7975694
http://blog.csdn.net/smilingjames/article/details/6205540
linux所有時間基礎都是以低層硬體為基礎的,低層硬體有GPT和cpu local timer,比如GPT的時鐘源為13M HZlinux低層時間的架構分為clock source,clock event device,clock source上層分為Xtimer和Hrtimer,Xtimer主要是指牆上時間(開機的時候從RTC暫存器讀取牆上時間),Hrtimer主要是高精度的計時器,精度可以到ns級別,Clock event Device向上層提供jiffies以及時間輪的概念,比如程序切換的最小精度是10ms。

struct clocksource,定義了一個clock device的基本屬性及行為, 這些clock device一般都有計數,定時, 產生中斷能力, 比如GPT
struct clock_event_device  Clock event的主要作用是分發clock事件及設定下一次觸發條件. 在沒有clock event之前,時鐘中斷都是週期性地產生, 也就是熟知的jiffies和HZ

二:jiffies和HZ的概念
在ARM系統上HZ的大小一般為100,表示1s內有100個節拍,jiffies表示的是系統自從啟動以來的節拍總數,jiffies一般為unsigned long型別,所以可能會溢位。比如:unsigned long jiffies; unsigned long timeout=jiffies+HZ/2;表示的是未來的0.5s
jiffies迴繞的問題:

01.unsigned long jiffies;
02.unsigned long timeout = jiffies + HZ/2;
03.//......
04.if (timeout > jiffies) {
05.        //沒有超時,很好
06.}
07.else {
08.        //超時了,發生錯誤
09.}
其中jiffies是個不斷在增大的unsigned long,timeout可以看作比jiffies“大不了多少”的unsigned long。當jiffies變得比2^32-1還要大的時候會發生溢位,“迴繞”(wrap around)到0附近。此時,判斷語句為真,雖然實際上超時了,但是判斷為沒有超時。
Linux核心提供了一組巨集解決這個問題。其中巨集time_after(a, b)是考慮可能的溢位情況後判斷時間a是否在時間b之後(即“b < a”)。
01.#define time_after(a, b) ((long)(b) - (long)(a) < 0)

三,Clock Source Struct

1.  struct clocksource {  
6.      cycle_t (*read)(struct clocksource *cs);  
7.      cycle_t cycle_last;  
8.      cycle_t mask;  
9.      u32 mult;  
10.     u32 shift;  
11.     u64 max_idle_ns;  
12.     u32 maxadj;  
13. #ifdef CONFIG_ARCH_CLOCKSOURCE_DATA  
14.     struct arch_clocksource_data archdata;  
15. #endif    
17.     const char *name;  
18.     struct list_head list;  
19.     int rating;  
20.     int (*enable)(struct clocksource *cs);  
21.     void (*disable)(struct clocksource *cs);  
22.     unsigned long flags;  
23.     void (*suspend)(struct clocksource *cs);  
24.     void (*resume)(struct clocksource *cs);  
25.   
26.     /* private: */  
27. #ifdef CONFIG_CLOCKSOURCE_WATCHDOG  
28.     /* Watchdog related data, used by the framework */  
29.     struct list_head wd_list;  
30.     cycle_t cs_last;  
31.     cycle_t wd_last;  
32. #endif  
33. } ____cacheline_aligned; 

1  rating:時鐘源的精度
同一個裝置下,可以有多個時鐘源,每個時鐘源的精度由驅動它的時鐘頻率決定,比如一個由10MHz時鐘驅動的時鐘源,他的精度就是100nS。clocksource結構中有一個rating欄位,代表著該時鐘源的精度範圍,它的取值範圍如下:
1--99: 不適合於用作實際的時鐘源,只用於啟動過程或用於測試;
100--199:基本可用,可用作真實的時鐘源,但不推薦;
200--299:精度較好,可用作真實的時鐘源;
300--399:很好,精確的時鐘源;
400--499:理想的時鐘源,如有可能就必須選擇它作為時鐘源;
2  read回撥函式
時鐘源本身不會產生中斷,要獲得時鐘源的當前計數,只能通過主動呼叫它的read回撥函式來獲得當前的計數值,注意這裡只能獲得計數值,也就是所謂的cycle,要獲得相應的時間,必須要藉助clocksource的mult和shift欄位進行轉換計算。
1.3  mult和shift欄位
因為從clocksource中讀到的值是一個cycle計數值,要轉換為時間,我們必須要知道驅動clocksource的時鐘頻率F,一個簡單的計算就可以完成:
t = cycle/F;
可是clocksource並沒有儲存時鐘的頻率F,因為使用上面的公式進行計算,需要使用浮點運算,這在核心中是不允許的,因此,核心使用了另外一個變通的辦法,根據時鐘的頻率和期望的精度,事先計算出兩個輔助常數mult和shift,然後使用以下公式進行cycle和t的轉換:
t = (cycle * mult) >> shift;只要我們保證:F = (1 << shift) / mult;核心內部使用64位進行該轉換計算:
xtime 是人們日常所使用的牆上時間
monotonic time  該時間自系統開機後就一直單調地增加,它不像xtime可以因使用者的調整時間而產生跳變,不過該時間不計算系統休眠的時間,也就是說,系統休眠時,monotoic時間不會遞增。核心用timekeeper結構來組織與時間相關的資料,它的定義如下

四, Struct timekeeper

14struct timekeeper {
15      /* Current clocksource used for timekeeping. */
16      struct clocksource     *clock;
17      /* NTP adjusted clock multiplier */
18      u32                    mult;
19      /* The shift value of the current clocksource. */
20      u32                    shift;
21      /* Number of clock cycles in one NTP interval. */
22      cycle_t                cycle_interval;
23      /* Last cycle value (also stored in clock->cycle_last) */
24      cycle_t                cycle_last;
25      /* Number of clock shifted nano seconds in one NTP interval. */
26      u64                    xtime_interval;
27      /* shifted nano seconds left over when rounding cycle_interval */
28      s64                    xtime_remainder;
29      /* Raw nano seconds accumulated per NTP interval. */
30      u32                    raw_interval;
31
32      /* Current CLOCK_REALTIME time in seconds */
33      u64                    xtime_sec;
34      /* Clock shifted nano seconds */
35      u64                    xtime_nsec;
36
37      /* Difference between accumulated time and NTP time in ntp
38      * shifted nano seconds. */
39      s64                    ntp_error;
40      /* Shift conversion between clock shifted nano seconds and
41      * ntp shifted nano seconds. */
42      u32                    ntp_error_shift;
43
44      /*
45      * wall_to_monotonic is what we need to add to xtime (or xtime corrected
46      * for sub jiffie times) to get to monotonic time.  Monotonic is pegged
47      * at zero at system boot time, so wall_to_monotonic will be negative,
48      * however, we will ALWAYS keep the tv_nsec part positive so we can use
49      * the usual normalization.
50      *
51      * wall_to_monotonic is moved after resume from suspend for the
52      * monotonic time not to jump. We need to add total_sleep_time to
53      * wall_to_monotonic to get the real boot based time offset.
54      *
55      * - wall_to_monotonic is no longer the boot time, getboottime must be
56      * used instead.
57      */
58      struct timespec        wall_to_monotonic;
59      /* Offset clock monotonic -> clock realtime */
60      ktime_t                offs_real;
61      /* time spent in suspend */
62      struct timespec        total_sleep_time;
63      /* Offset clock monotonic -> clock boottime */
64      ktime_t                offs_boot;
65      /* The raw monotonic time for the CLOCK_MONOTONIC_RAW posix clock. */
66      struct timespec        raw_time;
67      /* The current UTC to TAI offset in seconds */
68      s32                    tai_offset;
69      /* Offset clock monotonic -> clock tai */
70      ktime_t                offs_tai;
71
72};

核心定義了一個變數wall_to_monotonic,記錄了牆上時間和monotonic時間之間的偏移量,當需要獲得monotonic時間時,把xtime和wall_to_monotonic相加即可,
Timekeeper的初始化,timekeeper的初始化由timekeeping_init完成,該函式在start_kernel的初始化序列中被呼叫,時間的更新:xtime一旦初始化完成後,timekeeper就開始獨立於RTC,利用自身關聯的clocksource進行時間的更新操作,根據核心的配置項的不同,更新時間的操作發生的頻度也不盡相同,如果沒有配置NO_HZ選項,通常每個tick的定時中斷週期,do_timer會被呼叫一次。