4. tick_device
當內核沒有配置成支持高精度定時器時,系統的tick由tick_device產生,tick_device其實是clock_event_device的簡單封裝,它內嵌了一個clock_event_device指針和它的工作模式:
-
struct tick_device {
-
struct clock_event_device *evtdev;
-
enum tick_device_mode mode;
-
};
在kernel/time/tick-common.c中,定義了一個per-cpu的tick_device全局變量,tick_cpu_device:
-
-
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:
-
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;
如果不是本地clock_event_device,會做進一步的判斷:如果不能把irq綁定到本cpu,則放棄處理,如果本cpu已經有了一個本地clock_event_device,也放棄處理:
-
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;
-
}
反之,如果本cpu已經有了一個clock_event_device,則根據是否支持單觸發模式和它的rating值,決定是否替換原來舊的clock_event_device:
-
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; // 舊的比新的精度高,不能替換
-
}
在這些判斷都通過之後,說明或者來cpu還沒有綁定tick_device,或者是新的更好,需要替換:
-
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));
上面的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函數的最後:
-
if (td->mode == TICKDEV_MODE_PERIODIC)
-
tick_setup_periodic(newdev, 0);
-
else
-
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即可:
-
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);
-
}
-
}
該函數首先調用tick_periodic函數,完成tick事件的所有處理,如果是週期觸發模式,處理結束,如果工作在單觸發模式,則計算並設置下一次的觸發時刻,這裏用了一個循環,是爲了防止當該函數被調用時,clock_event_device中的計時實際上已經經過了不止一個tick週期,這時候,tick_periodic可能被多次調用,使得jiffies和時間可以被正確地更新。tick_periodic的代碼如下:
-
static void tick_periodic(int cpu)
-
{
-
if (tick_do_timer_cpu == cpu) {
-
write_seqlock(&xtime_lock);
-
-
-
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);
-
}
如果當前cpu負責更新時間,則通過do_timer進行以下操作:
- 更新jiffies_64變量;
- 更新牆上時鐘;
- 每10個tick,更新一次cpu的負載信息;
調用update_peocess_times,完成以下事情:
- 更新進程的時間統計信息;
- 觸發TIMER_SOFTIRQ軟件中斷,以便系統處理傳統的低分辨率定時器;
- 檢查rcu的callback;
- 通過scheduler_tick觸發調度系統進行進程統計和調度工作;