參考: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負責全局時鍾相關事宜,(該CPU放棄該職責時,
| 應該如何處理,可以參考前一篇記錄) | 此處略過,自行查看; | | 關注共同點: | 每個個cpu 的 tick_device首次運行該函數,還會將其 | 工作模式設置為TICKDEV_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

