天天看點

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

<code>Read the fucking source code!</code> --By 魯迅

<code>A picture is worth a thousand words.</code> --By 高爾基

說明:

KVM版本:5.9.1

QEMU版本:5.0.0

工具:Source Insight 3.5, Visio

文章同步在部落格園:<code>https://www.cnblogs.com/LoyenWang/</code>

先從作業系統的角度來看一下timer的作用吧:

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

通過timer的中斷,OS實作的功能包括但不局限于上圖:

定時器的維護,包括使用者态和核心态,當指定時間段過去後觸發事件操作,比如IO操作注冊的逾時定時器等;

更新系統的運作時間、wall time等,此外還儲存目前的時間和日期,以便能通過<code>time()</code>等接口傳回給使用者程式,核心中也可以利用其作為檔案和網絡包的時間戳;

排程器在排程任務配置設定給CPU時,也會去對task的運作時間進行統計計算,比如CFS排程,Round-Robin排程等;

資源使用統計,比如系統負載的記錄等,此外使用者使用top指令也能進行檢視;

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

本文将圍繞着ARMv8的timer虛拟化來展開。

看一下ARMv8架構下的CPU内部圖:

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

<code>Generic Timer</code>提供了一個系統計數器,用于測量真實時間的消逝;

<code>Generic Timer</code>支援虛拟計數器,用于測量虛拟的時間消逝,一個虛拟計數器對應一個虛拟機;

<code>Timer</code>可以在特定的時間消逝後觸發事件,可以設定成<code>count-up</code>計數或者<code>count-down</code>計數;

來看一下<code>Generic Timer</code>的簡圖:

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

或者這個:

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

<code>System Counter</code>位于<code>Always-on</code>電源域,以固定頻率進行系統計數的增加,<code>System Counter</code>的值會廣播給系統中的所有核,所有核也能有一個共同的基準了,<code>System Counter</code>的頻率範圍為1-50MHZ,系統計數值的位寬在56-64bit之間;

每個核有一組timer,這些timer都是一些比較器,與<code>System Counter</code>廣播過來的系統計數值進行比較,軟體可以配置固定時間消逝後觸發中斷或者觸發事件;

每個核提供的timer包括:1)<code>EL1 Physical timer</code>;2)<code>EL1 Virtual timer</code>;此外還有在EL2和EL3下提供的timer,具體取決于ARMv8的版本;

有兩種方式可以配置和使用一個timer:1)<code>CVAL(comparatoer)</code>寄存器,通過設定比較器的值,當<code>System Count &gt;= CVAL</code>時滿足觸發條件;2)<code>TVAL</code>寄存器,設定<code>TVAL</code>寄存器值後,比較器的值<code>CVAL = TVAL + System Counter</code>,當<code>System Count &gt;= CVAL</code>時滿足觸發條件,<code>TVAL</code>是一個有符号數,當遞減到0時還會繼續遞減,是以可以記錄timer是在多久之前觸發的;

timer的中斷是私有中斷<code>PPI</code>,其中<code>EL1 Physical Timer</code>的中斷号為30,<code>EL1 Virtual Timer</code>的中斷号為27;

timer可以配置成觸發事件産生,當CPU通過<code>WFE</code>進入低功耗狀态時,除了使用<code>SEV</code>指令喚醒外,還可以通過<code>Generic Timer</code>産生的事件流來喚醒;

<code>Generic Timer</code>的虛拟化如下圖:

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

虛拟的timer,同樣也有一個count值,計算關系:<code>Virtual Count = Physical Count - &lt;offset&gt;</code>,其中offset的值放置在<code>CNTVOFF</code>寄存器中,<code>CNTPCT/CNTVCT</code>分别用于記錄目前實體/虛拟的count值;

如果EL2沒有實作,則将offset設定為0,,實體的計數器和虛拟的計數器值相等;

<code>Physical Timer</code>直接與<code>System counter</code>進行比較,<code>Virtual Timer</code>在<code>Physical Timer</code>的基礎上再減去一個偏移;

Hypervisor負責為目前排程運作的vCPU指定對應的偏移,這種方式使得虛拟時間隻會覆寫vCPU實際運作的那部分時間;

示例如下:

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

6ms的時間段裡,每個vCPU運作3ms,Hypervisor可以使用偏移寄存器來将vCPU的時間調整為其實際的運作時間;

先簡單看一下資料結構吧:

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

在ARMv8虛拟化中,使用<code>struct arch_timer_cpu</code>來描述<code>Generic Timer</code>,從結構體中也能很清晰的看到層次結構,建立vcpu時,需要去初始化vcpu架構相關的字段,其中就包含了timer;

<code>struct arch_timer_cpu</code>包含了兩個timer,分别對應實體timer和虛拟timer,此外還有一個高精度定時器,用于Guest處在非運作時的計時工作;

<code>struct arch_timer_context</code>用于描述一個timer需要的内容,包括了幾個字段用于存儲寄存器的值,另外還描述了中斷相關的資訊;

初始化分為兩部分:

架構相關的初始化,針對所有的CPU,在kvm初始化時設定:

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

