時間管理——高精度時鐘、動態時鐘——實現

http://www.cnblogs.com/openix/p/3327202.html

參考:1、《Professional Linux Kernel Architecture》1ed_CN p714~p760

           2、http://blog.csdn.net/droidphone/article/details/7975694

           3、2.6.34

說明下,如果單純以unicore架構的sep611爲例,沒有必要(沒有高精度時鐘源、現行的unicore內核本身也不支持),龍芯沒有繼續向下做,所以此處以omap44xx爲參考做記錄。沒有記錄timer wheel,相關部分看下代碼就行了。

從系統初始化開始記錄:

複製代碼
start_kernel()
    |---->tick_init()
          |---->clockevents_register_notifier(&tick_notifier);
          |    將tick_notifier訂閱者掛入clockevents_chain
          |    中,通過clockevents_chain發佈有時鐘事件發生,
          |    進而通知訂閱者。
    |......
    |---->init_timers()
          |---->timer_cpu_notify(&timers_nb, 
          |          (unsigned long)CPU_UP_PREPARE,
          |          (void *)(long)smp_processor_id());
               |---->init_timers_cpu(cpu)
               |     初始化各個CPU的tvec_base(timer wheel)
               |
          |----register_cpu_notifier(&timers_nb);
          |----open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
    |
    |---->hrtimers_init()
    |......
    |---->timekeeping_init()
    |     該函數中初始化了大量的時鐘相關全局變量
    |     該函數中調用的函數有部分實際上沒有真正的實現
    |     關於RTC時間同步,可以參考rtc_hctosys函數
          |---->ntp_init()
          |---->clock = clocksource_default_clock();
          |     系統在啓動期間,如果計算機確實沒有提供更好的選擇
          |     (在啓動後,決不會如此),內核提供了一個基於
          |     jiffies的時鐘 clocksource_jiffies
          |---->timekeeper_setup_internals(clock);
          |---->set_normalized_timespec(&wall_to_monotonic,
          |                             -boot.tv_sec, -boot.tv_nsec);
    |---->time_init()
         |---->system_timer->init()
         |     system_timer和平臺相關,在setup_arch函數中被設置
         |     
         |     以omap4430爲例(儘管不熟習這個平臺)
    |----.......
    |---->rest_init()
         |---->kernel_thread(kernel_init, NULL, CLONE_FS |
         |                                  CLONE_SIGHAND);
         |     kernel_init中會調用smp_prepare_cpus(setup_max_cpus),
         |     完成各個核的時鐘事件設備註冊(有點不準確,實際上應該說是boot核
         |     但是該函數中調用了percpu_timer_setup,其它核都會執行該函數)


void __init smp_prepare_cpus(unsigned int max_cpus)
複製代碼
void __init smp_prepare_cpus(unsigned int max_cpus)                                                                
   |----......
   |---->percpu_timer_setup()
        |---->unsigned int cpu = smp_processor_id();
        |     struct clock_event_device *evt = 
        |            &per_cpu(percpu_clockevent, cpu);
        |     evt->cpumask = cpumask_of(cpu);
        |---->local_timer_setup(evt);
        |     即使是boot核在初始化階段使用的clock_event_device
        |     也將被替換因爲此處使用的rating值高達400
複製代碼

 

(記錄的最後有個ARM SMP的啓動簡略圖)

主核何時通知其它核啓動

複製代碼
static int __init kernel_init(void *unused)
     |----->smp_perpare_cpus(setup_max_cpus)
              |-----......
              |----->wakeup_secondary()
     |-----......
     |----->smp_init()
              |-----......
              |----->cpu_up(cpu)
                       |---->_cpu_up(cpu, 0)
                               |---->__cpu_up(cpu);
                                      |----->boot_secondary(cpu, idle)
複製代碼

 

關於其它核的初始化流程:

複製代碼
arch/arm/mach-omp2/omap-headsmp.S

ENTRY(omap_secondary_startup)
   hold:......
            bne hold
            b secondary_startup
END(omap_secondary_startup)


arch/arm/kernel/head.S

ENTRY(secondary_startup)
......
    mov r13, r12 @__secondary_switched address
......
ENDPROC(secondary_startup)

ENTRY(__secondary_switched)
......
    b secondary_start_kernel
ENDPROC(__secondary_switched)

asmlinkage void __cpuinit secondary_start_kernel(void)
{
    ........
    percpu_timer_setup();   //其它核上的clock_event_device設置
    .......
}
複製代碼

 

複製代碼

 

基於以上信息,我們可以得到系統初始化後每個核都有自己的時鐘事件設備,但是這個時候仍然採用低分辨率週期時鐘,我們自然會問:什麼時候切換成了高精度時鐘如下:
由於開始時週期性中斷處理函數仍是tick_handle_periodic,那麼第一次觸發時鐘中斷時:

