【原創】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的做用吧:數據結構

經過timer的中斷,OS實現的功能包括但不侷限於上圖:架構

  • 定時器的維護,包括用戶態和內核態,當指定時間段過去後觸發事件操做,好比IO操做註冊的超時定時器等;
  • 更新系統的運行時間、wall time等,此外還保存當前的時間和日期,以便能經過time()等接口返回給用戶程序,內核中也能夠利用其做爲文件和網絡包的時間戳;
  • 調度器在調度任務分配給CPU時,也會去對task的運行時間進行統計計算,好比CFS調度,Round-Robin調度等;
  • 資源使用統計,好比系統負載的記錄等,此外用戶使用top命令也能進行查看;

 
timer就像是系統的脈搏,重要性不言而喻。ARMv8架構處理器提供了一個Generic Timer,與GIC相似,Generic Timer在硬件上也支持了虛擬化,減小了軟件模擬帶來的overhead。框架

 
本文將圍繞着ARMv8的timer虛擬化來展開。ide

2. ARMv8 Timer虛擬化

2.1 Generic Timer

看一下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之間;
  • 每一個核有一組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 TimerPhysical 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須要的內容,包括了幾個字段用於存儲寄存器的值,另外還描述了中斷相關的信息;

初始化分爲兩部分:

  1. 架構相關的初始化,針對全部的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兩個函數中實現的操做就是中斷的打開和關閉,僅此而已;
  1. 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_expirebg_timer的到期執行函數,當須要調用kvm_vcpu_block讓vcpu睡眠時,須要先啓動bg_timerbg_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_regkvm_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》

歡迎關注我的公衆號,不按期更新內核相關技術文章

相關文章
相關標籤/搜索