linux時間子系統(八)

3.3 定時器的添加

  hrtimer添加的流程圖以下:node

  在添加定時器到紅黑樹時,若是已經存在與紅黑樹上,必須得先刪除定時器,以後使用enqueue_hrtimer函數將hrtimer插入到紅黑樹上。若是當前添加的定時器是最先到期的,則須要從新設定定時器硬件的到期時間,須要將當前定時器的到期時間設置到定時器硬件,使其能夠最先獲得處理。react

3.4 定時器的處理

  高精度定時器系統有3個入口能夠對到期的定時器進行處理,分別是編程

  1 沒有切換到高精度模式時,在每一個jiffies的tick事件中斷中進行查詢和處理。app

  2 在HRTIMER_SOFTIRQ軟中斷中進行查詢和處理。函數

  3 切換到高精度模式之後,在每一個clock_event_device的到期事件的中斷中進行查詢和處理。oop

3.4.1 低精度模式

  系統並非一開始就會支持高精度模式,而是在系統啓動後的某個階段,等待全部的條件都知足後,纔會切換到高精度模式。當系統沒有切換到高精度模式時,全部的高精度定時器都運行在低精度模式下,在每一個jiffies的tick事件中斷中進行到期定時器的查詢和處理,顯然此時的精度和低分辨率定時器是同樣的(HZ級別)。低精度模式下,每一個tick事件中斷中,hrtimer_run_queues函數會被調用,由它完成定時器的到期處理。hrtimer_run_queue首先判斷目前高精度模式是否已經啓用,若是已經切換到高精度模式下,直接返回。this

void hrtimer_run_queues(void)spa

{       調試

        struct timerqueue_node *node;code

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

}

相關文章
相關標籤/搜索