Linux時間子系統之四(2):定時器…

4. tick_device

當內核沒有配置成支持高精度定時器時,系統的tick由tick_device產生,tick_device其實是clock_event_device的簡單封裝,它內嵌了一個clock_event_device指針和它的工作模式:
[cpp] viewplaincopy
  1. struct tick_device {  
  2.     struct clock_event_device *evtdev;  
  3.     enum tick_device_mode mode;  
  4. };  
在kernel/time/tick-common.c中,定義了一個per-cpu的tick_device全局變量,tick_cpu_device:
[cpp] viewplaincopy
  1.   
  2. DEFINE_PER_CPU(struct tick_device, tick_cpu_device);  
前面曾經說過,當machine的代碼爲每個cpu註冊clock_event_device時,通知回調函數tick_notify會被調用,進而進入tick_check_new_device函數,下面讓我們看看該函數如何工作,首先,該函數先判斷註冊的clock_event_device是否可用於本cpu,然後從per-cpu變量中取出本cpu的tick_device:
[cpp] viewplaincopy
  1. static int tick_check_new_device(struct clock_event_device *newdev)  
  2. {  
  3.         ......  
  4.     cpu = smp_processor_id();  
  5.     if (!cpumask_test_cpu(cpu, newdev->cpumask))  
  6.         goto out_bc;  
  7.   
  8.     td = &per_cpu(tick_cpu_device, cpu);  
  9.     curdev = td->evtdev;  
如果不是本地clock_event_device,會做進一步的判斷:如果不能把irq綁定到本cpu,則放棄處理,如果本cpu已經有了一個本地clock_event_device,也放棄處理:

[cpp] viewplaincopy
  1. if (!cpumask_equal(newdev->cpumask, cpumask_of(cpu))) {  
  2.                ......  
  3.     if (!irq_can_set_affinity(newdev->irq))  
  4.         goto out_bc;  
  5.                ......  
  6.     if (curdev && cpumask_equal(curdev->cpumask, cpumask_of(cpu)))  
  7.         goto out_bc;  
  8. }  
反之,如果本cpu已經有了一個clock_event_device,則根據是否支持單觸發模式和它的rating值,決定是否替換原來舊的clock_event_device:

[cpp] viewplaincopy
  1. if (curdev) {  
  2.     if ((curdev->features & CLOCK_EVT_FEAT_ONESHOT) &&  
  3.         !(newdev->features & CLOCK_EVT_FEAT_ONESHOT))  
  4.         goto out_bc;  // 新的不支持單觸發,但舊的支持,所以不能替換  
  5.     if (curdev->rating >= newdev->rating)  
  6.         goto out_bc;  // 舊的比新的精度高,不能替換  
  7. }  
在這些判斷都通過之後,說明或者來cpu還沒有綁定tick_device,或者是新的更好,需要替換:
[cpp] viewplaincopy
  1. if (tick_is_broadcast_device(curdev)) {  
  2.     clockevents_shutdown(curdev);  
  3.     curdev = NULL;  
  4. }  
  5. clockevents_exchange_device(curdev, newdev);  
  6. tick_setup_device(td, newdev, cpu, cpumask_of(cpu));  
上面的tick_setup_device函數負責重新綁定當前cpu的tick_device和新註冊的clock_event_device,如果發現是當前cpu第一次註冊tick_device,就把它設置爲TICKDEV_MODE_PERIODIC模式,如果是替換舊的tick_device,則根據新的tick_device的特性,設置爲TICKDEV_MODE_PERIODIC或TICKDEV_MODE_ONESHOT模式。可見,在系統的啓動階段,tick_device是工作在週期觸發模式的,直到框架層在合適的時機,纔會開啓單觸發模式,以便支持NO_HZ和HRTIMER。

5. tick事件的處理--最簡單的情況

clock_event_device最基本的應用就是實現tick_device,然後給系統定期地產生tick事件,通用時間框架對clock_event_device和tick_device的處理相當複雜,因爲涉及配置項:CONFIG_NO_HZ和CONFIG_HIGH_RES_TIMERS的組合,兩個配置項就有4種組合,這四種組合的處理都有所不同,所以這裏我先只討論最簡單的情況:
  • CONFIG_NO_HZ == 0;
  • CONFIG_HIGH_RES_TIMERS == 0;
在這種配置模式下,我們回到上一節的tick_setup_device函數的最後:
[cpp] viewplaincopy
  1. if (td->mode == TICKDEV_MODE_PERIODIC)  
  2.     tick_setup_periodic(newdev, 0);  
  3. else  
  4.     tick_setup_oneshot(newdev, handler, next_event);  
因爲啓動期間,第一個註冊的tick_device必然工作在TICKDEV_MODE_PERIODIC模式,所以tick_setup_periodic會設置clock_event_device的事件回調字段event_handler爲tick_handle_periodic,工作一段時間後,就算有新的支持TICKDEV_MODE_ONESHOT模式的clock_event_device需要替換,再次進入tick_setup_device函數,tick_setup_oneshot的handler參數也是之前設置的tick_handle_periodic函數,所以我們考察tick_handle_periodic即可:
[cpp] viewplaincopy
  1. void tick_handle_periodic(struct clock_event_device *dev)  
  2. {  
  3.     int cpu = smp_processor_id();  
  4.     ktime_t next;  
  5.   
  6.     tick_periodic(cpu);  
  7.   
  8.     if (dev->mode != CLOCK_EVT_MODE_ONESHOT)  
  9.         return;  
  10.   
  11.     next = ktime_add(dev->next_event, tick_period);  
  12.     for (;;) {  
  13.         if (!clockevents_program_event(dev, next, false))  
  14.             return;  
  15.         if (timekeeping_valid_for_hres())  
  16.             tick_periodic(cpu);  
  17.         next = ktime_add(next, tick_period);  
  18.     }  
  19. }  
該函數首先調用tick_periodic函數,完成tick事件的所有處理,如果是週期觸發模式,處理結束,如果工作在單觸發模式,則計算並設置下一次的觸發時刻,這裏用了一個循環,是爲了防止當該函數被調用時,clock_event_device中的計時實際上已經經過了不止一個tick週期,這時候,tick_periodic可能被多次調用,使得jiffies和時間可以被正確地更新。tick_periodic的代碼如下:
[cpp] viewplaincopy
  1. static void tick_periodic(int cpu)  
  2. {  
  3.     if (tick_do_timer_cpu == cpu) {  
  4.         write_seqlock(&xtime_lock);  
  5.   
  6.           
  7.         tick_next_period = ktime_add(tick_next_period, tick_period);  
  8.   
  9.         do_timer(1);  
  10.         write_sequnlock(&xtime_lock);  
  11.     }  
  12.   
  13.     update_process_times(user_mode(get_irq_regs()));  
  14.     profile_tick(CPU_PROFILING);  
  15. }  
如果當前cpu負責更新時間,則通過do_timer進行以下操作:
  • 更新jiffies_64變量;
  • 更新牆上時鐘;
  • 每10個tick,更新一次cpu的負載信息;
調用update_peocess_times,完成以下事情:
  • 更新進程的時間統計信息;
  • 觸發TIMER_SOFTIRQ軟件中斷,以便系統處理傳統的低分辨率定時器;
  • 檢查rcu的callback;
  • 通過scheduler_tick觸發調度系統進行進程統計和調度工作;

發佈了6 篇原創文章 · 獲贊 3 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章