linux kernel中timer的使用

在kernel中如果想周期性的干些什么事情,或者某个特定时间干些什么事情,可以使用timer。
例如像周期性地dump某段buffer的数据等等。

先来看看使用方法。
先定义一个struct timer_list的对象。eg: struct timer_list dump_t;
这个对象相当于一个闹钟,其中包含了时间点,也就是什么时候激活闹钟;一个函数指针,闹钟激活后干活的地方;
还有一个void 指针,在闹钟干活的时候可能需要传给它一些当前的数据。

先看使用方法,之后再稍微深入了解一下。
前面定义好了struct timer_list对象,接下来就需要初始化该对象。
调用函数init_timer进行初步初始化。
然后对结构体中的一些成员进行赋值:
  init_timer(&dump_t);
  dump_t.function = dump_function;
  dump_t.data = (unsigned long) my_dev;
  dump_t.expires = jiffies + HZ;    // 1秒钟之后timer被激活,如果是n秒,将HZ改为n*HZ。注意单位是tick
这样就OK了么?
当然没有,需要把timer加到timer list中,也就是要告诉系统,你申请了这么一个timer。
  add_timer(&dump_t);
  
现在不明白的地方就是timer到了,干活的地方,即dump_function函数。
函数声明:
static void dump_function(unsigned long channel);
其中的实现么,就随意了,看你想让这个timer干些什么活。
注意一点,这个timer只会响应一次,因为jiffies + HZ时间点只有一个。
如果想让此timer周期性地干活,就需要在dump_function函数中重新启动该timer。
启动方法:
 del_timer(&dump_t);
 dump_t.function = dump_function;
 dump_t.data = (unsigned long) mydev;
 dump_t.expires = jiffies + HZ;
 add_timer(&dump_t);
 
 
使用方法至此基本上介绍完了。
下面看看timer的具体实现。

先看看struct timer_list的定义:
struct timer_list {
 /*
  * All fields that change during normal runtime grouped to the
  * same cacheline
  */
 struct list_head entry;
 unsigned long expires;
 struct tvec_base *base;

 void (*function)(unsigned long);
 unsigned long data;

 int slack;

#ifdef CONFIG_TIMER_STATS
 int start_pid;
 void *start_site;
 char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
 struct lockdep_map lockdep_map;
#endif
};

init_timer的定义:
#define init_timer(timer)      \
 do {        \
  static struct lock_class_key __key;   \
  init_timer_key((timer), #timer, &__key);  \
 } while (0)
 
/**
 * init_timer_key - initialize a timer
 * @timer: the timer to be initialized
 * @name: name of the timer
 * @key: lockdep class key of the fake lock used for tracking timer
 *       sync lock dependencies
 *
 * init_timer_key() must be done to a timer prior calling *any* of the
 * other timer functions.
 */
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 inline void debug_init(struct timer_list *timer)
{
 debug_timer_init(timer);
 trace_timer_init(timer);
}

static inline void debug_timer_init(struct timer_list *timer)
{
 debug_object_init(timer, &timer_debug_descr);
}

/**
 * debug_object_init - debug checks when an object is initialized
 * @addr: address of the object
 * @descr: pointer to an object specific debug description structure
 */
void debug_object_init(void *addr, struct debug_obj_descr *descr)
{
 if (!debug_objects_enabled)
  return;

 __debug_object_init(addr, descr, 0);
}

static void
__debug_object_init(void *addr, struct debug_obj_descr *descr, int onstack)
{
 enum debug_obj_state state;
 struct debug_bucket *db;
 struct debug_obj *obj;
 unsigned long flags;

 fill_pool();

 db = get_bucket((unsigned long) addr);

 raw_spin_lock_irqsave(&db->lock, flags);

 obj = lookup_object(addr, db);
 if (!obj) {
  obj = alloc_object(addr, db, descr);
  if (!obj) {
   debug_objects_enabled = 0;
   raw_spin_unlock_irqrestore(&db->lock, flags);
   debug_objects_oom();
   return;
  }
  debug_object_is_on_stack(addr, onstack);
 }

 switch (obj->state) {
 case ODEBUG_STATE_NONE:
 case ODEBUG_STATE_INIT:
 case ODEBUG_STATE_INACTIVE:
  obj->state = ODEBUG_STATE_INIT;
  break;

 case ODEBUG_STATE_ACTIVE:
  debug_print_object(obj, "init");
  state = obj->state;
  raw_spin_unlock_irqrestore(&db->lock, flags);
  debug_object_fixup(descr->fixup_init, addr, state);
  return;

 case ODEBUG_STATE_DESTROYED:
  debug_print_object(obj, "init");
  break;
 default:
  break;
 }

 raw_spin_unlock_irqrestore(&db->lock, flags);
}

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);
 timer->slack = -1;
