Chinaunix首页 | 论坛 | 博客
  • 博客访问: 244737
  • 博文数量: 63
  • 博客积分: 179
  • 博客等级: 入伍新兵
  • 技术积分: 342
  • 用 户 组: 普通用户
  • 注册时间: 2010-06-27 20:29
文章分类

全部博文(63)

文章存档

2019年(2)

2013年(5)

2012年(53)

2011年(3)

分类:

2012-06-11 20:48:04


  欢迎转载:http://kylinux.cublog.cn



TIMER_INITIALIZER():
1):TIMER_INITIALIZER()用来声明一个定时器,它的定义如下:
#define TIMER_INITIALIZER(_function, _expires, _data) {         \
                .function = (_function),                        \            
                .expires = (_expires),                          \
                .data = (_data),                                \
                .base = &boot_tvec_bases,                       \
        }

注:在上章对里面的数据结构已经解释过了


2): mod_timer():修改定时器的到时时间
int mod_timer(struct timer_list *timer, unsigned long expires)
{
    //如果该定时器没有定义fuction
        BUG_ON(!timer->function);

        timer_stats_timer_set_start_info(timer);
        /*
         * 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->expires == expires && timer_pending(timer))
                return 1;
    //调用__mod_timer().呆会再给出分析
        return __mod_timer(timer, expires);
}


从代码可以看出,如果所给的要修改的时间等于定时器原来的时间并且定时器现在正处于活动状态,则不修改,返回1,否则修改定时器时间,返回0。mod_timer()是一个非有效的更新处于活动状态的定时器的时间的方法,如果定时器处于非活动状态,则会激活定时器。在功能上,mod_timer()等价于:

3): add_timer()用来将定时器挂载到定时软中断队列,激活该定时器
static inline void add_timer(struct timer_list *timer)
{
        BUG_ON(timer_pending(timer));
        __mod_timer(timer, timer->expires);
}

可以看到mod_timer与add_timer 最后都会调用__mod_timer().为了分析这个函数。

下面转入__mod_timer()的代码了:
int __mod_timer(struct timer_list *timer, unsigned long expires)
{
        tvec_base_t *base, *new_base;
        unsigned long flags;
        int ret = 0;

        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);
                ret = 1;
        }
    //取得当前CPU对应的tvec_bases
        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 上的同步锁( struct tvec_base 变量中的自旋锁),并返回该软件时钟的 base ,保存在 base 变量中
如果该软件时钟处在 pending 状态(在 base 中,准备执行),则调用detach_timer()函数将该定时器从它原来所属的链表中删除。
取得本 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 )



4)internal_add_timer()的代码如下:

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) {
                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);
}



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


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

5):定时器更新
每过一个HZ,就会检查当前是否有定时器的定时器时间到达.如果有,运行它所注册的函数,再将其删除.为了分析这一过程,我们先从定时器系统的初始化看起.
asmlinkage void __init start_kernel(void)
{
         ……
         init_timers();
         ……
}
Init_timers()的定义如下:
void __init init_timers(void)                                                               
{       
        int err = timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE,
                                (void *)(long)smp_processor_id());
        
        init_timer_stats();
       
        BUG_ON(err == NOTIFY_BAD);
        register_cpu_notifier(&timers_nb);
        //注册TIMER_SOFTIRQ软中断
        open_softirq(TIMER_SOFTIRQ, run_timer_softirq, NULL);
}

我们在前面分析过,每当时钟当断函数到来的时候,就会打开定时器的软中断.运行其软中断函数.run_timer_softirq()
代码如下:
static void run_timer_softirq(struct softirq_action *h)                                    
{
    ////取得当于CPU的tvec_base_t结构
        tvec_base_t *base = __get_cpu_var(tvec_bases);

        hrtimer_run_queues();
    //如果jiffies > base->timer_jiffies
        if (time_after_eq(jiffies, base->timer_jiffies))
                __run_timers(base);
}

__run_timers()代码如下:

static inline void __run_timers(struct tvec_base *base)
{
    ……
    spin_lock_irq(&base->lock);
    while (time_after_eq(jiffies, base->timer_jiffies)) {
        ……
        int index = base->timer_jiffies & TVR_MASK;
        if (!index &&
              (!cascade(base, &base->tv2, INDEX(0))) &&
                 (!cascade(base, &base->tv3, INDEX(1))) &&
                    !cascade(base, &base->tv4, INDEX(2)))
                       cascade(base, &base->tv5, INDEX(3));
        ++base->timer_jiffies;
        list_replace_init(base->tv1.vec + index, &work_list);
        while (!list_empty(head)) {
            ……
            timer = list_first_entry(head, struct timer_list,entry);
            fn = timer->function;
            data = timer->data;
            ……
            set_running_timer(base, timer);
            detach_timer(timer, 1);
            spin_unlock_irq(&base->lock);
            {
                int preempt_count = preempt_count();
                fn(data);
                ……
            }
            spin_lock_irq(&base->lock);
        }
    }
    set_running_timer(base, NULL);
    spin_unlock_irq(&base->lock);
}


代码解析:
获得 base 的同步锁
如果 jiffies 大于等于 timer_jiffies (当前正要处理的软件时钟的到期时间,说明可能有软件时钟到期了),就一直运行3~7,否则跳转至8
计算得到 tv1 的索引,该索引指明当前到期的软件时钟所在 tv1 中的链表,代码:

int index = base->timer_jiffies & TVR_MASK;


调用 cascade 函数对软件时钟进行必要的调整(稍后会介绍调整的过程)
使得 timer_jiffies 的数值增加1
取出相应的软件时钟链表
遍历该链表,对每个元素进行如下操作

    * 设置当前软件时钟为 base 中正在运行的软件时钟(即保存当前软件时钟到 base-> running_timer 成员中)
    * 将当前软件时钟从链表中删除,即卸载该软件时钟
    * 释放锁,执行软件时钟处理程序
    * 再次获得锁

设置当前 base 中不存在正在运行的软件时钟
释放锁



static int cascade(struct tvec_base *base, struct tvec *tv, int index)
{
    struct timer_list *timer, *tmp;
    struct list_head tv_list;
    list_replace_init(tv->vec + index, &tv_list);
    list_for_each_entry_safe(timer, tmp, &tv_list, entry) {
        ……
        internal_add_timer(base, timer);
    }
    return index;
}
该函数根据索引,取出相应的 tv ( tv2~tv5 )中的链表,然后遍历链表每一个元素。按照其到期时间重新将软件时钟加入到软件时钟的 base 中。该函数返回 tv 中被调整的链表索引值

 
6):del_timer()删除定时器
//删除一个timer
int del_timer(struct timer_list *timer)
{
        tvec_base_t *base;
        unsigned long flags;
        int ret = 0;

        timer_stats_timer_clear_start_info(timer);
        if (timer_pending(timer)) {
                base = lock_timer_base(timer, &flags);
                if (timer_pending(timer))
                {
                    //将timer从链表中删除
                        detach_timer(timer, 1);
                        ret = 1;
                }
                spin_unlock_irqrestore(&base->lock, flags);
        }

        return ret;
}
被激活或未被激活的定时器都可以用该函数,如果定时器未被激活,该函数返回0;否则返回1。
注意,不需为已经超时定时器调用该函数,因为会被自动调用。删除定时器时需要等待其他cpu运行该定时器处理器程序都退出。

7): del_timer_sync()有竞争情况下的定时器删除
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 则要将该软件时钟卸载所引发的问题。
定时器部份到这里就介绍完了.为了管理定时器.内核用了一个很巧妙的数据结构.值得好好的体会.


注:下章,介绍定时器的延迟。

参考资料:
linux内核设计与实现
http://blog.ccidnet.com/blog-htm-do-showone-uid-84561-type-blog-itemid-258809.html
http://blog.chinaunix.net/u2/61477/showart_481379.html

http://hi.baidu.com/80695073/blog/item/9c170f55be0ae5c1b745ae5b.html
http://blog.chinaunix.net/u1/51562/showart_509400.html

http://www.ibm.com/developerworks/cn/linux/l-cn-clocks/index.html
阅读(951) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~