<code>kvm_timer_hyp_init</code>函數完成相應的初始化工作;

<code>arch_timer_get_kvm_info</code>從Host Timer驅動中去擷取資訊,主要包括了虛拟中斷号和實體中斷号,以及timecounter資訊等;

vtimer中斷設定包括:判斷中斷的觸發方式(隻支援電平觸發),注冊中斷處理函數<code>kvm_arch_timer_handler</code>,設定中斷到vcpu的affinity等;

ptimer中斷設定與vtimer中斷設定一樣,同時它的中斷處理函數也是<code>kvm_arch_timer_handler</code>,該處理函數也比較簡單,最終會調用<code>kvm_vgic_inject_irq</code>函數來完成虛拟中斷注入給vcpu;

<code>cpuhp_setup_state</code>用來設定CPU熱插拔時timer的響應處理,而在<code>kvm_timer_starting_cpu/kvm_timer_dying_cpu</code>兩個函數中實作的操作就是中斷的打開和關閉,僅此而已;

vcpu相關的初始化,在建立vcpu時進行初始化設定:

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

針對vcpu的timer相關初始化比較簡單,回到上邊那張資料結構圖看一眼就明白了,所有的初始化工作都圍繞着<code>struct arch_timer_cpu</code>結構體;

<code>vcpu_timer</code>:用于擷取vcpu包含的<code>struct arch_timer_cpu</code>結構;

<code>vcpu_vtimer/vcpu_ptimer</code>:用于擷取<code>struct arch_timer_cpu</code>結構體中的<code>struct arch_timer_context</code>,分别對應vtimer和ptimer;

<code>update_vtimer_cntvoff</code>:用于更新vtimer中的cntvoff值,讀取實體timer的count值,更新VM中所有vcpu的cntvoff值;

<code>hrtimer_init</code>:用于初始化高精度定時器,包含有三個,<code>struct arch_timer_cpu</code>結構中有一個<code>bg_timer</code>,vtimer和ptimer所對應的<code>struct arch_timer_context</code>中分别對應一個;

<code>kvm_bg_timer_expire</code>:<code>bg_timer</code>的到期執行函數,當需要調用<code>kvm_vcpu_block</code>讓vcpu睡眠時,需要先啟動<code>bg_timer</code>,<code>bg_timer</code>到期時再将vcpu喚醒;

<code>kvm_hrtimer_expire</code>:vtimer和ptimer的到期執行函數,最終通過調用<code>kvm_timer_update_irq</code>來向vcpu注入中斷;

可以從使用者态對vtimer進行讀寫操作,比如Qemu中,流程如下:

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

使用者态建立完vcpu後,可以通過vcpu的檔案描述符來進行寄存器的讀寫操作;

以ARM為例,ioctl通過<code>KVM_SET_ONE_REG/KVM_GET_ONE_REG</code>将最終觸發寄存器的讀寫;

如果操作的是timer的相關寄存器,則通過<code>kvm_arm_timer_set_reg</code>和<code>kvm_arm_timer_get_reg</code>來完成;

讀寫的寄存器包括虛拟timer的CTL/CVAL,以及實體timer的CTL/CVAL等;

Guest對Timer的通路,涉及到系統寄存器的讀寫,将觸發異常并Trap到Hyp進行處理,流程如下:

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

Guest OS通路系統寄存器時,Trap到Hypervisor進行處理;

Hypervisor對異常退出進行處理,如果發現是通路系統寄存器造成的異常,則調用<code>kvm_handle_sys_reg</code>來處理;

<code>kvm_handle_sys_reg</code>:調用<code>emulate_sys_reg</code>來對系統寄存器進行模拟,在該函數中首先會查找通路的是哪一個寄存器,然後再去調用相應的回調函數;

kvm中維護了<code>struct sys_reg_desc sys_reg_descs[]</code>系統寄存器的描述表,其中<code>struct sys_reg_desc</code>結構體中包含了對該寄存器操作的函數指針,用于指向最終的操作函數,比如針對Timer的<code>kvm_arm_timer_write_sysreg/kvm_arm_timer_read_sysreg</code>讀寫操作函數;

Timer的讀寫操作函數,主要在<code>kvm_arm_timer_read/kvm_arm_timer_write</code>中完成,實作的功能就是根據實體的count值和offset來計算等;

timer的虛拟化還是比較簡單,就此打住了。

按計劃,接下裡該寫IO虛拟化了,然後緊接着Qemu的源碼相關分析。不過,在寫IO虛拟化之前,我會先去講一下PCIe的驅動架構,甚至可能還會去研究一下網絡,who knows,反正這些也都是IO相關。

<code>Any way,I will be back soon!</code>

<code>《AArch64 Programmer's Guides Generic Timer》</code>

<code>《Arm Architecture Reference Manual》</code>

歡迎關注個人公衆号,不定期更新核心相關技術文章

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

作者:LoyenWang

出處:https://www.cnblogs.com/LoyenWang/

公衆号:<b>LoyenWang</b>

版權:本文版權歸作者和部落格園共有

轉載:歡迎轉載,但未經作者同意,必須保留此段聲明;必須在文章中給出原文連接配接;否則必究法律責任