複製代碼
void tick_handle_periodic(struct clock_event_device *dev)
  |----tick_periodic()
       |----......
       |---->update_process_times(user_mode(get_irq_regs()));
             |----......
             |---->run_local_timers()
                  |---->hrtimer_run_queues();
                  |     處理高精度時鐘,實際上由於還沒有激活高精度
                  |     時鐘功能,因此無效。但是一旦激活高精度時鐘,
                  |     其職責將變得很大。
                  |---->raise_softirq(TIMER_SOFTIRQ);
                  |     第一次喚醒TIMER_SOFTIRQ軟中斷時將激活高
                  |     精度時鐘。

複製代碼
static void run_timer_softirq(struct softirq_action *h)
   |---->hrtimer_run_pending()
  |    系統在hrtimer_run_pending函數中判斷系統的條
  |    件是否滿足切換到高精度模式NO_HZ模式也在該函數中
  |    判斷並切換。
  |---->__run_timers(base);
   |    timer wheel(更適用於timer out)

複製代碼
void hrtimer_run_pending(void)
  |---->if (tick_check_oneshot_change(!hrtimer_is_hres_enabled()))
   |         hrtimer_switch_to_hres();
   |     1、如果hrtimer_is_hres_enabled()返回0,說明沒有使能高精度
   |        時鐘功能。
   |       (1)如果使能動態時鐘則,則設置clock_event_device的
   |          event_handler爲tick_nohz_handler;裏面仍然用高精
   |          度時鐘來做tick的週期定時。
   |          也許你會很奇怪,爲什麼在沒有使能高精度時鐘的情況
   |          下,仍用高精度時鐘來管理tick_sched.sched_timer,
   |          其實這裏僅是爲了代碼複用,畢竟高精度時鐘中的
   |          hrtimer_forward可以便於我們週期性地不停的觸發tick
   | 
   |       (2)如果沒有使能動態時鐘,則仍保持event_handler爲
   |          tick_handle_periodic
   |    2、如果hrtimer_is_hres_enabled()返回1,說明可以激活高精
   |       度時鐘功能。通過hrtimer_switch_to_hres()激活高精度時鐘。


複製代碼
static int hrtimer_switch_to_hres(void)
   |----......
   |---->tick_init_highres()
        |---->tick_switch_to_oneshot(hrtimer_interrupt) 
        |     設置clock_event_device的event_handler爲
      |    hrtimer_interrupt
  |----......
   |---->tick_setup_sched_timer();
   |     這個函數使用tick_cpu_sched這個per-CPU變量來模擬原來
  |    tick device的功能。tick_cpu_sched本身綁定了
  |    一個hrtimer這個hrtimer的超時值爲下一個tick,
   |     回調函數爲tick_sched_timer因此,每過一個
   |     tick,tick_sched_timer就會被調用一次,在這回調函數中首先
   |     完成原來tick device的工作,然後設置下一次的超時值爲再下一個
   |     tick,從而達到了模擬週期運行的tick device的功能。如果所有的
   |     CPU在同一時間點被喚醒,併發執行tick時可能會出現。
   |
   |     關於tick_shced_timer自行看下
   |
   |---->retrigger_next_event(NULL);
   |     此處傳入的參數爲NULL,使得tick_device立刻產生到期中斷,
   |     hrtimer_interrupt被調用一次,然後下一個到期的定時器的時間
   |     會編程到tick_device中,從而完成高精度模式的切換。  
   |
   |     切換到高精度時鐘可以幹嘛?精確的定時,msleep依然基於
   |     timer wheel實現, 而nanosleep則基於高精度時鐘實現 
   |    (nanosleep->sys_nanosleep->
   |              htimer_nanosleep->do_nanosleep)
複製代碼

 

複製代碼

 

複製代碼

 

複製代碼

 

激活動態時鐘與沒有使用動態時鐘的區別主要在於當核調度IDLE進程時的區別(此處記錄的較爲簡略,沒有太深入)

複製代碼
void cpu_idle(void)
  |----tick_nohz_stop_sched_tick(1);
  |    不會再週期性產生中斷
  |    退出的情形包括:
  |    (1)一個外部中斷使某個進程變成可運行的,這要求時鐘機制恢復工作
  |    (2)下一個時鐘信號即將到期
  |    關於(2),因爲時鐘信號一定會到來(防止硬件溢出),此時仍會進入
  |    週期性中斷處理函數,問題在於,如果此時沒有激活新的進程,那麼我們
  |    可以把tick觸發時刻繼續推後,這需要注意在irq_exit也有可能會調
  |    用tick_nohz_stop_sched_tick(0);
  |----......
  |----tick_nohz_restart_sched_tick();
  |----......
複製代碼

 

=====================================================================

