Linux系統定時器,在核心中扮演著重要角色。核心的許多重要實現如任務排程,工作佇列等均以系統定時器關係密切。系統定時器能以可程式設計的頻率中斷處理,這一中斷叫做軟中斷。此頻率即為每秒的定時器節拍數HZ。HZ的越大,說明定時器節拍越小,執行緒排程的準確性會越高。但HZ設得過大,對一個系統來說並不好,會導CPU開銷過大,反而造成任務排程效率降低。滴答jiffies 變數記錄系統啟動以來,系統定時器已經觸發的次數。也就是說每過一秒jiffies的增量為HZ,一般HZ=100,HZ是可以配置的,在S3C2440 arm linux中配置為200.

下面基於Linux2.6.30.4原始碼來探討其實現原理及其過程。

要理解系統定時器實現原理,先來看看關係到系統定時器的各種資料結構,其具體的描述引數。

結構體structtimer_list來描述timer的引數,其資料結構如下:

struct timer_list {
       structlist_head entry;              //timer雙向連結串列
       unsignedlong expires;             //timer超時變數
 
       void(*function)(unsigned long);   //timer超時回撥函式
       unsignedlong data;                  //傳遞給回撥函式的資料,也就是定時器資料
      struct tvec_base *base;            //timer base向量表用於timer_list的掛載和連結串列管理
                                              //timer的一些擴充套件引數
#ifdef CONFIG_TIMER_STATS       
       void*start_site;
       charstart_comm[16];
       intstart_pid;
#endif
#ifdef CONFIG_LOCKDEP
       structlockdep_map lockdep_map;
#endif
};

其中:

list_entry結構:
struct list_head {
       structlist_head *next, *prev;
};
tevc_base的結構:
struct tvec_base {
       spinlock_tlock;                         //自旋鎖lock
       structtimer_list *running_timer;   //指向已經掛載進來的timer_list
       unsignedlong timer_jiffies;          //timer jiffies用於記錄定時器當前jiffies
       structtvec_root tv1;                  //5組tvec_base,從tv1~tv5,成員數各不相同
       structtvec tv2;                        //其成員數TVR_SIZE,TVN_SIZE決定
       structtvec tv3;
       structtvec tv4;
       structtvec tv5;
} ____cacheline_aligned;

#define TVN_BITS (CONFIG_BASE_SMALL ? 4 :6)
#define TVR_BITS (CONFIG_BASE_SMALL ? 6 :8)
#define TVN_SIZE (1 << TVN_BITS)
#define TVR_SIZE (1 << TVR_BITS)
#define TVN_MASK (TVN_SIZE - 1)
#define TVR_MASK (TVR_SIZE - 1)
 
struct tvec {
       structlist_head vec[TVN_SIZE];   // tv2~t5個數為64的陣列
};                            
 
struct tvec_root {
       structlist_head vec[TVR_SIZE];  //tv1個數為256的陣列
};

可見涉及到系統定時器的資料結構並不多,那麼:對於一個linux系統中,定時器個數可能會很多,而且每個定時器的超時事件時間並不相同,所以如何管理和處理定時器超時事件,關係到核心效能的高低。它根據不同的定時事件,按時間間分組,將新增的timer定時器建成雙向連結串列,然後按照一定方式存放於5組tv1~tv5變數中稱為tec_base。對於對稱式多理器(SMP)系統還考慮到了TIMER從一個CPU遷移到另一個CPU的情況,相應的tev_base也跟著更改。那它在系統是怎樣實現的呢?現在先從一個簡單的系統定時器應用例子來看看它的實現過程:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h>
 
struct  timer_list   my_timer;
 
static void my_function(unsigned   long  data)
{
          static int i = 0;
           printk( "timer’s callback function\n");
           printk( "timer’s data = %lu\n",data);
           return;
}
 
static int my_timer_init(void)
{
              printk(“timerinit…\n”);
           my_timer.data = 0xff;
           my_timer.function = my_function;
           my_timer.expires = jiffies + 3*HZ;
           init_timer(&my_timer);
           add_timer(&my_timer);
           return  0;
}
 
static void my_timer_exit(void)
{
              printk( "timer exit…\n");
}
 
