作者:艾灸养生加盟 | 来源:互联网 | 2017-10-26 11:47
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;}
代码解释:
注:卸载软件时钟的意思是指将软件时钟从软件时钟所在 base 中删除,以后所说的卸载软件时钟也都是这个意思
取得软件时钟所在 base 上的同步锁( struct tvec_base 变量中的自旋锁),并返回该软件时钟的 base ,保存在 base 变量中
如果该软件时钟处在 pending 状态(在 base 中,准备执行),则卸载该软件时钟
取得本 CPU 上的 base 指针(类型为 struct tvec_base* ),保存在 new_base 中
如果 base 和 new_base 不一样,也就是说软件时钟发生了迁移(从一个 CPU 中移到了另一个 CPU 上),那么如果该软件时钟的处理函数当前没有在迁移之前的那个 CPU 上运行,则先将软件时钟的 base 设置为 NULL ,然后再将该软件时钟的 base 设置为 new_base 。否则,跳到5。
设置软件时钟的到期时间
调用 internal_add_timer 函数将软件时钟添加到软件时钟的 base 中(本 CPU 的 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 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, ]或者( , 0)(该软件时钟已经到期),则将要添加到 tv1 中
[, ],则将要添加到 tv2 中
[, ],则将要添加到 tv3 中
[, ],则将要添加到 tv4 中
[, ),则将要添加到 tv5 中,但实际上最大值为 0xffffffffUL
计算所要加入的具体位置(哪个链表中,即 tv1~tv5 的哪个子链表,参考图3-1)
最后将其添加到相应的链表中
从这个函数可以得知,内核中是按照软件时钟到期时间的相对值(相对于 timer_jiffies 的值)将软件时钟添加到软件时钟所在的 base 中的。
3.3.2 删除软件时钟
内核可调用 del_timer 函数删除软件时钟, del_timer 的代码如清单3-4
清单3-4 del_timer 函数
int del_timer(struct timer_list *timer){ struct tvec_base *base; unsigned long flags; int ret = 0; …… if (timer_pending(timer)) { base = lock_timer_base(timer, &flags); if (timer_pending(timer)) { detach_timer(timer, 1); ret = 1; } spin_unlock_irqrestore(&base->lock, flags); } return ret;}
代码解释:
检测该软件时钟是否处在 pending 状态(在 base 中,准备运行),如果不是则直接函数返回
如果处于 pending 状态,则获得锁
再次检测软件时钟是否处于 pending 状态(该软件时钟可能被卸载了),不是则释放锁然后函数返回
如果还是 pending 状态,则将其卸载,之后释放锁,函数返回
如果在 SMP 系统中,则需使用 del_timer_sync 函数来删除软件时钟。在讲解 del_timer_sync 函数之前,先来看下 try_to_del_timer_sync 函数的实现(该函数被 del_timer_sync 函数使用),其代码如清单3-5
清单3-5 try_to_del_timer_sync 函数
int try_to_del_timer_sync(struct timer_list *timer){ struct tvec_base *base; unsigned long flags; int ret = -1; base = lock_timer_base(timer, &flags); if (base->running_timer == timer) goto out; ret = 0; if (timer_pending(timer)) { detach_timer(timer, 1); ret = 1; }out: spin_unlock_irqrestore(&base->lock, flags); return ret;}
该函数检测当前运行的软件时钟是不是该软件时钟,如果是,则函数返回-1,表明目前不能删除该软件时钟;如果不是检测该软件时钟是否处于 pending 状态,如果不是,则函数返回0,表明软件时钟已经被卸载,如果处于 pending 状态再把软件时钟卸载,函数返回1,表明成功卸载该软件时钟。
[1] [2] [3] 下一页