【原創】Linux虛擬化KVM-Qemu分析(七)之timer虛擬化
背景
Read the fucking source code!
–By 魯迅A picture is worth a thousand words.
–By 高爾基
說明:
- KVM版本:5.9.1
- QEMU版本:5.0.0
- 工具:Source Insight 3.5, Visio
- 文章同步在部落格園:
//www.cnblogs.com/LoyenWang/
1. 概述
先從作業系統的角度來看一下timer的作用吧:
通過timer的中斷,OS實現的功能包括但不局限於上圖:
- 定時器的維護,包括用戶態和內核態,當指定時間段過去後觸發事件操作,比如IO操作註冊的超時定時器等;
- 更新系統的運行時間、wall time等,此外還保存當前的時間和日期,以便能通過
time()
等介面返回給用戶程式,內核中也可以利用其作為文件和網路包的時間戳; - 調度器在調度任務分配給CPU時,也會去對task的運行時間進行統計計算,比如CFS調度,Round-Robin調度等;
- 資源使用統計,比如系統負載的記錄等,此外用戶使用top命令也能進行查看;
timer就像是系統的脈搏,重要性不言而喻。ARMv8架構處理器提供了一個Generic Timer,與GIC類似,Generic Timer在硬體上也支援了虛擬化,減少了軟體模擬帶來的overhead。
本文將圍繞著ARMv8的timer虛擬化來展開。
2. ARMv8 Timer虛擬化
2.1 Generic Timer
看一下ARMv8架構下的CPU內部圖:
Generic Timer
提供了一個系統計數器,用於測量真實時間的消逝;Generic Timer
支援虛擬計數器,用於測量虛擬的時間消逝,一個虛擬計數器對應一個虛擬機;Timer
可以在特定的時間消逝後觸發事件,可以設置成count-up
計數或者count-down
計數;
來看一下Generic Timer
的簡圖:
或者這個:
System Counter
位於Always-on
電源域,以固定頻率進行系統計數的增加,System Counter
的值會廣播給系統中的所有核,所有核也能有一個共同的基準了,System Counter
的頻率範圍為1-50MHZ,系統計數值的位寬在56-64bit之間;- 每個核有一組timer,這些timer都是一些比較器,與
System Counter
廣播過來的系統計數值進行比較,軟體可以配置固定時間消逝後觸發中斷或者觸發事件; - 每個核提供的timer包括:1)
EL1 Physical timer
;2)EL1 Virtual timer
;此外還有在EL2和EL3下提供的timer,具體取決於ARMv8的版本; - 有兩種方式可以配置和使用一個timer:1)
CVAL(comparatoer)
暫存器,通過設置比較器的值,當System Count >= CVAL
時滿足觸發條件;2)TVAL
暫存器,設置TVAL
暫存器值後,比較器的值CVAL = TVAL + System Counter
,當System Count >= CVAL
時滿足觸發條件,TVAL
是一個有符號數,當遞減到0時還會繼續遞減,因此可以記錄timer是在多久之前觸發的; - timer的中斷是私有中斷
PPI
,其中EL1 Physical Timer
的中斷號為30,EL1 Virtual Timer
的中斷號為27; - timer可以配置成觸發事件產生,當CPU通過
WFE
進入低功耗狀態時,除了使用SEV
指令喚醒外,還可以通過Generic Timer
產生的事件流來喚醒;
2.2 虛擬化支援
Generic Timer
的虛擬化如下圖:
- 虛擬的timer,同樣也有一個count值,計算關係:
Virtual Count = Physical Count - <offset>
,其中offset的值放置在CNTVOFF
暫存器中,CNTPCT/CNTVCT
分別用於記錄當前物理/虛擬的count值; - 如果EL2沒有實現,則將offset設置為0,,物理的計數器和虛擬的計數器值相等;
Physical Timer
直接與System counter
進行比較,Virtual Timer
在Physical Timer
的基礎上再減去一個偏移;- Hypervisor負責為當前調度運行的vCPU指定對應的偏移,這種方式使得虛擬時間只會覆蓋vCPU實際運行的那部分時間;
示例如下:
- 6ms的時間段里,每個vCPU運行3ms,Hypervisor可以使用偏移暫存器來將vCPU的時間調整為其實際的運行時間;
3. 流程分析
3.1 初始化
先簡單看一下數據結構吧:
- 在ARMv8虛擬化中,使用
struct arch_timer_cpu
來描述Generic Timer
,從結構體中也能很清晰的看到層次結構,創建vcpu時,需要去初始化vcpu架構相關的欄位,其中就包含了timer; struct arch_timer_cpu
包含了兩個timer,分別對應物理timer和虛擬timer,此外還有一個高精度定時器,用於Guest處在非運行時的計時工作;struct arch_timer_context
用於描述一個timer需要的內容,包括了幾個欄位用於存儲暫存器的值,另外還描述了中斷相關的資訊;
初始化分為兩部分:
- 架構相關的初始化,針對所有的CPU,在kvm初始化時設置:
kvm_timer_hyp_init
函數完成相應的初始化工作;arch_timer_get_kvm_info
從Host Timer驅動中去獲取資訊,主要包括了虛擬中斷號和物理中斷號,以及timecounter資訊等;- vtimer中斷設置包括:判斷中斷的觸發方式(只支援電平觸發),註冊中斷處理函數
kvm_arch_timer_handler
,設置中斷到vcpu的affinity等; - ptimer中斷設置與vtimer中斷設置一樣,同時它的中斷處理函數也是
kvm_arch_timer_handler
,該處理函數也比較簡單,最終會調用kvm_vgic_inject_irq
函數來完成虛擬中斷注入給vcpu; cpuhp_setup_state
用來設置CPU熱插拔時timer的響應處理,而在kvm_timer_starting_cpu/kvm_timer_dying_cpu
兩個函數中實現的操作就是中斷的打開和關閉,僅此而已;
- vcpu相關的初始化,在創建vcpu時進行初始化設置:
- 針對vcpu的timer相關初始化比較簡單,回到上邊那張數據結構圖看一眼就明白了,所有的初始化工作都圍繞著
struct arch_timer_cpu
結構體; vcpu_timer
:用於獲取vcpu包含的struct arch_timer_cpu
結構;vcpu_vtimer/vcpu_ptimer
:用於獲取struct arch_timer_cpu
結構體中的struct arch_timer_context
,分別對應vtimer和ptimer;update_vtimer_cntvoff
:用於更新vtimer中的cntvoff值,讀取物理timer的count值,更新VM中所有vcpu的cntvoff值;hrtimer_init
:用於初始化高精度定時器,包含有三個,struct arch_timer_cpu
結構中有一個bg_timer
,vtimer和ptimer所對應的struct arch_timer_context
中分別對應一個;kvm_bg_timer_expire
:bg_timer
的到期執行函數,當需要調用kvm_vcpu_block
讓vcpu睡眠時,需要先啟動bg_timer
,bg_timer
到期時再將vcpu喚醒;kvm_hrtimer_expire
:vtimer和ptimer的到期執行函數,最終通過調用kvm_timer_update_irq
來向vcpu注入中斷;
3.2 用戶層訪問
可以從用戶態對vtimer進行讀寫操作,比如Qemu中,流程如下:
- 用戶態創建完vcpu後,可以通過vcpu的文件描述符來進行暫存器的讀寫操作;
- 以ARM為例,ioctl通過
KVM_SET_ONE_REG/KVM_GET_ONE_REG
將最終觸發暫存器的讀寫; - 如果操作的是timer的相關暫存器,則通過
kvm_arm_timer_set_reg
和kvm_arm_timer_get_reg
來完成; - 讀寫的暫存器包括虛擬timer的CTL/CVAL,以及物理timer的CTL/CVAL等;
3.3 Guest訪問
Guest對Timer的訪問,涉及到系統暫存器的讀寫,將觸發異常並Trap到Hyp進行處理,流程如下:
- Guest OS訪問系統暫存器時,Trap到Hypervisor進行處理;
- Hypervisor對異常退出進行處理,如果發現是訪問系統暫存器造成的異常,則調用
kvm_handle_sys_reg
來處理; kvm_handle_sys_reg
:調用emulate_sys_reg
來對系統暫存器進行模擬,在該函數中首先會查找訪問的是哪一個暫存器,然後再去調用相應的回調函數;- kvm中維護了
struct sys_reg_desc sys_reg_descs[]
系統暫存器的描述表,其中struct sys_reg_desc
結構體中包含了對該暫存器操作的函數指針,用於指向最終的操作函數,比如針對Timer的kvm_arm_timer_write_sysreg/kvm_arm_timer_read_sysreg
讀寫操作函數; - Timer的讀寫操作函數,主要在
kvm_arm_timer_read/kvm_arm_timer_write
中完成,實現的功能就是根據物理的count值和offset來計算等;
timer的虛擬化還是比較簡單,就此打住了。
PS:
按計劃,接下里該寫IO虛擬化了,然後緊接著Qemu的源碼相關分析。不過,在寫IO虛擬化之前,我會先去講一下PCIe的驅動框架,甚至可能還會去研究一下網路,who knows,反正這些也都是IO相關。
Any way,I will be back soon!
參考
《AArch64 Programmer's Guides Generic Timer》
《Arm Architecture Reference Manual》
歡迎關注個人公眾號,不定期更新內核相關技術文章