#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);
}

对下面这个东东比较感兴趣:
 timer->base = __raw_get_cpu_var(tvec_bases);
 
看看__raw_get_cpu_var的实现,其中SMP和非SMP有差别。
SMP的情况:
#define __raw_get_cpu_var(var) (*__this_cpu_ptr(&(var)))
非SMP的情况:
#define __raw_get_cpu_var(var) (*VERIFY_PERCPU_PTR(&(var)))

先看简单的:
#define VERIFY_PERCPU_PTR(__p) ({   \
 __verify_pcpu_ptr((__p));   \
 (typeof(*(__p)) __kernel __force *)(__p); \
})

 * Macro which verifies @ptr is a percpu pointer without evaluating
 * @ptr.  This is to be used in percpu accessors to verify that the
 * input parameter is a percpu pointer.
 */
#define __verify_pcpu_ptr(ptr) do {     \
 const void __percpu *__vpp_verify = (typeof(ptr))NULL;  \
 (void)__vpp_verify;      \
} while (0)

再看看__this_cpu_ptr:
#define __this_cpu_ptr(ptr) SHIFT_PERCPU_PTR(ptr, __my_cpu_offset)

/*
 * Add a offset to a pointer but keep the pointer as is.
 *
 * Only S390 provides its own means of moving the pointer.
 */
#ifndef SHIFT_PERCPU_PTR
/* Weird cast keeps both GCC and sparse happy. */
#define SHIFT_PERCPU_PTR(__p, __offset) ({    \
 __verify_pcpu_ptr((__p));     \
 RELOC_HIDE((typeof(*(__p)) __kernel __force *)(__p), (__offset)); \
})
#endif

又合二为一了。

init先看到这,还是一头雾水,先把代码整理出来,以后学习用。

下面看看add_timer的实现。
/**
 * add_timer - start a timer
 * @timer: the timer to be added
 *
 * The kernel will do a ->function(->data) callback from the
 * timer interrupt at the ->expires point in the future. The
 * current time is 'jiffies'.
 *
 * The timer's ->expires, ->function (and if the handler uses it, ->data)
 * fields must be set prior calling this function.
 *
 * Timers with an ->expires field in the past will be executed in the next
 * timer tick.
 */
void add_timer(struct timer_list *timer)
{
 BUG_ON(timer_pending(timer));
 mod_timer(timer, timer->expires);
}

/**
 * timer_pending - is a timer pending?
 * @timer: the timer in question
 *
 * timer_pending will tell whether a given timer is currently pending,
 * or not. Callers must ensure serialization wrt. other operations done
 * to this timer, eg. interrupt contexts, or other CPUs on SMP.
 *
 * return value: 1 if the timer is pending, 0 if not.
 */
static inline int timer_pending(const struct timer_list * timer)
{
 return timer->entry.next != NULL;
}