module_init(my_timer_init);
module_exit(my_timer_exit);
MODULE_AUTHOR( "itspy");
MODULE_LICENSE( "GPL");
MODULE_DESCRIPTION("linux kernel timerprogramming");

上面例子,實現了一個定時器事件,將在3 HZ(秒)發生。my_imer_init函式中呼叫到的定時器API只有:

init_timer(&my_timer);   //用於定時器初始化

add_timer(&my_timer);    //增加一個新的定時器到tev_base向量表中

其中init_timer中呼叫了__init_timer(),這個函式才是真正初始化定時器的:

static void __init_timer(struct timer_list*timer,
                      const char *name,
                      struct lock_class_key *key)
{
       timer->entry.next= NULL;                           //對於新增的timer例項,其下一各總是指向NULL。
       timer->base= __raw_get_cpu_var(tvec_bases); //SMP中,獲得當前處理器的tev_base
                                                                   //這個tev_bases是根據一定規律變化的,稍後會將到
    …
}

新增的定時器初始化,就是完成了一個timer_list結構初始化過程。

add_timer()  --> mod_timer()  -->  __mod_timer()

其中:
static inline int
__mod_timer(struct timer_list *timer,unsigned long expires, bool pending_only)
{
       structtvec_base *base, *new_base;
       unsignedlong flags;
       intret;
 
       ret= 0;
       BUG_ON(!timer->function);                 // BUG檢測,確保回撥函式為非空NULL
       base= lock_timer_base(timer, &flags); //獲取本地cpu的tev_base,這是一個臨
                                                          //界資源,裡邊是一個for(;;)迴圈,如果找不到說明已經遷移到了別的CPU
       if (timer_pending(timer)) {                  //當已掛載的timer 定時超時發生後,會被解除安裝摘除
              detach_timer(timer,0);           
              ret= 1;
       }else {
              if(pending_only)                        //新增一個定時器時,pending_only 為 false
                     gotoout_unlock;
       }
 
…
       new_base= __get_cpu_var(tvec_bases);   //獲取本地cpu中的tevc_bases
       if(base != new_base) {     //由於之前base 可能已被遷移到其他CPU的 tev_base向量表,會造成 base != new_base
              if(likely(base->running_timer != timer)) { //由於在timer正在執行時,我們不能直接更改base,位與一個叫做DEFERRABLE(可延後標誌)後處理
                     /*See the comment in lock_timer_base() */    
                     timer_set_base(timer,NULL);
                     spin_unlock(&base->lock);
                     base= new_base;
                     spin_lock(&base->lock);
                     timer_set_base(timer,base);
              }
       }
 
       timer->expires= expires;                
       internal_add_timer(base,timer);       //分析timer expires及建表過程
 
out_unlock:
       spin_unlock_irqrestore(&base->lock,flags);
 
       return ret;
}

對於新增的timer最後呼叫internal_add_timer(base, timer)加入到相應的timer_list中以完成初始化過程。,這是一個建表的過程,表的建立情況,關係到表的管理效率。之前我們說到它共有tv1~tv5 組,tv1是一個很特別的組。每個tv中有各個組員,每個timer是如何新增的呢,看看internal_add_timer()的實現過程:

static void internal_add_timer(structtvec_base *base, struct timer_list *timer)
{
       unsignedlong expires = timer->expires;
       unsignedlong idx = expires - base->timer_jiffies;
       structlist_head *vec;
 
       if(idx < TVR_SIZE) {
              inti = expires & TVR_MASK;
              vec= base->tv1.vec + i;
       }else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
              inti = (expires >> TVR_BITS) & TVN_MASK;
              vec= base->tv2.vec + i;
       }else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {
              inti = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
              vec= base->tv3.vec + i;
       }else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {
              inti = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
              vec= base->tv4.vec + i;
       }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);
       }else {
              inti;
              /*If the timeout is larger than 0xffffffff on 64-bit
               * architectures then we use the maximumtimeout:
               */
              if(idx > 0xffffffffUL) {
                     idx= 0xffffffffUL;
                     expires= idx + base->timer_jiffies;
              }
              i= (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
              vec= base->tv5.vec + i;
       }
       /*
        * Timers are FIFO:
        */
       list_add_tail(&timer->entry,vec);
}

