Linux)時間的起源


Linux 核心中,時間由一個名為 jiffies 的全域變數衡量,該變數識別系統啟動以來經過的滴答數。在最低的級別上,計算滴答數的方式取決于正在執行的特定硬體平台;但是,滴答計數通常在一次中斷期間仍然繼續進行。滴答速率(jiffies 的最不重要的位)可以配置,但在最近針對 x86 2.6 核心中,一次滴答等于 4ms250Hz)。jiffies 全域變數在核心中廣泛使用,目的有几個,其中之一是提供用于計算一個計時器的逾時值的目前絕對時間(稍后將展示一個例子)。



核心計時器


最近的 2.6 核心中有几個不同的計時器型態,其中最簡單、最不精確(但適用于大多數實例)的型態就是計時器 API。這個 API 允許搆造在 jiffies 域(最低 4ms 逾時)中執行的計時器。還有一個高精計時API,它允許搆造在以納秒定義的時間中執行的計時器。根據您的處理器和處理器執行的速度,您的裡程(mileage)可能會不同,但這個 API 的確提供了一種方法來在 jiffies 滴答間隔下排程逾時。


標准計時器


標准計時器 API 作為 Linux 核心的一部分已經有很長一段時間了(自從 Linux 核心的早期版本開始)。儘管它提供的精確性比高精確度計時器要低,但它對于在處理物理裝置時提供錯誤覆寫的傳統驅動程式逾時來說比對理想。在很多情況下,這些逾時實際上從不觸發,而是被啟動,然后被移除。


簡單核心計時器使用計時timer wheel 實現。這個主意是由 Finn Arne Gangstad 1997 年首次引入的。它不理睬管理大量計時器的問題,而是很好地管理數量合理的計時器 — 典型情況。(初始計時器實現只是按照過期順序將計時器實現雙重鏈結。儘管在概念上比對簡單,但這種方法是不可伸縮的。)時間輪是一個 buckets 集合,其中每個 bucker 表示將來計時器過期的一個時間塊。這些 buckets 使用基于 5 bucket 的對數時間定義。使用 jiffies 作為時間粒度,定義了几個組,它們表示將來的過期時段(其中每個組通過一列計時器表示)。計時器插入使用具有 O(1) 複雜度的清單作業發生,過期發生在 O(N) 時間內。計時器過期以串聯的形式出現,其中計時器被從高粒度 buckets 移除,然后隨着它們的過期時間的下降被插入到低粒度 buckets 中。現在我們檢視一下針對這個計時器實現的 API


計時器 API


Linux 提供了一個簡單的 API 來搆造和管理計時器。它包含一些函數(和助手函數),用于建立、取消和管理計時器。


計時器通過 timer_list 架構定義,該架構包括實現一個計時器所需的所有資料(其中包括清單指標和在編譯時配置的可選計時器統計資料)。從用戶角度看,timer_list 包含一個過期時間,一個回調函數(當/如果計時器過期),以及一個用戶提供的內文。用戶必須起始化計時器,可以釆取几種方法,最簡單的方法是呼叫 setup_timer,該函數起始化計時器并設定用戶提供的回調函數和內文。或者,用戶可以設定計時器中的這些值(函數和資料)并簡單地呼叫 init_timer。注意,init_timer setup_timer 內部呼叫。








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 );





計時器示例


我們來檢查一下這些 API 函數的實際執行情況。清单 1 提供了一個簡單的核心模組,用于展示簡單計時器 API 的核心特點。在 init_module 中,您使用 setup_timer 起始化了一個計時器,然后呼叫 mod_timer 來啟動它。當計時器過期時,將呼叫回調函數 my_timer_callback。最后,當您移除模組時,計時器移除(通過 del_timer)發生。(注意來自 del_timer 的傳回檢查,它確定計時器是否還在使用。)



清單 1. 探索簡單計時器 API








                                
#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;
}





您可以在 ./include/linux/timer.h 中進一步了解計時器 API。儘管簡單計時器 API 簡單有效,但它并無法提供實時應用程式所需的准確性。為此,我們來看一下 Linux 最近新增的功能,該功能用于支援精確度更高的計時器。


高精確度計時器


高精確度計時器(簡稱 hrtimers)提供一個高精確度的計時器管理框架,這個框架獨立于此前討論過的計時器框架,原因是合併這兩個框架太複雜。儘管計時器在 jiffies 粒度上執行,hrtimers 在納秒粒度上執行。


hrtimer 框架的實現方式與傳統計時器 API 不同。hrtimer 不使用 buckets 和串聯作業,而是維護一個按時間排序的計時器資料架構(按時間順序插入計時器,以最小化啟動時的處理)。這個資料架構是一個 “紅-黑” 樹,對于注重效能的應用程式很理想(且恰好作為核心中的一個程式庫普遍可用)。


hrtimer 框架作為核心中的一個 API 可用,用戶空間應用程式也可以通過 nanosleepitimers Portable Operating System Interface (POSIX)-timers interface 使用它。hrtimer 框架被主線化(mainlined)到 2.6.21 核心中。


高精確度計時器 API


hrtimer API 與傳統 API 有些相似,但它們之間的一些根本差別是它能夠進行其餘的時間控制。應該注意的第一點是:時間不是用 jiffies 表示的,而是以一種名為 ktime 的特殊資料類別表示。這種表示方法隱藏了在這個粒度上有效管理時間的一些細節。hrtimer API 正式確認(formalize)了絕對時間和相對時間之間的區別,要求呼叫者指定類別。


與傳統的計時器 API 類似,高精確度計時器通過一個架構表示 — 這裡是 hrtimer。這個架構從用戶角度定義定時器(回調函數、過期時間等)并包含了管理資訊(其中計時器存在于 “紅-黑” 樹、可選統計資料等中)。


定義過程首先通過 hrtimer_init 起始化一個計時器。這個呼叫包含計時器、時鐘定義和計時器型態(one-shot restart)。使用的時鐘在 ./include/linux/time.h 中定義,表示系統支援的各種時鐘(比如實時時鐘或者單一時鐘,后者只表示從一個起點[比如系統啟動]開始的時間)。計時器被起始化之后,就可以通過 hrtimer_start 啟動。這個呼叫包含過期時間(在 ktime_t 中)和時間值的型態(絕對或相對值)。








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 函數也嘗試取消計時器,但如果計時器已經發出,它將傳回失敗。








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);





ktime API


本文沒有討論 ktime API,它提供一組丰富的函數來以較高的精確度管理時間。可以在 ./linux/include/ktime.h 中檢視 ktime API


一個 hrtimer 示例


hrtimer API 的使用方法非常簡單,如 清单 2 所示。在 init_module 中,首先定義針對逾時的相對時間(本例中為 200ms)。然后,通過呼叫 hrtimer_init 來起始化您的 hrtimer(使用單一時鐘),并設定回調函數。最后,使用此前建立的 ktime 值啟動計時器。當計時器發出時,將呼叫 my_hrtimer_callback 函數,該函數傳回 HRTIMER_NORESTART,以避免計時器自動重新啟動。在 cleanup_module 函數中,通過呼叫 hrtimer_cancel 來取消計時器。



清單 2. 探索 hrtimer API








                                
#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


 


原文轉貼
http://www.ibm.com/developerworks/cn/linux/l-timers-list/index.html

arrow
arrow
    全站熱搜

    立你斯 發表在 痞客邦 留言(0) 人氣()