linux內核筆記之時間管理(二) : 低精度timer

低精度定時器的API

初始化定時器:
void init_timer(struct timer_list * timer);
增加定時器:
void add_timer(struct timer_list * timer);
刪除定時器:
int del_timer(struct timer_list * timer);
修改定時器的expire:
int mod_timer(struct timer_list *timer, unsigned long expires);

使用示例

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/timer.h>

struct timer_list mytimer;//定義一個定時器
void  mytimer_func(unsigned long data)
{

      printk("timer data: %d\n", data);
}

static int __init test_init (void)
{
    printk("test init\n");
    init_timer(&mytimer);     //初始化定時器
    mytimer.expires = jiffies + 100;//設定超時時間100代表1秒
    mytimer.data = 200;    //傳遞給定時器超時函數的值
    mytimer.function = mytimer_func;//設置定時器超時函數
    add_timer(&mytimer); //添加定時器,定時器開始生效
    return 0;
}

static void __exit test_exit (void)

{
    del_timer(&mytimer);//卸載模塊時,刪除定時器
    printk("test exit\n");
}

module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");

實現機制

低精度定時器的實現機制是時間輪算法。
每一個CPU維護一個tevc_base結構,爲這個CPU的時間輪

static DEFINE_PER_CPU(struct tvec_base *, tvec_bases) = &boot_tvec_bases;  

tevc_base結構:

struct tvec_base {  
    spinlock_t lock;  
    struct timer_list *running_timer;  
    unsigned long timer_jiffies;  
    unsigned long next_timer;  
    struct tvec_root tv1;  
    struct tvec tv2;  
    struct tvec tv3;  
    struct tvec tv4;  
    struct tvec tv5;  
};  

running_timer 該字段指向當前cpu正在處理的定時器所對應的timer_list結構。
next_timer 該字段指向該cpu下一個即將到期的定時器。
tv1~5代表5個wheel。
tv1是轉速最快的wheel,TVR_SIZE(256)個jiffies內到期的定時器都會掛在tv1的某個鏈表頭中。
tv2是轉速第二快的wheel,裏面掛的定時器超時jiffies在2^8 ~ 2^(8+6)之間。
tv3是轉速第三快的wheel,超時jiffies在2^(8+6) ~ 2^(8+2*6)之間。
tv4、tv5以此類推。

tv1~tv5的size分別爲256,64,64,64,64,表示的範圍爲256+64+64+64+64 = 2^32.

下圖爲時間輪的結構:
這裏寫圖片描述

內核實現:
(參考博文:http://blog.csdn.net/walkingman321/article/details/6101536

add_timer是__mod_timer函數的一個封裝,__mod_timer最終會調用internal_add_timer。

static void internal_add_timer(struct tvec_base *base, struct timer_list *timer)
{
       unsigned long expires = timer->expires;
       unsigned long idx = expires - base->timer_jiffies; // idx爲定時器距離超時還有多少jiffies
       struct list_head *vec;

       // 如果idx少於TVR_SIZE(256),把定時器加入tv1
       if (idx < TVR_SIZE) {                        
              int i = expires & TVR_MASK;
              vec = base->tv1.vec + i;
       }
    // 如果idx少於2^(8+6)次方,加入tv2
    else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
              int i = (expires >> TVR_BITS) & TVN_MASK;
              vec = base->tv2.vec + i;
        }
    // 類推,加入tv3
    else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {
              int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
              vec = base->tv3.vec + i;
        } 
    // 類推,加入tv4
    else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {
              int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
              vec = base->tv4.vec + i;
       } 
       // 如果idx < 0,說明定時器已經超時,應該馬上被調度,把它加入base->timer_jiffies,
       // 即base的當前jiffies對應的隊列。這裏要注意的是,這個函數在spin_lock_irq被調 
       // 用之後調用,所以不會與定時器調度函數產生競態。
    else if ((signed long) idx < 0) {
              /*
               * Can happen if you add a timer with expires == jiffies,
               * or you set a timer to Go off in the past
               */
              vec = base->tv1.vec + (base->timer_jiffies & TVR_MASK);
       } 
       // 加入tv5隊列。
       else {
              int i;
              /* If the timeout is larger than 0xffffffff on 64-bit
               * architectures then we use the maximum timeout:
               */
              if (idx > 0xffffffffUL) {
                     idx = 0xffffffffUL;
                     expires = idx + base->timer_jiffies;
              }
              i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
              vec = base->tv5.vec + i;
       }
       trace_timer_set(timer);
       /*
        * Timers are FIFO:
        */
       list_add_tail(&timer->entry, vec);
}

