linux时间子系统(八)

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

}

猜你喜欢

转载自www.cnblogs.com/kylinos/p/9378781.html
今日推荐