早期的內核版本中,進程的調度基於一個稱之爲tick的時鐘滴答,通常使用時鐘中斷來定時地產生tick信號,每次tick定時中斷都會進行進程的統計和調度,並對tick進行計數,記錄在一個jiffies變量中,定時器的設計也是基於jiffies。這時候的內核代碼中,幾乎所有關於時鐘的操作都是在machine級的代碼中實現,很多公共的代碼要在每個平臺上重複實現。隨後,隨着通用時鐘框架的引入,內核需要支持高精度的定時器,爲此,通用時間框架爲定時器硬件定義了一個標準的接口:clock_event_device,machine級的代碼只要按這個標準接口實現相應的硬件控制功能,剩下的與平臺無關的特性則統一由通用時間框架層來實現。
/*****************************************************************************************************/
聲明:本博內容均由http://blog.csdn.net/droidphone原創,轉載請註明出處,謝謝!/*****************************************************************************************************/
1. 時鐘事件軟件架構
本系列文章的第一節中,我們曾經討論了時鐘源設備:clocksource,現在又來一個時鐘事件設備:clock_event_device,它們有何區別?看名字,好像都是給系統提供時鐘的設備,實際上,clocksource不能被編程,沒有產生事件的能力,它主要被用於timekeeper來實現對真實時間進行精確的統計,而clock_event_device則是可編程的,它可以工作在週期觸發或單次觸發模式,系統可以對它進行編程,以確定下一次事件觸發的時間,clock_event_device主要用於實現普通定時器和高精度定時器,同時也用於產生tick事件,供給進程調度子系統使用。時鐘事件設備與通用時間框架中的其他模塊的關係如下圖所示:
圖1.1 clock_event_device軟件架構
- 與clocksource一樣,系統中可以存在多個clock_event_device,系統會根據它們的精度和能力,選擇合適的clock_event_device對系統提供時鐘事件服務。在smp系統中,爲了減少處理器間的通信開銷,基本上每個cpu都會具備一個屬於自己的本地clock_event_device,獨立地爲該cpu提供時鐘事件服務,smp中的每個cpu基於本地的clock_event_device,建立自己的tick_device,普通定時器和高精度定時器。
- 在軟件架構上看,clock_event_device被分爲了兩層,與硬件相關的被放在了machine層,而與硬件無關的通用代碼則被集中到了通用時間框架層,這符合內核對軟件的設計需求,平臺的開發者只需實現平臺相關的接口即可,無需關注複雜的上層時間框架。
- tick_device是基於clock_event_device的進一步封裝,用於代替原有的時鐘滴答中斷,給內核提供tick事件,以完成進程的調度和進程信息統計,負載平衡和時間更新等操作。
2. 時鐘事件設備相關數據結構
2.1 struct clock_event_device
時鐘事件設備的核心數據結構是clock_event_device結構,它代表着一個時鐘硬件設備,該設備就好像是一個具有事件觸發能力(通常就是指中斷)的clocksource,它不停地計數,當計數值達到預先編程設定的數值那一刻,會引發一個時鐘事件中斷,繼而觸發該設備的事件處理回調函數,以完成對時鐘事件的處理。clock_event_device結構的定義如下:
- struct clock_event_device {
- void (*event_handler)(struct clock_event_device *);
- int (*set_next_event)(unsigned long evt,
- struct clock_event_device *);
- int (*set_next_ktime)(ktime_t expires,
- struct clock_event_device *);
- ktime_t next_event;
- u64 max_delta_ns;
- u64 min_delta_ns;
- u32 mult;
- u32 shift;
- enum clock_event_mode mode;
- unsigned int features;
- unsigned long retries;
- void (*broadcast)(const struct cpumask *mask);
- void (*set_mode)(enum clock_event_mode mode,
- struct clock_event_device *);
- unsigned long min_delta_ticks;
- unsigned long max_delta_ticks;
- const char *name;
- int rating;
- int irq;
- const struct cpumask *cpumask;
- struct list_head list;
- } ____cacheline_aligned;
event_handler 該字段是一個回調函數指針,通常由通用框架層設置,在時間中斷到來時,machine底層的的中斷服務程序會調用該回調,框架層利用該回調實現對時鐘事件的處理。
set_next_event 設置下一次時間觸發的時間,使用類似於clocksource的cycle計數值(離現在的cycle差值)作爲參數。
set_next_ktime 設置下一次時間觸發的時間,直接使用ktime時間作爲參數。
max_delta_ns 可設置的最大時間差,單位是納秒。
min_delta_ns 可設置的最小時間差,單位是納秒。
mult shift 與clocksource中的類似,只不過是用於把納秒轉換爲cycle。
mode 該時鐘事件設備的工作模式,兩種主要的工作模式分別是:
- CLOCK_EVT_MODE_PERIODIC 週期觸發模式,設置後按給定的週期不停地觸發事件;
- CLOCK_EVT_MODE_ONESHOT 單次觸發模式,只在設置好的觸發時刻觸發一次;
set_mode 函數指針,用於設置時鐘事件設備的工作模式。
rating 表示該設備的精度等級。
list 系統中註冊的時鐘事件設備用該字段掛在全局鏈表變量clockevent_devices上。
2.2 全局變量clockevent_devices
- /* Notification for clock events */
- static RAW_NOTIFIER_HEAD(clockevents_chain);
3. clock_event_device的初始化和註冊
- struct sys_timer {
- void (*init)(void);
- void (*suspend)(void);
- void (*resume)(void);
- #ifdef CONFIG_ARCH_USES_GETTIMEOFFSET
- unsigned long (*offset)(void);
- #endif
- };
- MACHINE_START(SMDK4412, "SMDK4412")
- /* Maintainer: Kukjin Kim <[email protected]> */
- /* Maintainer: Changhwan Youn <[email protected]> */
- .atag_offset = 0x100,
- .init_irq = exynos4_init_irq,
- .map_io = smdk4x12_map_io,
- .handle_irq = gic_handle_irq,
- .init_machine = smdk4x12_machine_init,
- .timer = &exynos4_timer,
- .restart = exynos4_restart,
- MACHINE_END
- static void __init exynos4_timer_init(void)
- {
- if (soc_is_exynos4210())
- mct_int_type = MCT_INT_SPI;
- else
- mct_int_type = MCT_INT_PPI;
- exynos4_timer_resources();
- exynos4_clocksource_init();
- exynos4_clockevent_init();
- }
- struct sys_timer exynos4_timer = {
- .init = exynos4_timer_init,
- };
- static struct clock_event_device mct_comp_device = {
- .name = "mct-comp",
- .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
- .rating = 250,
- .set_next_event = exynos4_comp_set_next_event,
- .set_mode = exynos4_comp_set_mode,
- };
- ......
- static void exynos4_clockevent_init(void)
- {
- clockevents_calc_mult_shift(&mct_comp_device, clk_rate, 5);
- ......
- mct_comp_device.cpumask = cpumask_of(0);
- clockevents_register_device(&mct_comp_device);
- setup_irq(EXYNOS4_IRQ_MCT_G0, &mct_comp_event_irq);
- }
- /*
- * Timer (local or broadcast) support
- */
- static DEFINE_PER_CPU(struct clock_event_device, percpu_clockevent);
- static int __cpuinit exynos4_local_timer_setup(struct clock_event_device *evt)
- {
- ......
- evt->name = mevt->name;
- evt->cpumask = cpumask_of(cpu);
- evt->set_next_event = exynos4_tick_set_next_event;
- evt->set_mode = exynos4_tick_set_mode;
- evt->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
- evt->rating = 450;
- clockevents_calc_mult_shift(evt, clk_rate / (TICK_BASE_CNT + 1), 5);
- ......
- clockevents_register_device(evt);
- ......
- enable_percpu_irq(EXYNOS_IRQ_MCT_LOCALTIMER, 0);
- ......
- return 0;
- }
圖3.1 clock_event_device的系統初始化
由上面的圖示可以看出,框架層的初始化步驟很簡單,又start_kernel開始,調用tick_init,它位於kernel/time/tick-common.c中,也只是簡單地調用clockevents_register_notifier,同時把類型爲notifier_block的tick_notifier作爲參數傳入,回看2.3節,clockevents_register_notifier註冊了一個通知鏈,這樣,當系統中的clock_event_device狀態發生變化時(新增,刪除,掛起,喚醒等等),tick_notifier中的notifier_call字段中設定的回調函數tick_notify就會被調用。接下來start_kernel調用了time_init函數,該函數通常定義在體系相關的代碼中,正如前面所討論的一樣,它主要完成machine級別對時鐘系統的初始化工作,最終通過clockevents_register_device註冊系統中的時鐘事件設備,把每個時鐘時間設備掛在clockevent_device全局鏈表上,最後通過clockevent_do_notify觸發框架層事先註冊好的通知鏈,其實就是調用了tick_notify函數,我們主要關注CLOCK_EVT_NOTIFY_ADD通知,其它通知請自行參考代碼,下面是tick_notify的簡化版本:
- static int tick_notify(struct notifier_block *nb, unsigned long reason,
- void *dev)
- {
- switch (reason) {
- case CLOCK_EVT_NOTIFY_ADD:
- return tick_check_new_device(dev);
- case CLOCK_EVT_NOTIFY_BROADCAST_ON:
- case CLOCK_EVT_NOTIFY_BROADCAST_OFF:
- case CLOCK_EVT_NOTIFY_BROADCAST_FORCE:
- ......
- case CLOCK_EVT_NOTIFY_BROADCAST_ENTER:
- case CLOCK_EVT_NOTIFY_BROADCAST_EXIT:
- ......
- case CLOCK_EVT_NOTIFY_CPU_DYING:
- ......
- case CLOCK_EVT_NOTIFY_CPU_DEAD:
- ......
- case CLOCK_EVT_NOTIFY_SUSPEND:
- ......
- case CLOCK_EVT_NOTIFY_RESUME:
- ......
- }
- return NOTIFY_OK;
- }
4. tick_device
- struct tick_device {
- struct clock_event_device *evtdev;
- enum tick_device_mode mode;
- };
- /*
- * Tick devices
- */
- DEFINE_PER_CPU(struct tick_device, tick_cpu_device);
- static int tick_check_new_device(struct clock_event_device *newdev)
- {
- ......
- cpu = smp_processor_id();
- if (!cpumask_test_cpu(cpu, newdev->cpumask))
- goto out_bc;
- td = &per_cpu(tick_cpu_device, cpu);
- curdev = td->evtdev;
- if (!cpumask_equal(newdev->cpumask, cpumask_of(cpu))) {
- ......
- if (!irq_can_set_affinity(newdev->irq))
- goto out_bc;
- ......
- if (curdev && cpumask_equal(curdev->cpumask, cpumask_of(cpu)))
- goto out_bc;
- }
- if (curdev) {
- if ((curdev->features & CLOCK_EVT_FEAT_ONESHOT) &&
- !(newdev->features & CLOCK_EVT_FEAT_ONESHOT))
- goto out_bc; // 新的不支持單觸發,但舊的支持,所以不能替換
- if (curdev->rating >= newdev->rating)
- goto out_bc; // 舊的比新的精度高,不能替換
- }
- if (tick_is_broadcast_device(curdev)) {
- clockevents_shutdown(curdev);
- curdev = NULL;
- }
- clockevents_exchange_device(curdev, newdev);
- tick_setup_device(td, newdev, cpu, cpumask_of(cpu));
5. tick事件的處理--最簡單的情況
- CONFIG_NO_HZ == 0;
- CONFIG_HIGH_RES_TIMERS == 0;
- if (td->mode == TICKDEV_MODE_PERIODIC)
- tick_setup_periodic(newdev, 0);
- else
- tick_setup_oneshot(newdev, handler, next_event);
- void tick_handle_periodic(struct clock_event_device *dev)
- {
- int cpu = smp_processor_id();
- ktime_t next;
- tick_periodic(cpu);
- if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
- return;
- next = ktime_add(dev->next_event, tick_period);
- for (;;) {
- if (!clockevents_program_event(dev, next, false))
- return;
- if (timekeeping_valid_for_hres())
- tick_periodic(cpu);
- next = ktime_add(next, tick_period);
- }
- }
- static void tick_periodic(int cpu)
- {
- if (tick_do_timer_cpu == cpu) {
- write_seqlock(&xtime_lock);
- /* Keep track of the next tick event */
- tick_next_period = ktime_add(tick_next_period, tick_period);
- do_timer(1);
- write_sequnlock(&xtime_lock);
- }
- update_process_times(user_mode(get_irq_regs()));
- profile_tick(CPU_PROFILING);
- }
- 更新jiffies_64變量;
- 更新牆上時鐘;
- 每10個tick,更新一次cpu的負載信息;
- 更新進程的時間統計信息;
- 觸發TIMER_SOFTIRQ軟件中斷,以便系統處理傳統的低分辨率定時器;
- 檢查rcu的callback;
- 通過scheduler_tick觸發調度系統進行進程統計和調度工作;