1. 程式人生 > >【原創】Linux虛擬化KVM-Qemu分析(七)之timer虛擬化

【原創】Linux虛擬化KVM-Qemu分析(七)之timer虛擬化

# 背景 - `Read the fucking source code!` --By 魯迅 - `A picture is worth a thousand words.` --By 高爾基 說明: 1. KVM版本:5.9.1 2. QEMU版本:5.0.0 3. 工具:Source Insight 3.5, Visio 4. 文章同步在部落格園:`https://www.cnblogs.com/LoyenWang/` # 1. 概述 先從作業系統的角度來看一下timer的作用吧: ![](https://img2020.cnblogs.com/blog/1771657/202012/1771657-20201205235145167-121582686.png) 通過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內部圖: ![](https://img2020.cnblogs.com/blog/1771657/202012/1771657-20201205235155674-1601941220.png) - `Generic Timer`提供了一個系統計數器,用於測量真實時間的消逝; - `Generic Timer`支援虛擬計數器,用於測量虛擬的時間消逝,一個虛擬計數器對應一個虛擬機器; - `Timer`可以在特定的時間消逝後觸發事件,可以設定成`count-up`計數或者`count-down`計數; 來看一下`Generic Timer`的簡圖: ![](https://img2020.cnblogs.com/blog/1771657/202012/1771657-20201205235203044-1599197060.png) 或者這個: ![](https://img2020.cnblogs.com/blog/1771657/202012/1771657-20201205235208470-1804165099.png) - `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`的虛擬化如下圖: ![](https://img2020.cnblogs.com/blog/1771657/202012/1771657-20201205235216413-90333420.png) - 虛擬的timer,同樣也有一個count值,計算關係:`Virtual Count = Physical Count - `,其中offset的值放置在`CNTVOFF`暫存器中,`CNTPCT/CNTVCT`分別用於記錄當前物理/虛擬的count值; - 如果EL2沒有實現,則將offset設定為0,,物理的計數器和虛擬的計數器值相等; - `Physical Timer`直接與`System counter`進行比較,`Virtual Timer`在`Physical Timer`的基礎上再減去一個偏移; - Hypervisor負責為當前排程執行的vCPU指定對應的偏移,這種方式使得虛擬時間只會覆蓋vCPU實際執行的那部分時間; 示例如下: ![](https://img2020.cnblogs.com/blog/1771657/202012/1771657-20201205235232851-53486309.png) - 6ms的時間段裡,每個vCPU執行3ms,Hypervisor可以使用偏移暫存器來將vCPU的時間調整為其實際的執行時間; # 3. 流程分析 ## 3.1 初始化 先簡單看一下資料結構吧: ![](https://img2020.cnblogs.com/blog/1771657/202012/1771657-20201205235240939-617714506.png) - 在ARMv8虛擬化中,使用`struct arch_timer_cpu`來描述`Generic Timer`,從結構體中也能很清晰的看到層次結構,建立vcpu時,需要去初始化vcpu架構相關的欄位,其中就包含了timer; - `struct arch_timer_cpu`包含了兩個timer,分別對應物理timer和虛擬timer,此外還有一個高精度定時器,用於Guest處在非執行時的計時工作; - `struct arch_timer_context`用於描述一個timer需要的內容,包括了幾個欄位用於儲存暫存器的值,另外還描述了中斷相關的資訊; 初始化分為兩部分: 1. 架構相關的初始化,針對所有的CPU,在kvm初始化時設定: ![](https://img2020.cnblogs.com/blog/1771657/202012/1771657-20201205235248193-159181159.png) - `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`兩個函式中實現的操作就是中斷的開啟和關閉,僅此而已; 2. vcpu相關的初始化,在建立vcpu時進行初始化設定: ![](https://img2020.cnblogs.com/blog/1771657/202012/1771657-20201205235255363-2105252369.png) - 針對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中,流程如下: ![](https://img2020.cnblogs.com/blog/1771657/202012/1771657-20201205235302190-2105625971.png) - 使用者態建立完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進行處理,流程如下: ![](https://img2020.cnblogs.com/blog/1771657/202012/1771657-20201205235311000-378032277.png) - 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》` 歡迎關注個人公眾號,不定期更新核心相關技術文章 ![](https://img2020.cnblogs.com/blog/1771657/202012/1771657-20201205235402372-3587635