Linux設備驅動——內核定時器

內核定時器使用linux

  

內核定時器是內核用來控制在將來某個時間點(基於jiffies)調度執行某個函數的一種機制,其實現位於 <Linux/timer.h> 和 kernel/timer.c 文件中。算法

被調度的函數確定是異步執行的,它相似於一種「軟件中斷」,並且是處於非進程的上下文中,因此調度函數必須遵照如下規則:緩存

1) 沒有 current 指針、不容許訪問用戶空間。由於沒有進程上下文,相關代碼和被中斷的進程沒有任何聯繫。數據結構

2) 不能執行休眠(或可能引發休眠的函數)和調度。併發

3) 任何被訪問的數據結構都應該針對併發訪問進行保護,以防止競爭條件。 異步

 

內核定時器的調度函數運行過一次後就不會再被運行了(至關於自動註銷),但能夠經過在被調度的函數中從新調度本身來週期運行。函數

在SMP系統中,調度函數老是在註冊它的同一CPU上運行,以儘量得到緩存的局域性。spa

 

內核定時器的數據結構.net

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.hvoid do_gettimeofday(struct timeval *tv)

相關文章
相關標籤/搜索