詳解時鐘源的管理和虛擬化

前言

kvm-clock,tsc,hpet,acpi_pm,pit,rtc...這些詞看著都暈了@@

虛擬化場景下,容作者在這裡一一道來。

正文:

1,Linux clocksource

以Linux為例,查看操作系統的clocksource:

/sys/devices/system/clocksource/clocksource0目錄下;

available_clocksource是當前所有可用的clocksource

current_clocksource是當前正在使用的clocksource

所以,先不管這些名詞是什麼東西,怎麼實現的,起碼kvm-clock、tsc、hpet、acpi_pm是同一類的東西;並且,在Linux上,統一管理它們,並叫「clocksource」。它們提供了timer的能力,給操作系統使用。舉例來說,kernel中的調度,就需要使用timer來支持。

Advertisements

因為windows不開源,也不知道它的源代碼,最多就是根據外部的一些現象來猜測它的邏輯。具體的代碼分析都基於Linux。

2,clocksource management

主要邏輯代碼在linux-4.0.4/kernel/time/clocksource.c中實現:

clocksource實現了一個基本框架,提供了clocksource_register給具體的driver使用。

其中關鍵函數clocksource_enqueue

可見,所有的clocksource是根據rating參數大小被組織到了一個list中。linux默認使用rating最高的clocksource或者用戶修改過clocksource通過clocksource_select函數生效。

Advertisements

linux-4.0.4/arch/x86/kernel/kvmclock.c

聲明了kvm-clock的rating是400

同理,在linux-4.0.4/arch/x86/kernel/tsc.c中,可以看到tsc的rating是300

linux-4.0.4/arch/x86/kernel/hpet.c中,hpet的rating是250

linux-4.0.4/drivers/clocksource/acpi_pm.c中,acpi_pm的rating是200

linux-4.0.4/drivers/clocksource/i8253.c中,pit的rating是110

綜上,Linux大致實現了clocksource的管理框架,各個timer實現自己的drvier,並註冊到clocksource中。根據rating來看timer的性能:kvmclock>tsc>hpet>acpi_pm>pit。

3,kvmclock

kvmclock比較特殊,是在kvm虛擬化的時候使用的,物理機上並沒有---這也是和其他的timer很大的一個差別。

linux-4.0.4/arch/x86/kernel/kvmclock.c中,kvmclock實現的關鍵函數:

計算出來pvit的物理地址(這個「物理地址」,其實是Guest PhysicalAddress,即GPA),寫MSR寄存器,通過msr_kvm_system_time(MSR_KVM_SYSTEM_TIME),告訴Host它的pvit地址。

在Host中

linux-4.0.4/arch/x86/kvm/x86.c文件中kvm_set_msr_common函數:

這裡說一下,Guest中使用了wrmsr指令,Host會感知,然後進入到kvm_set_msr_common中處理。可見,通過kvm_gfn_to_hva_cache_init函數,會把傳過來的地址參數data記錄到pv_time中。這樣子就可以通過pv_time來直接修改Guest中的pvit

因為Host和Guest,使用的是相同的tsc,這裡還需要說明一個問題:

參考linux-4.0.4/arch/x86/kvm/x86.c文件中kvm_guest_time_update函數:

關於Guest中的時間的計算,pvtime中有兩個重要參數:tsc_timestamp和system_time

Guest Sytem Time = Guest Sytem Time +offset

為什麼要這麼麻煩的計算?

因為熱遷移。兩台Host的TSC不一樣,如果Dst Host的TSC比SrcHost的TSC小,那麼可能會讓Windows藍屏或者linuxpanic。 如果Dst Host的TSC比SrcHost的TSC大,那麼在Guest中看到tsc瞬間跳變。所以需要計算offset來調整。

另外,在Guest中,還需要做一次計算delta:

linux-4.0.4/arch/x86/include/asm/pvclock.h文件中的__pvclock_read_cycles函數中:

計算Host中讀取到TscGuest中讀取到的Tsc的差值,再計算出來delta,最後再計算出來Guest中的「kvmclock」。這裡把Host和Guest中前後兩次的Tsc微小差值都計算進去了,可見精度確實很高。

kvmclock是kvm虛擬化中rating最高的timer;當然,它的計算也真的很麻煩。

4,tsc

如果Guest中使用rdtsc指令,則會被Host攔截,Host中處理后返回給Guest:

linux-4.0.4/arch/x86/kvm/emulate.c中:

繼續調用,會到linux-4.0.4/arch/x86/kvm/vmx.c中:

先讀取出來Host的Tsc,在加上offset。offset的原理也是為了防止熱遷移tsc變小

這裡再說一下tsc的timer。因為tsc只是一個單調遞增的寄存器,本身不能產生timer的irq。設置了tsc之後,apic會產生timer的irq

5, hpet

hpet是純粹的qemu在用戶態模擬出來的。

代碼qemu-2.8.0-rc4/hw/timer/hpet.c

qemu的設備虛擬化邏輯。注意,是為hpet設備註冊了callback函數hpet_timer:

前面是計算下次超時的時間,最後一行很關鍵,向Guest里inject irq了。

所以,hpet的邏輯就很清晰了:qemu模擬了hpet device,並在用戶態周期性的injectirq,在Guest中就覺得是一個timer了

6, pit

代碼實現在:linux-4.0.4/arch/x86/kvm/i8254.c中:

關鍵代碼分析,Host為Guest的pit創建了一個內核線程,名稱就是「kvm-pit/PID」。所以,啟動一個qemu虛擬機之後,ps找到qemu的pid,然後就能看到一個對應的內核線程。稍微說一下,這個線程稍微特殊一點,不是一個常規定義的routine函數,是基於kernelworker機制

繼續分析,當Guest中激活了PIT之後,Host中調用create_pit_timer函數創建一個hrtimer。hrtimer會在interval后調用callback函數---pit_timer_fn。pit_timer_fn就是把一個work加入到workerqueue中,剛剛創建的kvm-pit線程就可以執行了。kvm-pit真正執行的,就是pit_do_work函數。

好吧,又向Guest中inject irq了。

這裡多說一句哈,在Guest Linux上,雖然創建了kvm-pit線程,但是卻沒有跑;在Guestwindows上,kvm-pit線程周期性的執行work。

後記:

總結上述timer的邏輯,或使用不同的硬體,或使用軟體,或在內核態,或在用戶態,都是周期性地向Guest中injectirq

KVM虛擬化CPU和管理內存,qemu虛擬化設備,加上上述的timer,還有就是apic,那麼龍珠就集齊了,可以召喚神龍了~

Advertisements

你可能會喜歡