內核定時器使用
內核定時器是內核用來控制在未來某個時間點(基於jiffies)調度執行某個函數的一種機制,其實現位於 <Linux/timer.h> 和 kernel/timer.c 文件中。
被調度的函數肯定是異步執行的,它類似於一種“軟件中斷”,而且是處於非進程的上下文中,所以調度函數必須遵守以下規則:
1) 沒有 current 指針、不允許訪問用戶空間。因為沒有進程上下文,相關代碼和被中斷的進程沒有任何聯系。
2) 不能執行休眠(或可能引起休眠的函數)和調度。
3) 任何被訪問的數據結構都應該針對並發訪問進行保護,以防止競爭條件。
內核定時器的調度函數運行過一次后就不會再被運行了(相當於自動注銷),但可以通過在被調度的函數中重新調度自己來周期運行。
在SMP系統中,調度函數總是在注冊它的同一CPU上運行,以盡可能獲得緩存的局域性。
內核定時器的數據結構
struct timer_list { struct list_head entry; unsigned long expires; void (*function)(unsigned long); unsigned long data; struct tvec_base *base; /* ... */ };
其中 expires 字段表示期望定時器執行的 jiffies 值,到達該 jiffies 值時,將調用 function 函數,並傳遞 data 作為參數。當一個定時器被注冊到內核之后,entry 字段用來連接該定時器到一個內核鏈表中。base 字段是內核內部實現所用的。
需要注意的是 expires 的值是32位的,因為內核定時器並不適用於長的未來時間點。
初始化
在使用 struct timer_list 之前,需要初始化該數據結構,確保所有的字段都被正確地設置。初始化有兩種方法。
方法一:
DEFINE_TIMER(timer_name, function_name, expires_value, data);
該宏會定義一個名叫 timer_name 內核定時器,並初始化其 function, expires, name 和 base 字段。
方法二:
struct timer_list mytimer; void init_timer(struct timer_list *timer);
上述init_timer函數將初始化struct timer_list的 entry的next 為 NULL ,並未base指針賦值
tm->expires = ;
tm->function = ;
tm->data = ;
setup_timer(&mytimer, (*function)(unsigned long), unsigned long data); 方法也可以用於初始化定時器並賦值其成員,源代碼為:
static inline void setup_timer(struct timer_list * timer, void (*function)(unsigned long), unsigned long data) { timer->function = function; timer->data = data; init_timer(timer); }
注意,無論用哪種方法初始化,其本質都只是給字段賦值,所以只要在運行 add_timer() 之前,expires, function 和 data 字段都可以直接再修改。
關於上面這些宏和函數的定義,參見 include/linux/timer.h。
注冊
定時器要生效,還必須被連接到內核專門的鏈表中,這可以通過 add_timer(struct timer_list *timer) 來實現。
重新注冊(修改)
要修改一個定時器的調度時間,可以通過調用 mod_timer(struct timer_list *timer, unsigned long expires) 。mod_timer() 會重新注冊定時器到內核,而不管定時器函數是否被運行過。
注銷
注銷一個定時器,可以通過 del_timer(struct timer_list *timer) 或 del_timer_sync(struct timer_list *timer) 。
其中 del_timer_sync 是用在 SMP 系統上的(在非SMP系統上,它等於del_timer),當要被注銷的定時器函數正在另一個 cpu 上運行時,del_timer_sync() 會等待其運行完,所以這個函數會休眠。另外還應避免它和被調度的函數爭用同一個鎖。對於一個已經被運行過且沒有重新注冊自己的定時器而言,注銷函數其實也沒什么事可做。
int timer_pending(const struct timer_list *timer);
這個函數用來判斷一個定時器是否被添加到了內核鏈表中以等待被調度運行。注意,當一個定時器函數即將要被運行前,內核會把相應的定時器從內核鏈表中刪除(相當於注銷)。
使用范例
/* 實現每隔一秒向內核log中打印一條信息 */ #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/time.h> #include <linux/timer.h> static struct timer_list tm; struct timeval oldtv; void callback(unsigned long arg) { struct timeval tv; char *strp = (char*)arg; printk("%s: %lu, %s\n", __func__, jiffies, strp); do_gettimeofday(&tv); printk("%s: %ld, %ld\n", __func__, tv.tv_sec - oldtv.tv_sec, //與上次中斷間隔 s tv.tv_usec- oldtv.tv_usec); //與上次中斷間隔 ms oldtv = tv; tm.expires = jiffies+1*HZ; add_timer(&tm); //重新開始計時 } static int __init demo_init(void) { printk(KERN_INFO "%s : %s : %d - ok.\n", __FILE__, __func__, __LINE__); init_timer(&tm); //初始化內核定時器 do_gettimeofday(&oldtv); //獲取當前時間 tm.function= callback; //指定定時時間到后的回調函數 tm.data = (unsigned long)"hello world"; //回調函數的參數 tm.expires = jiffies+1*HZ; //定時時間 add_timer(&tm); //注冊定時器 return 0; } static void __exit demo_exit(void) { printk(KERN_INFO "%s : %s : %d - ok.\n", __FILE__, __func__, __LINE__); del_timer(&tm); //注銷定時器 } module_init(demo_init); module_exit(demo_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Farsight"); MODULE_DESCRIPTION("Demo for kernel module");
一些和時間相關的內容
linux/jiffies.h
計數值:
jiffies
u64 get_jiffies_64(void)
asm/param.h
每秒觸發中斷的次數
HZ
---------------------------------------------
時間值
秒數=(jiffies(new) - jiffies(old))/HZ
jiffies(new) = jiffies(old) + 秒*HZ
---------------------------------------------
linux/delay.h
延時函數
void ssleep(unsigned int seconds);
void msleep(unsigned int msecs);
---------------------------------------------
時間函數
linux/time.h
void do_gettimeofday(struct timeval *tv)