歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux技術 >> Linux驅動學習筆記之一——高精度定時器(1)

Linux驅動學習筆記之一——高精度定時器(1)

日期:2017/3/3 12:05:14   编辑:Linux技術
突兀地說高精度定時器,感覺摸不著頭腦,至少初學者會茫然,但是從字面上來理解,很簡單,定時器嘛,精度高點,然後,就沒有然後了。其實差不多就是這麼回事,只是裡面涉及到了一些別的細節上的問題。
工欲善其事必先利其器,在開始講之前,我們先利一下器:
2 相關的用到的幾個源代碼文件以及其路徑如下:
Hrtimers.txt (linux-3.2.12\documentation\timers)
Hrtimer.c (linux-3.2.12\kernel)
Hrtimer.h (linux-3.2.12\include\linux)
2 單純的在高精度定時器模式下操作高精度定時器,整個操作框架如下:
初始化hrtimer_init,通過hetimer結構體設置相關數據,比如定時時長等->開啟定時器hrtimer_start->運行高精度定時器hrtimer_run_queues->觸發中斷,調用中斷回調函數,hrtimer_interrupt->移除高精度定時器remove_hrtimer.
讀者現在腦子裡有一個框架,具體驅動細節下文將一一闡述。
先概述一下,可能會有些生僻的東西在裡面難理解,不要緊,後面會有相關的代碼和例子來解釋。
? 高精度定時器按照時間在一棵紅黑樹上排序。
? 他們獨立於周期時鐘,采用納秒時間戳而非jiffies的時間規格。
先把Linux代碼裡面的高精度定時器相關的文檔拿出來,看看他的介紹,然後我再解釋一下,文檔路徑:Hrtimers.txt (linux-3.2.12\documentation\timers)
文檔內容……說實話文檔有點長,而且Linux的文檔維護度不是很高,我從裡面找了幾句話翻譯出來,然後解釋一下:
? This patch introduces a new subsystem for high-resolution kernel timers.這句話裡的patch這個單詞有點意思,他是說高精度定時器作為一個補丁包被安裝到系統裡的,在2.6.16之前是沒有這個概念的。
? 第二點,英文太長就不貼了,是說為什麼要用高精度定時器,因為每個系統都存在定時器,當然精度不高,相比較而言就稱之為低精度定時器。說白了就是需要高精度。
? 第三點,高精度定時器還有個特點,就是他的框架在編譯的時候是在內核裡,但是如果沒有配置高精度定時器,那麼高精度定時器是按照普通的定時器來運行。
? 最後一點,高精度定時器是采用紅黑樹算法實現的,而普通的定時器是采用時間輪循算法實現的.
? 另外,文檔中還解釋了很多比如時鐘源、數據結構、紅黑樹等等問題,這些問題在下面分開闡述。
一、相關的數據結構
高分辨率定時器所涉及的數據結構,我們從這幾大方面考慮:
關於來源:就是說這個時鐘是怎麼來的,在hrtimer.h中定義了一個結構體,代碼如下:
struct hrtimer_clock_base {
struct hrtimer_cpu_base *cpu_base;
int index; //用於區分時鐘的屬性(一共有兩種,下面將會提及)
clockid_t clockid; //每個CPU所支持的時鐘的ID
struct timerqueue_head active; //正在啟用的定時器的紅黑樹根節點
ktime_t resolution; //時鐘的分辨率,納秒
ktime_t (*get_time)(void); //用來恢復當前時鐘
ktime_t softirq_time; //在軟中斷中運行高精度定時器隊列的時間
ktime_t offset; //修改定時器時鐘的額偏移量
};
關於上面的幾個元素,,有些東西解釋一下。
高分辨率定時器可以基於兩種時鐘(時鐘基礎,clock base):一種是單調時鐘(CLOCK_MONOTONIC),在系統啟動時,從0開始;另一種時鐘(CLOCK_REALTIME)表示系統的實際時間。上文的結構體struct hrtimer_clock_base中,index元素就是用來區分是CLOCK_MONOTONIC還是CLOCK_REALTIME時鐘的。對於系統的每一個CPU都提供了一個包含這兩種時鐘基礎的數據結構,每一個時候總時鐘基礎都有一個紅黑樹,來排序所有待決的高精度定時器,而每個CPU都提供兩個時鐘基礎(單調時鐘和實際時間),所有定時器都按過期時間在紅黑樹上排序,如果定時器已經到期但其處理程序回調函數尚未執行,則從紅黑樹遷移到一個鏈表中。在調整實時時鐘的時候,會造成存儲在CLOCK_REALTIME時鐘基礎上的定時器的過期時間值與當前實際時間之間的偏差。offset字段有助於修正這種情況,他表示定時器需要校正的偏移量。由於這只是一種臨時效應,很少發生。
在認識時鐘源之前,我們其實還應該認識一個結構體struct hrtimer,代碼如下:
struct hrtimer {
struct timerqueue_node node; //定時器隊列節點, 同時還管理 node.expires,高精度定時器的絕對失效時間在其內部的算法這個時間和定時器基於的時鐘有關(就是上文說的那兩種時基).
ktime_t _softexpires; //絕對的最早的到期時間
enum hrtimer_restart (*)(struct hrtimer *); //定時器到期回調函數
struct hrtimer_clock_base *base; //指向時基的指針(每個CPU,每個時鐘)
unsigned long state; //狀態信息,用來看位值
#ifdef CONFIG_TIMER_STATS
int start_pid; //定時器統計區域存儲的開始計時的任務的pid
void *start_site; //定時器存放當前定時的開始值
char start_comm[16]; //定時器統計區域名稱開始計時的存儲過程
#endif
};
以上這個結構體,對於用戶來說,只需關心三點,第一是字段,這個是定時器失效後的回調函數,第二是expires,這個表示到期時間,第三是最後一句話,高精度定時器結構體的使用必須經過 hrtimer_init()函數的初始化,hrtimer_init()函數屬於應用接口,所以放在後面說。這裡還有一個問題,也是高精度定時器的核心的問題,就是紅黑樹在高精度定時器的應用問題,當然現在講這個有點早,但是先讓讀者心裡有這麼個底,Linux的傳統定時器通過時間輪算法實現(timer.c),但hrtimer通過紅黑樹算法實現。在struct
hrtimer裡面有一個node域,類型為struct timerqueue_node,這個域代表了hrtimer在紅黑樹中的位置,注意一下,我參考的源代碼是3.2.12版本,在2.6.X版本中這個域的格式為struct rb_node。這裡先跟讀者打聲招呼,有這麼回事,等在將具體用法時,我們在說說是如何實現的。
兩個重要的結構體說完了,由於要兼容多核處理器,因此會涉及到每個CPU的時基,結構體struct hrtimer_cpu_base就是用來定義每個CPU的時鐘的,目前每個CPU只對應於單調時鐘和實時時鐘,結構體如下:
struct hrtimer_cpu_base { //單個CPU時基結構體
raw_spinlock_t lock; //鎖定相關的時基和定時器,自旋鎖
unsigned long active_bases; //用活動的定時器標記基礎的位字段
#ifdef CONFIG_HIGH_RES_TIMERS
ktime_t expires_next; //將要到期的下一個時間的絕對時間
int hres_active; //高分辨率模式的狀態,布爾變量
int hang_detected; //最新的被發現的掛起的高精度定時器中斷
unsigned long nr_events; //高精度定時器中斷總數
unsigned long nr_retries; //高精度定時器中斷重試總數
unsigned long nr_hangs; //高精度定時器中斷掛起總數
ktime_t max_hang_time; //在高精度定時器中斷觸發最長時間
#endif
struct hrtimer_clock_base clock_base[HRTIMER_MAX_CLOCK_BASES]; //此CPU時基指針
};
上面的三個結構體應該是最基礎的,定義了高精度定時器相關的功能和元素,並且每一個CPU都全套一個定義的結構體,然後初始化hrtimers。
基本的結構體講完了,下面開始講API接口。
首先是配置並初始化hrtimers的API。在開始的時候我們講struct hrtimer的時候,提到要使用struct hrtimer要先初始化,函數聲明代碼如下:
void hrtimer_init(struct hrtimer *timer, clockid_t clock_id,
enum hrtimer_mode mode) //給定時鐘初始化定時器
{
debug_init(timer, clock_id, mode);
__hrtimer_init(timer, clock_id, mode);
}
以上函數實現了一個高精度定時器的初始化,下面是相關元素的解釋:
/**
* hrtimer_init – 給定時鐘初始化定時器
* @timer: 將要被初始化的定時器
* @clock_id: 將要被用到的時鐘
* @mode: 定時器模式 abs/rel
*/
mode可以使用五個常數,如下:
enum hrtimer_mode {
HRTIMER_MODE_ABS = 0x0, /* 時間是絕對的 */
HRTIMER_MODE_REL = 0x1, /*時間是相對的 */
HRTIMER_MODE_PINNED = 0x02, /* 定時器被綁定到CPU */
HRTIMER_MODE_ABS_PINNED = 0x02,
HRTIMER_MODE_REL_PINNED = 0x03,
};
hrtimer_init()函數裡面調用了__hrtimer_init()函數,下面是該函數的原型:
static void __hrtimer_init(struct hrtimer *timer, clockid_t clock_id,enum hrtimer_mode mode)
{
struct hrtimer_cpu_base *cpu_base;
int base;
memsettimer, 0, sizeof(struct hrtimer));
cpu_base = &__raw_get_cpu_var(hrtimer_bases);
if (clock_id == CLOCK_REALTIME && mode != HRTIMER_MODE_ABS)
clock_id = CLOCK_MONOTONIC;
base = hrtimer_clockid_to_base(clock_id);
timer->base = &cpu_base->clock_base[base];
timerqueue_init(&timer->node);
#ifdef CONFIG_TIMER_STATS
timer->start_site = NULL;
timer->start_pid = -1;
memset(timer->start_comm, 0, TASK_COMM_LEN);
#endif
}
__hrtimer_init()函數調用了struct hrtimer_cpu_base結構體對CPU進行相關的初始化,並使用memset()函數,原型如下:
void *memset(void *s, int c, size_t n)
{
int i;
char *ss = s;
for (i = 0; i < n; i++)
ss[i] = c;
return s;
}
這個函數清空了memory裡面的東西,完成了初始化,memset(timer, 0, sizeof(struct hrtimer))。
在這裡注意一下,還是前面說到的一個問題,我用的源代碼是3.2.12的,而2.6.X的源代碼裡所提供的,其實只有兩個常數
Copyright © Linux教程網 All Rights Reserved