ARM Linux系統的時鐘機制
1. Linux下有兩類時鐘:
1.1 實時鐘RTC
它由板上電池驅動的“Real Time Clock”也叫做RTC或者叫CMOS時鐘,硬件時鐘。當操作系統關機的時候,用這個來記錄時間,但是對于運行的系統是不用這個時間的。
1.2 系統時鐘
“System clock”也叫內核時鐘或者軟件時鐘,是由軟件根據時間中斷來進行計數的,內核時鐘在系統關機的情況下是不存在的,所以,當操作系統啟動的時候,內核時鐘是要讀取RTC時間來進行時間同步.
2.1 時鐘滴答計時(jiffies)的幾個基本參數
2.1.1 時鐘周期(clock cycle)的頻率-晶振頻率
計時器Timer晶體振蕩器在1秒內產生的時鐘脈沖個數就是時鐘周期的頻率, 要注意把這個Timer的時鐘周期頻率與時鐘中斷的頻率區別開來, Linux用宏CLOCK_TICK_RATE來表示計時器的輸入時鐘脈沖的頻率(比如我的為#define CLOCK_TICK_RATE 1193180),該宏定義在arm/mach-xxx/include/mach/timex.h頭文件中。
2.1.2 時鐘中斷(clock tick)
我們知道當計數器減到0值時,它就在IRQ0上產生一次時鐘中斷,也即一次時鐘中斷, 計數器的初始值決定了要過多少時鐘周期才產生一次時鐘中斷,因此也就決定了一次時鐘滴答的時間間隔長度.
2.1.3 時鐘中斷的頻率(HZ)
即1秒時間內Timer所產生的時鐘中斷次數。確定了時鐘中斷的頻率值后也就可以確定Timer的計數器初值。Linux內核用宏HZ來表示時鐘中斷的頻率,而且在不同的平臺上HZ有不同的定義值。對于SPARC、MIPS、ARM和i386等平臺HZ的值都是100。該宏在ARM平臺上的定義如下(/arch/arm/include/asm/param.h)
2.1.4 計數器的初始值
計數器的初始值由宏LATCH定義在文件:include/linux/jiffies.h
#define LATCH ((CLOCK_TICK_RATE + HZ/2) / HZ) /* For divider */
2.1.5 jiffies
在 Linux 內核中,時間由一個名為 jiffies 的全局變量衡量,該變量標識系統啟動以來經過的滴答數。在最低的級別上,計算滴答數的方式取決于正在運行的特定硬件平臺;但是,滴答計數通常在一次中斷期間仍然繼續進行。
標準計時器 API 作為 Linux 內核的一部分已經有很長一段時間了(自從 Linux 內核的早期版本開始)。盡管它提供的精確性比高精確度計時器要低,但它對于在處理物理設備時提供錯誤覆蓋的傳統驅動程序超時來說比較理想。在很多情況下,這些超時實際上從不觸發,而是被啟動,然后被刪除。
簡單內核計時器使用計時器輪(timer wheel) 實現。這個主意是由 Finn Arne Gangstad 在 1997 年首次引入的。它不理睬管理大量計時器的問題,而是很好地管理數量合理的計時器 — 典型情況。(原始計時器實現只是按照過期順序將計時器實現雙重鏈接。盡管在概念上比較簡單,但這種方法是不可伸縮的。)時間輪是一個 buckets 集合,其中每個 bucker 表示將來計時器過期的一個時間塊。這些 buckets 使用基于 5 個 bucket 的對數時間定義。使用 jiffies 作為時間粒度,定義了幾個組,它們表示將來的過期時段(其中每個組通過一列計時器表示)。計時器插入使用具有 O(1) 復雜度的列表操作發生,過期發生在 O(N) 時間內。計時器過期以串聯的形式出現,其中計時器被從高粒度 buckets 刪除,然后隨著它們的過期時間的下降被插入到低粒度 buckets 中。現在我們查看一下針對這個計時器實現的 API。
2.2 計時器 API
Linux 提供了一個簡單的 API 來構造和管理計時器。它包含一些函數(和助手函數),用于創建、取消和管理計時器。
計時器通過 timer_list 結構定義,該結構包括實現一個計時器所需的所有數據(其中包括列表指針和在編譯時配置的可選計時器統計數據)。從用戶角度看,timer_list 包含一個過期時間,一個回調函數(當/如果計時器過期),以及一個用戶提供的上下文。用戶必須初始化計時器,可以采取幾種方法,最簡單的方法是調用 setup_timer,該函數初始化計時器并設置用戶提供的回調函數和上下文。或者,用戶可以設置計時器中的這些值(函數和數據)并簡單地調用 init_timer。注意,init_timer 由 setup_timer 內部調用。
[cpp] view plain copy- void init_timer( struct timer_list *timer );
- void setup_timer( struct timer_list *timer,
- void (*function)(unsigned long), unsigned long data );
擁有一個經過初始化的計時器之后,用戶現在需要設置過期時間,這通過調用 mod_timer 來完成。由于用戶通常提供一個未來的過期時間,他們通常在這里添加 jiffies 來從當前時間偏移。用戶也可以通過調用 del_timer 來刪除一個計時器(如果它還沒有過期):
- int mod_timer( struct timer_list *timer, unsigned long expires );
- void del_timer( struct timer_list *timer );
最后,用戶可以通過調用 timer_pending(如果正在等待,將返回 1)來發現計時器是否正在等待(還沒有發出):
- int timer_pending( const struct timer_list *timer );
2.3 計時器示例
我們來檢查一下這些 API 函數的實際運行情況。下面的代碼提供了一個簡單的內核模塊,用于展示簡單計時器 API 的核心特點。在 init_module 中,您使用 setup_timer 初始化了一個計時器,然后調用 mod_timer 來啟動它。當計時器過期時,將調用回調函數 my_timer_callback。最后,當您刪除模塊時,計時器刪除(通過 del_timer)發生。(注意來自 del_timer 的返回檢查,它確定計時器是否還在使用。)
[cpp] view plain copy- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/timer.h>
- MODULE_LICENSE("GPL");
- static struct timer_list my_timer;
- void my_timer_callback( unsigned long data )
- {
- printk( "my_timer_callback called (%ld).\n", jiffies );
- }
- int init_module( void )
- {
- int ret;
- printk("Timer module installing\n");
- // my_timer.function, my_timer.data
- setup_timer( &my_timer, my_timer_callback, 0 );
- printk( "Starting timer to fire in 200ms (%ld)\n", jiffies );
- ret = mod_timer( &my_timer, jiffies + msecs_to_jiffies(200) );
- if (ret) printk("Error in mod_timer\n");
- return 0;
- }
- void cleanup_module( void )
- {
- int ret;
- ret = del_timer( &my_timer );
- if (ret) printk("The timer is still in use...\n");
- printk("Timer module uninstalling\n");
- return;
- }
3. 高精確度計時器(hrtimer)
高精確度計時器(簡稱 hrtimers)提供一個高精確度的計時器管理框架,這個框架獨立于此前討論過的標準計時器框架,原因是合并這兩個框架太復雜。盡管計時器在 jiffies 粒度上運行,hrtimers 在納秒粒度上運行。
hrtimer 框架的實現方式與標準計時器 API 不同。hrtimer 不使用 buckets 和串聯操作,而是維護一個按時間排序的計時器數據結構(按時間順序插入計時器,以最小化激活時的處理)。這個數據結構是一個 “紅-黑” 樹,對于注重性能的應用程序很理想(且恰好作為內核中的一個庫普遍可用)。
hrtimer 框架作為內核中的一個 API 可用,用戶空間應用程序也可以通過 nanosleep、itimers 和 Portable Operating System Interface (POSIX)-timers interface 使用它。hrtimer 框架被主線化(mainlined)到 2.6.21 內核中。
3.1 高精確度計時器 API
hrtimer API 與傳統 API 有些相似,但它們之間的一些根本差別是它能夠進行額外的時間控制。應該注意的第一點是:時間不是用 jiffies 表示的,而是以一種名為 ktime 的特殊數據類型表示。這種表示方法隱藏了在這個粒度上有效管理時間的一些細節。hrtimer API 正式確認(formalize)了絕對時間和相對時間之間的區別,要求調用者指定類型。
[cpp] view plain copy- union ktime {
- s64 tv64;
- #if BITS_PER_LONG != 64 && !defined(CONFIG_KTIME_SCALAR)
- struct {
- # ifdef __BIG_ENDIAN
- s32 sec, nsec;
- # else
- s32 nsec, sec;
- # endif
- } tv;
- #endif
- };
與傳統的計時器 API 類似,高精確度計時器通過一個結構表示 — 這里是 hrtimer。這個結構從用戶角度定義定時器(回調函數、過期時間等)并包含了管理信息(其中計時器存在于 “紅-黑” 樹、可選統計數據等中)。
定義過程首先通過 hrtimer_init 初始化一個計時器。這個調用包含計時器、時鐘定義和計時器模式(絕對或相對)。使用的時鐘在 ./include/linux/time.h 中定義,表示系統支持的各種時鐘(比如實時時鐘或者單一時鐘,后者只表示從一個起點[比如系統啟動]開始的時間)。計時器被初始化之后,就可以通過 hrtimer_start 啟動。這個調用包含過期時間(在 ktime_t 中)和時間值的模式(絕對或相對值)。
[cpp] view plain copy
- struct hrtimer {
- struct rb_node node;
- ktime_t _expires;
- ktime_t _softexpires;
- enum hrtimer_restart (*function)(struct hrtimer *);
- struct hrtimer_clock_base *base;
- unsigned long state;
- #ifdef CONFIG_TIMER_STATS
- int start_pid;
- void *start_site;
- char start_comm[16];
- #endif
- };
[cpp] view plain copy
- void hrtimer_init( struct hrtimer *time, clockid_t which_clock,
- enum hrtimer_mode mode );
- int hrtimer_start(struct hrtimer *timer, ktime_t time, const
- enum hrtimer_mode mode);
hrtimer 啟動后,可以通過調用 hrtimer_cancel 或 hrtimer_try_to_cancel 來取消。每個函數都包含將被停止的計時器的 hrtimer 引用。這兩個函數的區別在于:hrtimer_cancel 函數試圖取消計時器,但如果計時器已經發出,那么它將等待回調函數結束;hrtimer_try_to_cancel 函數也試圖取消計時器,但如果計時器已經發出,它將返回失敗。
[cpp] view plain copy- int hrtimer_cancel(struct hrtimer *timer);
- int hrtimer_try_to_cancel(struct hrtimer *timer);
可以通過調用 hrtimer_callback_running 來檢查 hrtimer 是否已經激活它的回調函數。注意,這個函數由 hrtimer_try_to_cancel 內部調用,以便在計時器的回調函數被調用時返回一個錯誤。
- int hrtimer_callback_running(struct hrtimer *timer);
3.2 一個 hrtimer 示例
[cpp] view plain copy- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/hrtimer.h>
- #include <linux/ktime.h>
- MODULE_LICENSE("GPL");
- #define MS_TO_NS(x) (x * 1E6L)
- static struct hrtimer hr_timer;
- enum hrtimer_restart my_hrtimer_callback( struct hrtimer *timer )
- {
- printk( "my_hrtimer_callback called (%ld).\n", jiffies );
- return HRTIMER_NORESTART;
- }
- int init_module( void )
- {
- ktime_t ktime;
- unsigned long delay_in_ms = 200L;
- printk("HR Timer module installing\n");
- ktime = ktime_set( 0, MS_TO_NS(delay_in_ms) );
- hrtimer_init( &hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL );
- hr_timer.function = &my_hrtimer_callback;
- printk( "Starting timer to fire in %ldms (%ld)\n", delay_in_ms, jiffies );
- hrtimer_start( &hr_timer, ktime, HRTIMER_MODE_REL );
- return 0;
- }
- void cleanup_module( void )
- {
- int ret;
- ret = hrtimer_cancel( &hr_timer );
- if (ret) printk("The timer was still in use...\n");
- printk("HR Timer module uninstalling\n");
- return;
- }
關于 hrtimer API,還有許多內容這里沒有涉及到。一個有趣的方面是它能夠定義回調函數的執行上下文(比如在 softirq 或 hardiirq 上下文中)。您可以在 ./include/linux/hrtimer.h 文件中進一步了解 hrtimer API。