首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

Linux 时钟处理机制(3)软件时钟处理-2

Linux 时钟处理机制(3)软件时钟处理-2

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 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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, ]或者(, 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 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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,表明成功卸载该软件时钟。
接下来,再来看看函数 del_timer_sync 定义,如清单3-6
清单3-6 del_timer_sync 函数
1
2
3
4
5
6
7
8
9
int del_timer_sync(struct timer_list *timer)
{
    for (;;) {
        int ret = try_to_del_timer_sync(timer);
        if (ret >= 0)
            return ret;
        cpu_relax();
    }
}




del_timer_sync 函数无限循环试图卸载该软件时钟,直到该软件时钟能够被成功卸载。从其实现中可以看出:如果一个软件时钟的处理函数正在执行时,对其的卸载操作将会失败。一直等到软件时钟的处理函数运行结束后,卸载操作才会成功。这样避免了在 SMP 系统中一个 CPU 正在执行软件时钟的处理函数,而另一个 CPU 则要将该软件时钟卸载所引发的问题。
返回列表