以上明白了高精度時鐘及動態時鐘的設置,下面記錄下如上記錄中沒有涉及到的部分細節。

hrtimers_init()

複製代碼
hrtimers_init()
  |---->hrtimer_cpu_notify(&hrtimers_nb, 
  |       (unsigned long)CPU_UP_PREPARE,
  |       (void *)(long)smp_processor_id());
       |---->init_hrtimers_cpu(cpu);
       |     初始化各個CPU的hrtimer_bases(高精度定時器,也可用於仿真週期性
       |     時鐘中斷) hrtimer_base是實現hrtimer的核心數據結構,通過
       |     hrtimer_bases,hrtimer可以管理掛在每一個CPU上的所有timer。
       |     每個CPU上的timer list不再使用timer wheel中多級鏈表的實現方式,
       |     而是採用紅黑樹(Red-Black Tree)來進行管理。每個hrtimer_bases
       |     都包含兩個clock_base,一個是CLOCK_REALTIME類型的,另一個是
       |     CLOCK_MONOTONIC類型的(即採用的時間基準不一樣)。hrtimer可以選
       |     擇其中之一來設置timer的expire time,可以是實際的時間,也可以是相
       |     對系統運行的時間。注意hrtimer_base定義時的初始值。
       |     hrtimer_base類型爲hrtimer_cpu_base,其中包含
       |       struct hrtimer_clock_base  clock_base[HRTIMER_MAX_CLOCK_BASES],
       |     注意hrtimer_clock_base中的index:
       |              用於區分CLOCK_MONOTONIC和CLOCK_REALTIME.
           |---->hrtimer_init_hres(cpu_base);
                |----base->expires_next.tv64 = KTIME_MAX;
                |    將要到期的下一個事件的絕對時間
                |----base->hres_active = 0;
                |    表示高分辨率模式是否已經啓用,還是隻提供低分辨率模式
  |----register_cpu_notifier(&hrtimers_nb);
  |----open_softirq(HRTIMER_SOFTIRQ, run_hrtimer_softirq);
複製代碼

 

ntp_init()

ntp_init()
  |---->ntp_clear()
  |---->hrtimer_init(&leap_timer, 
  |                CLOCK_REALTIMER, 
  |                HRTIMER_MODE_ABS);
  |----leap_timer.function = ntp_leap_second

 

omap2_gp_timer_init()

複製代碼
 omap2_gp_timer_init()
  |---->omap_dm_timer_init()
       |----dm_timers = omap4_dm_timers
       |----dm_timer_count = omap4_dm_timer_count
       |----dm_source_names = omap4_dm_source_names
       |----dm_source_clocks = omap4_dm_source_clocks
       |----for(i = 0; dm_source_names[i] != NULL; i++)
       |      dm_source_clocks[i] = 
       |           clk_get(NULL, dm_source_names[i]);
       |----I/O空間映射
  |---->omap2_gp_clockevent_init()
       |----gptimer = 
       |           omap_dm_timer_request_specific(gptimer_id);
       |    獲取一個時鐘
       |----tick_rate = 
       |           clk_get_rate(omap_dm_timer_get_fclk(gptimer));
       |    獲取時鐘的頻率
       |----omap2_gp_timer_irq.dev_id = (void *)gptimer;
       |----setup_irq(omap_dm_timer_get_irq(gptimer), 
       |              &omap2_gp_timer_irq);
       |    設置中斷
       |
       |    以下設置全局時鐘事件設備
       |----clockevent_gpt.mult = 
       |           div_sc(tick_rate, 
       |                  NSEC_PER_SEC,
       |                  clockevent_gpt.shift);
       |----clockevent_gpt.max_delta_ns =
       |           clockevent_delta2ns(0xffffffff, &clockevent_gpt);
       |----clockevent_gpt.min_delta_ns =
       |           clockevent_delta2ns(3, &clockevent_gpt);
       |----clockevent_gpt.cpumask = cpumask_of(0);
       |    將該clock_event_device即clockevent_gpt制定給CPU0
       |    SO,其它的核呢?請留意start_kernel->rest_init->kernel_init線程中的
       |    smp_prepare_cpus(setup_max_cpus);
       |
       |----clockevents_register_device(&clockevent_gpt);
       |    非常重要
  |---->omap2_gp_clocksource_init()
       |----gpt_clocksource = 
       |    omap_dm_timer_request();
       |    獲取一個時鐘源
       |    omap_dm_timer_set_source(gpt_clocksource, 
       |           OMAP_TIMER_SRC_SYS_CLK);
       |
       |    tick_rate = clk_get_rate(
       |         omap_dm_timer_get_fclk(gpt_clocksource));
       |
       |    tick_period = (tick_rate / HZ) - 1;
       |    omap_dm_timer_set_load_start(gpt_clocksource, 1, 0);
       |
       |----clocksource_gpt.mult =
       |----    clocksource_khz2mult(tick_rate/1000, 
       |                 clocksource_gpt.shift);
       |----clocksource_register(&clocksource_gpt)


