Linux的动态定时器--时间轮
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);
下面来看具体定时器的初始化和添加操作:
初始化的函数:
- #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);
- }