通過程式碼我們知道它的過程是這樣的:首先它根據每個timer中的超時差值idx來決定timer所處的tev_base組別tv1~tv5.所以超時事件越後發生,那麼它所處的組位置越靠後。對於tv1組,超時插值idx為0~255之間。差值idx即為所屬組tv1中的陣列下標。從中可知tv1組中相鄰定時器的超時事件間隔1 jiffies發生。對於tv2組,超時差值idx為 256~2^14(16386)  之間,組中相鄰定時時器超事件時間隔256^1 = 256 jiffies發生。以此類推,tv3 組超時差值idx為(16387~2^20)之間,組中相鄰定時器超時時間間隔256^2 = 65536 jiffies發生 … 最後,新增的timer加入到當前節點(超時差值相等)的尾部list_add_tail()形成一個雙向連結串列。這樣分組timer雙向連結串列方便了後面對定時器的遷移更新管理過程,以及最終提高了CPU的處理效率,因為在__run_timers()時,我們只需掃描tv1組中即將到來的定時器事件就行了。

我們知道啟動過程時start_kernel()對定時器的初始化是這樣的 :

init_timers() -> run_timer_softirq()  -> __run_timers()…

timer_interrupt() -> update_process_times() ->run_local_timers() -> raise_softirq(TIMER_SOFTIRQ);

之前我寫的一篇《核心窺祕之一:start_kernel()執行過程記錄》也有提到過.

       __run_timers()是系統定時器超時事件的服務程式。這是run_timer_softirq()中一部分,是通過軟中斷的實現的,它是在軟中斷下半部處理的。

static inline void __run_timers(structtvec_base *base)
{
       …
       while(time_after_eq(jiffies, base->timer_jiffies)) {      //確定當前tvec_base->timer_jiffies是否有效
              structlist_head work_list;
              structlist_head *head = &work_list;
              intindex = base->timer_jiffies & TVR_MASK;               //只需掃描tv1組,看當前jiffies時刻是否有超時發生
              if(!index &&                                                         //cascade()定時器佇列級聯函式實現了
                     (!cascade(base,&base->tv2, INDEX(0))) &&         //tv5~tv2組遷移過程
                            (!cascade(base,&base->tv3, INDEX(1))) &&  
                                   !cascade(base,&base->tv4, INDEX(2)))
                     cascade(base,&base->tv5, INDEX(3));
              ++base->timer_jiffies;                                            //更新當前tvec_base->timer_jiffies
              list_replace_init(base->tv1.vec+ index, &work_list);        //連結串列更新新、舊取代
              while(!list_empty(head)) { //判定是否有定時器超時事件發生,非空為有,知道處理完連結串列中所有相同的定時器事件為止
                     void(*fn)(unsigned long);
                     unsignedlong data;
 
                     timer= list_first_entry(head, struct timer_list,entry);  // 這是一個巨集,獲取第一個實體(對應的是入口的下一個)的地址
                     fn= timer->function;                                    
                     data= timer->data;            
           …
                     fn(data);                                                        //timer超時時回撥函式入口
           …
        }
       }
     …
}

對於cascade()函式它是確保之前定時器建立時internal_add_timer()定時器佇列以及佇列租得遷移更新工作,為什麼要遷移,因為,系統在處理定時器時,比較的只是tv1組而已,也就是說,原來的tv1執行完之後,那麼剩下的tv2,tv3,tv4,tv5將會先後遷移到tv1組:tv5 -> tv4 -> tv3 -> tv2-> tv1,這樣定時器超時事件服務程式並不需要對每組的tv的超時事件進行檢測,相比而言,也就提高了CPU的處理效率。那麼這樣一來timer 連結串列將發生變化,所以需要重新計算,重新實現internal_add_timer(),所以cascade ()函式程式碼如下:

tatic int cascade(struct tvec_base *base,struct tvec *tv, int index)
{
       /*cascade all the timers from tv up one level */
       structtimer_list *timer, *tmp;
       structlist_head tv_list;
 
       list_replace_init(tv->vec+ index, &tv_list);                   //
       list_for_each_entry_safe(timer,tmp, &tv_list, entry) {
              BUG_ON(tbase_get_base(timer->base)!= base);    //確保本地cpu 的tvec_base沒
              internal_add_timer(base,timer);                         //有發生改變
       }
 
       return index;
}

通過以上分析,我們對Linux中系統定時器TIMER實現過程有所瞭解了。

.