void clockevents_register_device(struct clock_event_device *dev)
複製代碼
void clockevents_register_device(struct clock_event_device *dev)
  |---->list_add(&dev->list, &clockevents_device)
  |---->clockevents_do_notify(CLOCK_EVT_NOTIFIY_ADD, dev);
  |     注意tick_init中,在clockevents_chain中加入的訂閱者
       |---->return tick_check_new_device(dev); 


static int tick_check_new_device(struct clock_event_device *newdev)
複製代碼
static int tick_check_new_device(struct clock_event_device *newdev)
  |----strcut tick_device *td;
  |    td = &per_cpu(tick_cpu_device, cpu)
  |    curdev = td->evtdev
  |----clockevents_exchange_device(curdev, newdev)
  |    關閉了newdev(後面會再開啓)
  |----tick_setup_device(td, newdev, cpu, cpumask_of(cpu));
       |----注意這個函數,因爲在其中會在首次運行該函數時,
     |    設置一個局部時鐘,負責全局時鐘相關事宜
       |    此處略過,自行查看;
       |
       |    關注共同點:
       |    如果個cpu 的 tick_device首次運行該函數,還會將其
       |    工作模式設置爲IICKDEV_MODE_PERIODIC
            |----td->evtdev = newdev
            |----if(td->mode = TICKDEV_MODE_PERIODIC)
            |    首次一定成立,因此:
            |        tick_setup_periodic(newdev, 0)
  |---->if(newdev->features & CLOCK_EVT_FEAT_ONESHOT)
    |--->tick_oneshot_notify()
        |--->struct_sched *ts = &_get_cpu_var(tick_cpu_sched)
        |    tick_sched是一個專門的數據結構,用於管理週期時鐘相關的所有信息,
        |    由全局變量tick_cpu_sched爲每個CPU分別提供一個該結構的實例
        |--->set_bit(0, &ts->check_clocks)

void tick_setup_periodic(struct clock_event_device *dev, int broadcast)
void tick_setup_periodic(struct clock_event_device *dev, int broadcast)
   |----假設傳入上文所示參數
   |----tick_set_periodic_handler(dev, broadcast)
        |當broadcast爲0時,則:
        |---->dev->event_handler = tick_handle_periodic

複製代碼

 

複製代碼

int clocksource_register(struct clocksource *cs)
複製代碼
int clocksource_register(struct clocksource *cs)                                                                   
  |---->cs->max_idle_ns = clocksource_max_deferment(cs);
  |    計算在該時鐘源上可睡眠的最長時間(防止硬件溢出)
  |---->clocksource_enqueue(cs);
  |    將時鐘源加入clocksource_list鏈表
  |---->clocksource_select();
       |----struct clocksource *best, *cs;
       |----......
       |    選取當前clocksource_list上最好時鐘源(rating)
       |----if (curr_clocksource != best) {
       |        curr_clocksource = best;
       |        timekeeping_notify(curr_clocksource);}
  |---->clocksource_enqueue_watchdog(cs);
複製代碼

 

複製代碼

 

 

static void local_timer_setup(struct clock_event_device *evt)  (percpu_timer_setup中使用)

複製代碼
static void local_timer_setup(struct clock_event_device *evt)
   |初始化每個CPU的clock_event_device
   |boot核的tick_device在初始化階段將被替換
   |----evt->name       = "dummy_timer";
   |    evt->features   = CLOCK_EVT_FEAT_ONESHOT |
   |                      CLOCK_EVT_FEAT_PERIODIC |
   |                      CLOCK_EVT_FEAT_DUMMY;
   |    evt->rating     = 400;  
   |    evt->mult       = 1;    
   |    evt->set_mode   = broadcast_timer_set_mode;
   |    evt->broadcast  = smp_timer_broadcast;
   |---->clockevents_register_device(evt);
         |---->如果是首次進入該該函數,則與boot核情形相同,
         |     但是要注意boot核,因爲boot核此時將二次進入,
         |     首次進入被設置成了tick_handle_periodic,
         |     即使是boot核在二次進入的情形下,仍被設置成
         |     tick_handle_perodic;
         |
         |     如果在進入tick_setup_device後tick_device
         |     的模式爲TICKDEV_MODE_ONESHOT,此時的處理函數
         |     仍然是tick_handle_periodic,關鍵在於此時由於
         |     此時是單次觸發模式,因此tick_handle_periodic
         |     的行爲將會做適當的改變(主動設置下次tick的觸發時間)
複製代碼

 

 ARM SMP啓動簡略圖:

引述自:http://www.linux-arm.org/LinuxBootLoader/SMPBoot


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章