定時器加入tv1中使用的下標索引是定時器到期時間expires的低8位,所以假設當前的base.timer_jiffies值是0x34567826,則馬上到期的定時器是在tv1.vec[0x26]中,如果這時候系統加入一個在jiffies值0x34567828到期的定時器,他將會加入到tv1.vec[0x28]中,運行兩個tick後,base.timer_jiffies的值會變爲0x34567828,很顯然,在每次tick事件中,定時器系統只要以base.timer_jiffies的低8位作爲索引,取出tv1中相應的鏈表,裏面正好包含了所有在該jiffies值到期的定時器列表。

當jiffies的低8位爲0時,tv1到了觸發點,調用cascade函數把tv2的某一格定時器搬到tv1。tv2的cascade函數返回0時,代表tv2也到了觸發點,於是又觸發下一級時間輪tv3。依此類推。

static int cascade(struct tvec_base *base, struct tvec *tv, int index)  
{  
    /* cascade all the timers from tv up one level */  
    struct timer_list *timer, *tmp;  
    struct list_head tv_list;  

    list_replace_init(tv->vec + index, &tv_list);  //  移除需要遷移的鏈表  

    /* 
     * We are removing _all_ timers from the list, so we 
     * don't have to detach them individually. 
     */  
    list_for_each_entry_safe(timer, tmp, &tv_list, entry) {  
        BUG_ON(tbase_get_base(timer->base) != base);  
        //  重新加入到定時器系統中,實際上將會遷移到下一級的tv數組中  
        internal_add_timer(base, timer);    
    }  

    return index;  
}  

timer的調度在軟中斷中完成,在init_timers函數中調用

open_softirq(TIMER_SOFTIRQ, run_timer_softirq)

註冊定時器對應的軟中斷處理函數。

static void run_timer_softirq(struct softirq_action *h)
{
       struct tvec_base *base = __get_cpu_var(tvec_bases);
       hrtimer_run_pending();    // 高精度時鐘的處理
       // 如果jiffies超過了base->timer_jiffies,那麼調用時鐘處理函數
       if (time_after_eq(jiffies, base->timer_jiffies))
              __run_timers(base);
}

run_timers

static inline void __run_timers(struct tvec_base *base)  
{  
    struct timer_list *timer;  

    spin_lock_irq(&base->lock);  
   /* 同步jiffies,在NO_HZ情況下,base->timer_jiffies可能落後不止一個tick  */  
    while (time_after_eq(jiffies, base->timer_jiffies)) {    
        struct list_head work_list;  
        struct list_head *head = &work_list;  
        /*  計算到期定時器鏈表在tv1中的索引  */  
        int index = base->timer_jiffies & TVR_MASK;    

        /* 
         * tv2--tv5定時器列表遷移處理 
         */  
        if (!index &&  
            (!cascade(base, &base->tv2, INDEX(0))) &&                
                (!cascade(base, &base->tv3, INDEX(1))) &&        
                    !cascade(base, &base->tv4, INDEX(2)))    
            cascade(base, &base->tv5, INDEX(3));    
        /*  該cpu定時器系統運行時間遞增一個tick  */                   
        ++base->timer_jiffies;    
       /*  取出到期的定時器*/                                         
       list_replace_init(base->tv1.vec + index, &work_list);  
        /*  遍歷所有的到期定時器  */            
        while (!list_empty(head)){                                      
            void (*fn)(unsigned long);  
            unsigned long data;  

            timer = list_first_entry(head, struct timer_list,entry);  
            fn = timer->function;  
            data = timer->data;  

            timer_stats_account_timer(timer);  

            base->running_timer = timer;    /*  標記正在處理的定時器  */  
            detach_timer(timer, 1);  

            spin_unlock_irq(&base->lock);  
            call_timer_fn(timer, fn, data);  /*  調用定時器的回調函數  */  
            spin_lock_irq(&base->lock);  
        }  
    }  
    base->running_timer = NULL;  
    spin_unlock_irq(&base->lock);  
}  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章