/**
 * mod_timer - modify a timer's timeout
 * @timer: the timer to be modified
 * @expires: new timeout in jiffies
 *
 * mod_timer() is a more efficient way to update the expire field of an
 * active timer (if the timer is inactive it will be activated)
 *
 * mod_timer(timer, expires) is equivalent to:
 *
 *     del_timer(timer); timer->expires = expires; add_timer(timer);
 *
 * Note that if there are multiple unserialized concurrent users of the
 * same timer, then mod_timer() is the only safe way to modify the timeout,
 * since add_timer() cannot modify an already running timer.
 *
 * The function returns whether it has modified a pending timer or not.
 * (ie. mod_timer() of an inactive timer returns 0, mod_timer() of an
 * active timer returns 1.)
 */
int mod_timer(struct timer_list *timer, unsigned long expires)
{
 expires = apply_slack(timer, 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);
}

/*
 * Decide where to put the timer while taking the slack into account
 *
 * Algorithm:
 *   1) calculate the maximum (absolute) time
 *   2) calculate the highest bit where the expires and new max are different
 *   3) use this bit to make a mask
 *   4) use the bitmask to round down the maximum time, so that all last
 *      bits are zeros
 */
static inline
unsigned long apply_slack(struct timer_list *timer, unsigned long expires)
{
 unsigned long expires_limit, mask;
 int bit;

 if (timer->slack >= 0) {
  expires_limit = expires + timer->slack;
 } else {
  long delta = expires - jiffies;

  if (delta < 256)
   return expires;

  expires_limit = expires + delta / 256;
 }
 mask = expires ^ expires_limit;
 if (mask == 0)
  return expires;

 bit = find_last_bit(&mask, BITS_PER_LONG);

 mask = (1 << bit) - 1;

 expires_limit = expires_limit & ~(mask);

 return expires_limit;
}

static inline int
__mod_timer(struct timer_list *timer, unsigned long expires,
      bool pending_only, int pinned)
{
 struct tvec_base *base, *new_base;
 unsigned long flags;
 int ret = 0 , cpu;

 timer_stats_timer_set_start_info(timer);
 BUG_ON(!timer->function);

 base = lock_timer_base(timer, &flags);

 if (timer_pending(timer)) {
  detach_timer(timer, 0);
  if (timer->expires == base->next_timer &&
      !tbase_get_deferrable(timer->base))
   base->next_timer = base->timer_jiffies;
  ret = 1;
 } else {
  if (pending_only)
   goto out_unlock;
 }

 debug_activate(timer, expires);

 cpu = smp_processor_id();

#if defined(CONFIG_NO_HZ) && defined(CONFIG_SMP)
 if (!pinned && get_sysctl_timer_migration() && idle_cpu(cpu))
  cpu = get_nohz_timer_target();
#endif
 new_base = per_cpu(tvec_bases, cpu);

 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_set_base(timer, NULL);
   spin_unlock(&base->lock);
   base = new_base;
   spin_lock(&base->lock);
   timer_set_base(timer, base);
  }
 }

 timer->expires = expires;
 if (time_before(timer->expires, base->next_timer) &&
     !tbase_get_deferrable(timer->base))
  base->next_timer = timer->expires;
 internal_add_timer(base, timer);

out_unlock:
 spin_unlock_irqrestore(&base->lock, flags);

 return ret;
}

太多了,不一一看了,只看internal_add_timer。

static void internal_add_timer(struct tvec_base *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) {
  int i = expires & TVR_MASK;
  vec = base->tv1.vec + i;
 } else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
  int i = (expires >> TVR_BITS) & TVN_MASK;
  vec = base->tv2.vec + i;
 } else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {
  int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
  vec = base->tv3.vec + i;
 } else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {
  int i = (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 {
  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:
  */
 list_add_tail(&timer->entry, vec);
}

/**
 * list_add_tail - add a new entry
 * @new: new entry to be added
 * @head: list head to add it before
 *
 * Insert a new entry before the specified head.
 * This is useful for implementing queues.
 */
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
 __list_add(new, head->prev, head);
}

__list_add的实现就比较简单了,基本链表操作。

Logo

更多推荐