Ubuntu Kylin技术论坛

 找回密码
查看: 1134|回复: 1

linux时间子系统(八)

[复制链接]
  • TA的每日心情
    奋斗
    2018-4-21 12:43
  • 签到天数: 63 天

    [LV.6]常住居民II

    发表于 2018-7-27 17:16:56 | 显示全部楼层 |阅读模式
    本帖最后由 kylinmarket 于 2018-7-27 17:22 编辑

    3.3 定时器的添加
      hrtimer添加的流程图如下:
      在添加定时器到红黑树时,如果已经存在与红黑树上,必须得先删除定时器,之后使用enqueue_hrtimer函数将hrtimer插入到红黑树上。如果当前添加的定时器是最早到期的,则需要重新设定定时器硬件的到期时间,需要将当前定时器的到期时间设置到定时器硬件,使其可以最早得到处理。
    3.4 定时器的处理
      高精度定时器系统有3个入口可以对到期的定时器进行处理,分别是
      1 没有切换到高精度模式时,在每个jiffies的tick事件中断中进行查询和处理。
      2 在HRTIMER_SOFTIRQ软中断中进行查询和处理。
      3 切换到高精度模式以后,在每个clock_event_device的到期事件的中断中进行查询和处理。
    3.4.1 低精度模式
      系统并不是一开始就会支持高精度模式,而是在系统启动后的某个阶段,等待所有的条件都满足后,才会切换到高精度模式。当系统没有切换到高精度模式时,所有的高精度定时器都运行在低精度模式下,在每个jiffies的tick事件中断中进行到期定时器的查询和处理,显然此时的精度和低分辨率定时器是一样的(HZ级别)。低精度模式下,每个tick事件中断中,hrtimer_run_queues函数会被调用,由它完成定时器的到期处理。hrtimer_run_queue首先判断目前高精度模式是否已经启用,如果已经切换到高精度模式下,直接返回。

    void hrtimer_run_queues(void)
    {      
            struct timerqueue_node *node;
            struct hrtimer_cpu_base *cpu_base = &__get_cpu_var(hrtimer_bases);
            struct hrtimer_clock_base *base;
            int index, gettime = 1, raise = 0;
            if (hrtimer_hres_active())        /* 判定是否启用高精度模式。如果启用,则直接退出。 */
                    return;
            for (index = 0; index < HRTIMER_MAX_CLOCK_BASES; index++) {        /* 遍历各个时间基准系统。 */
                    base = &cpu_base->clock_base[index];
                    if (!timerqueue_getnext(&base->active))
                            continue;
                    if (gettime) {
                            hrtimer_get_softirq_time(cpu_base);
                            gettime = 0;
                    }
                    raw_spin_lock(&cpu_base->lock);
                    while ((node = timerqueue_getnext(&base->active))) {        /* 获取base->active红黑树中的最早到期节点。*/
                            struct hrtimer *timer;
                            timer = container_of(node, struct hrtimer, node);
                            if (base->softirq_time.tv64 <=
                                            hrtimer_get_expires_tv64(timer))        /* 当前时间小于定时器timer的到期时间,说明此时钟基准的定时器
                                                                                       未到期,直接退出循环。*/
                                    break;
                            if (!hrtimer_rt_defer(timer))
                                    __run_hrtimer(timer, &base->softirq_time);        /* 到期则使用__run_hrtimer函数进行处理。 */
                            else
                                    raise = 1;
                    }
                    raw_spin_unlock(&cpu_base->lock);
            }
            if (raise)
                    raise_softirq_irqoff(HRTIMER_SOFTIRQ);
    }

       如果hrtimer_hres_active返回false,说明目前处于低精度模式下,则继续处理。它用一个for循环便利各个时间基准系统,查询每个hrtimer_clock_base对应红黑树的左下节点,判断其是否到期。如果到期,则使用__run_hrtimer函数,对到期定时器进行处理。包括,调用定时器回调函数,从红黑树中移除定时器,根据回调函数返回值决定是否重启该定时器等。
      函数中,while循环可以不断的使用timerqueue_getnext获取红黑树中的左下节点next,是因为__run_hrtimer会在处理过程中,移除到期的定时器,从而新的最早到期的节点会被更新到next字段中,使得循环可以一直执行,知道没有到期的定时器为止。

    3.4.2 高精度模式
      在切换到高精度模式后,原来给cpu提供tick时间的tick_device会被高精度定时器系统接管,它的中断时间回调函数被设置为hrtimer_interrupt,红黑树中最左下节点的定时器的到期时间被编程到该clock_event_device中。这样,每次clock_event_device的中断意味着有意个高精度定时器到期。另外,当timerkeeper系统中的时间需要修正,后者clock_event_device的到期事件时间被重新编程时,系统会发出HRTIMER_SOFTIRQ软中断,软中断的处理函数run_hrtimer_softirq最终会调用hrtimer_interrupt函数对定时器进行处理,所在在这里,我们只需要讨论hrtimer_interrupt函数即可。
      hrtimer_interrupt函数的前半部分和低精度模式下的hrtimer_run_queues函数完成相同的事情。它用一个for循环遍历各个时间基准系统,查询每个hrtimer_clock_base对应的红黑树的左下节点,判断它是否到期,如果到期,通过__run_hrtimer函数,对到期定时器进行处理。高精度定时器在处理完所有到期定时器之后,下一个定到期定时器的到期时间保存在变量expires_next中,接下来的工作就是把这个到期时间编程到tick_device中。

    void hrtimer_interrupt(struct clock_event_device *dev)
    {
            struct hrtimer_cpu_base *cpu_base = &__get_cpu_var(hrtimer_bases);
            ktime_t expires_next, now, entry_time, delta;
            int i, retries = 0, raise = 0;
            BUG_ON(!cpu_base->hres_active);
            cpu_base->nr_events++;
            dev->next_event.tv64 = KTIME_MAX;
            entry_time = now = ktime_get();
    retry:
            expires_next.tv64 = KTIME_MAX;
            raw_spin_lock(&cpu_base->lock);
            /*
             * We set expires_next to KTIME_MAX here with cpu_base->lock
             * held to prevent that a timer is enqueued in our queue via
             * the migration code. This does not affect enqueueing of
             * timers which run their callback and need to be requeued on
             * this CPU.
             */
            cpu_base->expires_next.tv64 = KTIME_MAX;
            for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++) {                /* 与hrtimer_run_queues一致,遍历各时间基准,查询到期的定时器并使用
                                                                       __run_hrtimer进行处理。 */
                    struct hrtimer_clock_base *base;
                    struct timerqueue_node *node;
                    ktime_t basenow;
                    if (!(cpu_base->active_bases & (1 << i)))
                            continue;
                    base = cpu_base->clock_base + i;
                    basenow = ktime_add(now, base->offset);
                    while ((node = timerqueue_getnext(&base->active))) {
                            struct hrtimer *timer;
                            timer = container_of(node, struct hrtimer, node);
                            trace_hrtimer_interrupt(raw_smp_processor_id(),
                                ktime_to_ns(ktime_sub(
                                    hrtimer_get_expires(timer), basenow)),
                                current,
                                timer->function == hrtimer_wakeup ?
                                container_of(timer, struct hrtimer_sleeper,
                                    timer)->task : NULL);
                            /*
                             * The immediate goal for using the softexpires is
                             * minimizing wakeups, not running timers at the
                             * earliest interrupt after their soft expiration.
                             * This allows us to avoid using a Priority Search
                             * Tree, which can answer a stabbing querry for
                             * overlapping intervals and instead use the simple
                             * BST we already have.
                             * We don't add extra wakeups by delaying timers that
                             * are right-of a not yet expired timer, because that
                             * timer will have to trigger a wakeup anyway.
                             */
                            if (basenow.tv64 < hrtimer_get_softexpires_tv64(timer)) {
                                    ktime_t expires;
                                    expires = ktime_sub(hrtimer_get_expires(timer),
                                                        base->offset);
                                    if (expires.tv64 < expires_next.tv64)
                                            expires_next = expires;
                                    break;
                            }
                            if (!hrtimer_rt_defer(timer))        /* 判断timer->irqsafe是否等于1,如果相等,然会0。*/
                                    __run_hrtimer(timer, &basenow);
                            else
                                    raise = 1;
                    }
            }
            /*
             * Store the new expiry value so the migration code can verify
             * against it.
             */
            cpu_base->expires_next = expires_next;        /* 记录下一个即将到期的定时器的时间。*/
            raw_spin_unlock(&cpu_base->lock);
            /* Reprogramming necessary ? */
            if (expires_next.tv64 == KTIME_MAX ||
                !tick_program_event(expires_next, 0)) {        /* 如果此时tick_program_event返回非0值,表示过期时间已经在当前时间的前面,
                                                               通常可能由以下原因造成:1 系统正在被调试跟踪,导致时间在走,程序不走。2
                                                               定时器的回调函数花了太长的时间。3 系统运行在虚拟机中,而虚拟机被调度导致
                                                               停止运行。默认设置成功,tick_program_event返回0。hrtimer_interrupt
                                                               函数执行if中的代码,程序根据raise的值,判断是否唤醒HRTIMER_SOFTIRQ后
                                                               退出。*/
                    cpu_base->hang_detected = 0;
                    if (raise)
                            raise_softirq_irqoff(HRTIMER_SOFTIRQ);
                    return;
            }
            /*
             * The next timer was already expired due to:
             * - tracing
             * - long lasting callbacks
             * - being scheduled away when running in a VM
             *
             * We need to prevent that we loop forever in the hrtimer
             * interrupt routine. We give it 3 attempts to avoid
             * overreacting on some spurious event.
             */
            now = ktime_get();
            cpu_base->nr_retries++;
            if (++retries < 3)                /* 为了避免当前时间已经过了下一个定时器到期时间的发生,系统提供三次机会,重新执行之前的循环
                                               处理到期的定时器。 */
                    goto retry;
            /*
             * Give the system a chance to do something else than looping
             * here. We stored the entry time, so we know exactly how long
             * we spent here. We schedule the next event this amount of
             * time away.
             */
            cpu_base->nr_hangs++;
            cpu_base->hang_detected = 1;
            delta = ktime_sub(now, entry_time);                        /* 计算本次总循环的时间。now为当前时间,entry_time为进入hrtimer_interrupt的时间。*/
            if (delta.tv64 > cpu_base->max_hang_time.tv64)
                    cpu_base->max_hang_time = delta;
            /*
             * Limit it to a sensible value as we enforce a longer
             * delay. Give the CPU at least 100ms to catch up.
             */
            if (delta.tv64 > 100 * NSEC_PER_MSEC)                                /* tick_device的到期时间被强制设定在100ms以内。*/
                    expires_next = ktime_add_ns(now, 100 * NSEC_PER_MSEC);
            else
                    expires_next = ktime_add(now, delta);
            tick_program_event(expires_next, 1);                                /* 设置下一次到期时间。 */
            printk_once(KERN_WARNING "hrtimer: interrupt took %llu ns\n",
                        ktime_to_ns(delta));
    }

    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?立即注册

    x
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    小黑屋|手机版|Archiver|Ubuntu Kylin    

    GMT+8, 2018-12-18 00:05 , Processed in 0.029738 second(s), 9 queries , File On.

    Copyright ©2013-2018 Ubuntu Kylin. All Rights Reserved .

    ICP No. 15002470-2 Tianjin

    快速回复 返回顶部 返回列表