3.3 添加或删除软件时钟

在了解了软件时钟的数据组织关系之后,现在来看一下如何添加以及删除一个软件时钟。

3.3.1 添加软件时钟

在 Linux 内核中要添加一个软件时钟,首先必须分配 struct timer_list 类型的变量,然后调用函数 add_timer() 将该软件时钟添加到相应调用 add_timer 函数的 CPU 的 base 中。 Add_timer 是对函数 __mod_timer() 的一层包装。函数 __mod_timer() 的代码如清单3-2:

清单3-2 __mod_timer 函数

int __mod_timer(struct timer_list *timer, unsigned long expires)
{
struct tvec_base *base, *new_base;
unsigned long flags;
int ret = 0;
……
base = lock_timer_base(timer, &flags);
if (timer_pending(timer)) {
detach_timer(timer, 0);
ret = 1;
}
new_base = __get_cpu_var(tvec_bases);

if (base != new_base) {
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;
internal_add_timer(base, timer);
spin_unlock_irqrestore(&base->lock, flags);
return ret;
}

清单3-2 __mod_timer 函数

代码解释:

  1. 取得软件时钟所在 base 上的同步锁 struct tvec_base 变量中的自旋锁),并返回该软件时钟的 base ,保存在 base 变量中
  2. 如果该软件时钟处在 pending 状态在 base 中,准备执行),则卸载该软件时钟
  3. 取得本 CPU 上的 base 指针类型为 struct tvec_base* ),保存在 new_base 中
  4. 如果 base 和 new_base 不一样,也就是说软件时钟发生了迁移从一个 CPU 中移到了另一个 CPU 上),那么如果该软件时钟的处理函数当前没有在迁移之前的那个 CPU 上运行,则先将软件时钟的 base 设置为 NULL ,然后再将该软件时钟的 base 设置为 new_base 。否则,跳到5。
  5. 设置软件时钟的到期时间
  6. 调用 internal_add_timer 函数将软件时钟添加到软件时钟的 base 中本 CPU 的 base )
  7. 释放锁

注:卸载软件时钟的意思是指将软件时钟从软件时钟所在 base 中删除,以后所说的卸载软件时钟也都是这个意思

这里有必要详细说明一下软件时钟如何被添加到软件时钟的 base 中的添加到本 CPU base 的 tv1~tv5 里面),因为这是软件时钟处理的基础。来看函数 internal_add_timer 函数的实现,如清单3-3

清单3-3 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) {
vec = base->tv1.vec + (base->timer_jiffies & TVR_MASK);
} else {
int i;
if (idx > 0xffffffffUL) {
idx = 0xffffffffUL;
expires = idx + base->timer_jiffies;
}
i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
vec = base->tv5.vec + i;
}
list_add_tail(&timer->entry, vec);
}

代码解释:

  • 计算该软件时钟的到期时间和 timer_jiffies 当前正在处理的软件时钟的到期时间)的差值,作为索引保存到 idx 变量中。
  • 判断 idx 所在的区间,在
    • [0, 对象12]或者( 对象13, 0)该软件时钟已经到期),则将要添加到 tv1 中
    • [对象14, 对象15],则将要添加到 tv2 中
    • [对象16, 对象17],则将要添加到 tv3 中
    • [对象18, 对象19],则将要添加到 tv4 中
    • [对象20, 对象21),则将要添加到 tv5 中,但实际上最大值为 0xffffffffUL
  • 计算所要加入的具体位置哪个链表中,即 tv1~tv5 的哪个子链表,参考图3-1)
  • 最后将其添加到相应的链表中

从这个函数可以得知,内核中是按照软件时钟到期时间的相对值相对于 timer_jiffies 的值)将软件时钟添加到软件时钟所在的 base 中的。


相关内容