Linux的定時器—有時也稱為動態定時器或內核定時器—是管理內核時間的基礎。定時器是一種軟件功能,即允許在將來的某個時刻,函數在給定的時間間隔用完時被調用。注意的是定時器並不會周期運行,它在超時後就自行銷毀,這也是定時器被稱為動態定時器的一個原因。動態定時器不斷地創建和銷毀,而且它的運行次數也不受限制。
定時器在內核代碼中屬於一個基礎組件。要想完全弄清楚linux2.6中內核定時器的實現,得先從初始化開始。
在start_kernel(void)-->init_timers(void)
- void __init init_timers(void)
- {
- int err = timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE,
- (void *)(long)smp_processor_id());
-
- init_timer_stats();
-
- BUG_ON(err == NOTIFY_BAD);
- register_cpu_notifier(&timers_nb);
- open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
- }
在timer_cpu_notify(&timers_nb,(unsigned long)CPU_UP_PREPARE,
(void*)(long)smp_processor_id());
中執行
init_timers_cpu(cpu) //初始化本cpu中的timers
初始化的主要代碼是:
- spin_lock_init(&base->lock);
-
- for (j = 0; j < TVN_SIZE; j++) {
- INIT_LIST_HEAD(base->tv5.vec + j);
- INIT_LIST_HEAD(base->tv4.vec + j);
- INIT_LIST_HEAD(base->tv3.vec + j);
- INIT_LIST_HEAD(base->tv2.vec + j);
- }
- for (j = 0; j < TVR_SIZE; j++)
- INIT_LIST_HEAD(base->tv1.vec + j);
-
- base->timer_jiffies = jiffies;
- base->next_timer = base->timer_jiffies;
這段代碼的主體是base,base的定義是:structtvec_base *base;
這個tvec_base是動態定時器的主要數據結構,每個cpu上有一個,它包含相應cpu中處理動態定時器需要的所有數據。為簡化分析僅考慮單cpu。給出這個數據機構:
- 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;
- } ____cacheline_aligned;
其中,timer_list是具體定時器的結構體(後面再具體看timer_list結構體);上面包含tv1,tv2,tv3,tv4,tv5;內核定時器的巧妙設計就在於此。
- #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 {
- struct list_head vec[TVN_SIZE];
- };
-
- struct tvec_root {
- struct list_head vec[TVR_SIZE];
- };
從這個定義看到,tv1就是長度為256的數組,數組成員是list_head;同樣的,tv2,tv3,tv4和tv5都是長度為64的數組,數組成員是list_head。List_head就是linux內核代碼中廣泛使用的雙向鏈表。在阻塞和非阻塞中的等待隊列時就看到了list_head的應用。那麼,tv1-tv5都是數組+鏈表的實現,其實hash的有一種簡單實現就是數組+鏈表的組合,那麼這幾個就有些hash的味道,具體是不是,還要分析道後面才知道。
再回到前面的初始化中,
for(j = 0; j < TVN_SIZE; j++) {
INIT_LIST_HEAD(base->tv5.vec+ j);
INIT_LIST_HEAD(base->tv4.vec+ j);
INIT_LIST_HEAD(base->tv3.vec+ j);
INIT_LIST_HEAD(base->tv2.vec+ j);
}
for(j = 0; j < TVR_SIZE; j++)
INIT_LIST_HEAD(base->tv1.vec+ j);
就tv1-tv5這5個結構體中的數組中每個list_head進行初始化。
base->timer_jiffies= jiffies;
base->next_timer= base->timer_jiffies;
將base中的timer_jiffies和next_timer都初始化為jiffies。
初始化完成後,在init_timers函數的第二個重要的步驟是:
open_softirq(TIMER_SOFTIRQ,run_timer_softirq);
這個函數注冊定時器軟中斷,簡單的軟中斷分析見《Linux的軟中斷》 見 http://www.linuxidc.com/Linux/2012-04/57918.htm;
下面來看具體定時器的初始化和添加操作:
初始化的函數:
- #define init_timer(timer) \
- do { \
- static struct lock_class_key __key; \
- init_timer_key((timer), #timer, &__key); \
- } while (0)
-
- void init_timer_key(struct timer_list *timer,
- const char *name,
- struct lock_class_key *key)
- {
- debug_init(timer);
- __init_timer(timer, name, key);
- }
-
- static void __init_timer(struct timer_list *timer,
- const char *name,
- struct lock_class_key *key)
- {
- timer->entry.next = NULL;
- timer->base = __raw_get_cpu_var(tvec_bases);
- #ifdef CONFIG_TIMER_STATS
- timer->start_site = NULL;
- timer->start_pid = -1;
- memset(timer->start_comm, 0, TASK_COMM_LEN);
- #endif
- lockdep_init_map(&timer->lockdep_map, name, key, 0);
- }
初始化的宏定義:
- #define TIMER_INITIALIZER(_function, _expires, _data) { \
- .entry = { .prev = TIMER_ENTRY_STATIC }, \
- .function = (_function), \
- .expires = (_expires), \
- .data = (_data), \
- .base = &boot_tvec_bases, \
- __TIMER_LOCKDEP_MAP_INITIALIZER( \
- __FILE__ ":" __stringify(__LINE__)) \
- }
定時器的添加:
- void add_timer(struct timer_list *timer)
- {
- BUG_ON(timer_pending(timer));
- mod_timer(timer, timer->expires);
- }
Timer_list結構體的定義:
- struct timer_list {
- struct list_head entry;
- unsigned long expires;
-
- void (*function)(unsigned long);
- unsigned long data;
-
- struct tvec_base *base;
- #ifdef CONFIG_TIMER_STATS
- void *start_site;
- char start_comm[16];
- int start_pid;
- #endif
- #ifdef CONFIG_LOCKDEP
- struct lockdep_map lockdep_map;
- #endif
- };
- int mod_timer(struct timer_list *timer, unsigned long expires)
- {
- /*
- * This is a common optimization triggered by the
- * networking code - if the timer is re-modified
- * to be the same thing then just return:
- */
- if (timer_pending(timer) && timer->expires == expires)
- return 1;
-
- return __mod_timer(timer, expires, false, TIMER_NOT_PINNED);
- }