timer的base

本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
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都是固定值。

猜你喜欢

转载自cxw06023273.iteye.com/blog/867145
今日推荐