Read the fucking source code!
--By 魯迅A picture is worth a thousand words.
--By 高爾基說明:網絡
https://www.cnblogs.com/LoyenWang/
先從操做系統的角度來看一下timer的做用吧:數據結構
經過timer的中斷,OS實現的功能包括但不侷限於上圖:架構
time()
等接口返回給用戶程序,內核中也能夠利用其做爲文件和網絡包的時間戳;
timer就像是系統的脈搏,重要性不言而喻。ARMv8架構處理器提供了一個Generic Timer,與GIC相似,Generic Timer在硬件上也支持了虛擬化,減小了軟件模擬帶來的overhead。框架
本文將圍繞着ARMv8的timer虛擬化來展開。ide
看一下ARMv8架構下的CPU內部圖:函數
Generic Timer
提供了一個系統計數器,用於測量真實時間的消逝;Generic Timer
支持虛擬計數器,用於測量虛擬的時間消逝,一個虛擬計數器對應一個虛擬機;Timer
能夠在特定的時間消逝後觸發事件,能夠設置成count-up
計數或者count-down
計數;來看一下Generic Timer
的簡圖:工具
或者這個:ui
System Counter
位於Always-on
電源域,以固定頻率進行系統計數的增長,System Counter
的值會廣播給系統中的全部核,全部核也能有一個共同的基準了,System Counter
的頻率範圍爲1-50MHZ,系統計數值的位寬在56-64bit之間;System Counter
廣播過來的系統計數值進行比較,軟件能夠配置固定時間消逝後觸發中斷或者觸發事件;EL1 Physical timer
;2)EL1 Virtual timer
;此外還有在EL2和EL3下提供的timer,具體取決於ARMv8的版本;CVAL(comparatoer)
寄存器,經過設置比較器的值,當System Count >= CVAL
時知足觸發條件;2)TVAL
寄存器,設置TVAL
寄存器值後,比較器的值CVAL = TVAL + System Counter
,當System Count >= CVAL
時知足觸發條件,TVAL
是一個有符號數,當遞減到0時還會繼續遞減,所以能夠記錄timer是在多久以前觸發的;PPI
,其中EL1 Physical Timer
的中斷號爲30,EL1 Virtual Timer
的中斷號爲27;WFE
進入低功耗狀態時,除了使用SEV
指令喚醒外,還能夠經過Generic Timer
產生的事件流來喚醒;Generic Timer
的虛擬化以下圖:操作系統
Virtual Count = Physical Count - <offset>
,其中offset的值放置在CNTVOFF
寄存器中,CNTPCT/CNTVCT
分別用於記錄當前物理/虛擬的count值;Physical Timer
直接與System counter
進行比較,Virtual Timer
在Physical Timer
的基礎上再減去一個偏移;示例以下:指針
先簡單看一下數據結構吧:
struct arch_timer_cpu
來描述Generic Timer
,從結構體中也能很清晰的看到層次結構,建立vcpu時,須要去初始化vcpu架構相關的字段,其中就包含了timer;struct arch_timer_cpu
包含了兩個timer,分別對應物理timer和虛擬timer,此外還有一個高精度定時器,用於Guest處在非運行時的計時工做;struct arch_timer_context
用於描述一個timer須要的內容,包括了幾個字段用於存儲寄存器的值,另外還描述了中斷相關的信息;初始化分爲兩部分:
kvm_timer_hyp_init
函數完成相應的初始化工做;arch_timer_get_kvm_info
從Host Timer驅動中去獲取信息,主要包括了虛擬中斷號和物理中斷號,以及timecounter信息等;kvm_arch_timer_handler
,設置中斷到vcpu的affinity等;kvm_arch_timer_handler
,該處理函數也比較簡單,最終會調用kvm_vgic_inject_irq
函數來完成虛擬中斷注入給vcpu;cpuhp_setup_state
用來設置CPU熱插拔時timer的響應處理,而在kvm_timer_starting_cpu/kvm_timer_dying_cpu
兩個函數中實現的操做就是中斷的打開和關閉,僅此而已;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注入中斷;能夠從用戶態對vtimer進行讀寫操做,好比Qemu中,流程以下:
KVM_SET_ONE_REG/KVM_GET_ONE_REG
將最終觸發寄存器的讀寫;kvm_arm_timer_set_reg
和kvm_arm_timer_get_reg
來完成;Guest對Timer的訪問,涉及到系統寄存器的讀寫,將觸發異常並Trap到Hyp進行處理,流程以下:
kvm_handle_sys_reg
來處理;kvm_handle_sys_reg
:調用emulate_sys_reg
來對系統寄存器進行模擬,在該函數中首先會查找訪問的是哪個寄存器,而後再去調用相應的回調函數;struct sys_reg_desc sys_reg_descs[]
系統寄存器的描述表,其中struct sys_reg_desc
結構體中包含了對該寄存器操做的函數指針,用於指向最終的操做函數,好比針對Timer的kvm_arm_timer_write_sysreg/kvm_arm_timer_read_sysreg
讀寫操做函數;kvm_arm_timer_read/kvm_arm_timer_write
中完成,實現的功能就是根據物理的count值和offset來計算等;
timer的虛擬化仍是比較簡單,就此打住了。
按計劃,接下里該寫IO虛擬化了,而後緊接着Qemu的源碼相關分析。不過,在寫IO虛擬化以前,我會先去講一下PCIe的驅動框架,甚至可能還會去研究一下網絡,who knows,反正這些也都是IO相關。
Any way,I will be back soon!
《AArch64 Programmer's Guides Generic Timer》
《Arm Architecture Reference Manual》
歡迎關注我的公衆號,不按期更新內核相關技術文章