msn: [email protected]
来源:http://yfydz.cublog.cn
1. 前言 2.6内核中的定时器结构中添加了一个新成员参数,本文讨论一下这个参数的作用和意义。 以下内核代码版本2.6.17.11。 2. 数据结构 /* include/linux/timer.h */ struct timer_list { struct list_head entry; unsigned long expires; void (*function)(unsigned long); unsigned long data; // 上面的结构成员和2.4的相同 // 增加了下面base这个成员 struct tvec_t_base_s *base; }; 在 kernel/timer.c 中定义了以下结构和参数: /* include/linux/timer.h */ /* * per-CPU timer vector definitions: */ // 根据内核是否配置了CONFIG_BASE_SMALL来确定向量链表数是32还是128 #define TVN_BITS (CONFIG_BASE_SMALL ? 4 : 6) // 根据内核是否配置了CONFIG_BASE_SMALL来确定root链表数是128还是512 #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) typedef struct tvec_s { struct list_head vec[TVN_SIZE]; } tvec_t; typedef struct tvec_root_s { struct list_head vec[TVR_SIZE]; } tvec_root_t; // 时钟向量base结构 struct tvec_t_base_s { spinlock_t lock; struct timer_list *running_timer; // base的基准时间 unsigned long timer_jiffies; // root HASH链 tvec_root_t tv1; // 二级HASH链表 tvec_t tv2; // 三级HASH链表 tvec_t tv3; // 四级HASH链表 tvec_t tv4; // 五级HASH链表 tvec_t tv5; } ____cacheline_aligned_in_smp; typedef struct tvec_t_base_s tvec_base_t; // 定义内核时钟base全局变量 tvec_base_t boot_tvec_bases; EXPORT_SYMBOL(boot_tvec_bases); // 为每个CPU定义一个时钟向量基表 static DEFINE_PER_CPU(tvec_base_t *, tvec_bases) = { &boot_tvec_bases }; 3. 函数处理 /* kernel/timer.c */ // 定时器初始化,这是每个定时器定义时必须的 // 而2.4的init_timer只是初始化timer的链表,而且是定义在timer.h文件中 void fastcall init_timer(struct timer_list *timer) { timer->entry.next = NULL; // 指定该时钟的base timer->base = per_cpu(tvec_bases, raw_smp_processor_id()); } 用add_timer()函数来看timer_base的作用: 2.6中的add_timer函数实际就是__mod_timer()函数,该函数定义挪到timer.h中: /* include/linux/timer.h */ static inline void add_timer(struct timer_list *timer) { BUG_ON(timer_pending(timer)); __mod_timer(timer, timer->expires); } /* kernel/timer.c */ int __mod_timer(struct timer_list *timer, unsigned long expires) { tvec_base_t *base, *new_base; unsigned long flags; int ret = 0; // 检查是否设置了中断处理函数,2.4中没检查 BUG_ON(!timer->function); // 锁住当前timer的base并返回 base = lock_timer_base(timer, &flags); // timer已经放到定时链表中,释放开 if (timer_pending(timer)) { detach_timer(timer, 0); ret = 1; } // 获取当前CPU的timer base new_base = __get_cpu_var(tvec_bases); // 如果当前CPU的timer base不是当前timer中的base, 更新timer的base if (base != new_base) { /* * We are trying to schedule the timer on the local CPU. * However we can't change timer's base while it is running, * otherwise del_timer_sync() can't detect that the timer's * handler yet has not finished. This also guarantees that * the timer is serialized wrt itself. */ if (likely(base->running_timer != timer)) { /* See the comment in lock_timer_base() */ timer->base = NULL; spin_unlock(&base->lock); base = new_base; spin_lock(&base->lock); timer->base = base; } } // 超时时间 timer->expires = expires; // 添加时钟 internal_add_timer(base, timer); spin_unlock_irqrestore(&base->lock, flags); return ret; } // 该函数根据超时时间将定时器加入到合适的链表 // 基本方法和2.4是相同的 // HASH表分五级,是根据超时时间来划分的,超时越短所在的HASH号就越小 // 时钟中断是主要检查HASH表1中的中断,其他HASH表号越大检查此时越少 static void internal_add_timer(tvec_base_t *base, struct timer_list *timer) { unsigned long expires = timer->expires; // 定时时间和当前时间的差 unsigned long idx = expires - base->timer_jiffies; struct list_head *vec; if (idx < TVR_SIZE) { // 时间差小于TVR_SIZE个jiffie时插入HASH表1 // i是HASH值确定HASH表中具体的链表,以下相同 int i = expires & TVR_MASK; vec = base->tv1.vec + i; } else if (idx < 1 << (TVR_BITS + TVN_BITS)) { // 否则时间差小于2^(TVR_BITS + TVN_BITS)个jiffie时插入HASH表2 int i = (expires >> TVR_BITS) & TVN_MASK; vec = base->tv2.vec + i; } else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) { // 否则时间差小于2^(TVR_BITS + 2 * TVN_BITS)个jiffie时插入HASH表3 int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK; vec = base->tv3.vec + i; } else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) { // 否则时间差小于2^(TVR_BITS + 3 * TVN_BITS)个jiffie时插入HASH表4 int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK; vec = base->tv4.vec + i; } else if ((signed long) idx < 0) { // 否则时间差似乎是个负数时插入HASH表1 /* * 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 { // 否则插入HASH表5 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; } /* * Timers are FIFO: */ // 添加到链表尾,FIFO list_add_tail(&timer->entry, vec); } 4. 结论 2.6和2.4定时器处理的基本思路是一样的,都是根据定时时间分成5个链表,但2.4中的各个链表是作为单独的变量定义的,并没有归结成一个结构单元;而2.6的改变就是将这些HASH链表包装成一个完整对象,每个定时器就能直接访问这个对象,而且这个变量各CPU分开的,不同的base可以设置不同的基准时间,使定时操作更加灵活。 另外2.4中没有定义CONFIG_BASE_SMALL选项,TVR_BITS和TVN_BITS都是固定值。