一篇分析Linux虛擬化KVM-Qemu分析之timer虛擬化
說明:
KVM版本:5.9.1
QEMU版本:5.0.0
工具:Source Insight 3.5, Visio
1. 概述
先從操作系統(tǒng)的角度來看一下timer的作用吧:

通過timer的中斷,OS實(shí)現(xiàn)的功能包括但不局限于上圖:
定時(shí)器的維護(hù),包括用戶態(tài)和內(nèi)核態(tài),當(dāng)指定時(shí)間段過去后觸發(fā)事件操作,比如IO操作注冊(cè)的超時(shí)定時(shí)器等;
更新系統(tǒng)的運(yùn)行時(shí)間、wall time等,此外還保存當(dāng)前的時(shí)間和日期,以便能通過
time()
等接口返回給用戶程序,內(nèi)核中也可以利用其作為文件和網(wǎng)絡(luò)包的時(shí)間戳;調(diào)度器在調(diào)度任務(wù)分配給CPU時(shí),也會(huì)去對(duì)task的運(yùn)行時(shí)間進(jìn)行統(tǒng)計(jì)計(jì)算,比如CFS調(diào)度,Round-Robin調(diào)度等;
資源使用統(tǒng)計(jì),比如系統(tǒng)負(fù)載的記錄等,此外用戶使用top命令也能進(jìn)行查看;
? timer就像是系統(tǒng)的脈搏,重要性不言而喻。ARMv8架構(gòu)處理器提供了一個(gè)Generic Timer,與GIC類似,Generic Timer在硬件上也支持了虛擬化,減少了軟件模擬帶來的overhead。
? 本文將圍繞著ARMv8的timer虛擬化來展開。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個(gè)人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!?。。ê曨l教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)?


2. ARMv8 Timer虛擬化
2.1 Generic Timer
看一下ARMv8架構(gòu)下的CPU內(nèi)部圖:

Generic Timer
提供了一個(gè)系統(tǒng)計(jì)數(shù)器,用于測(cè)量真實(shí)時(shí)間的消逝;Generic Timer
支持虛擬計(jì)數(shù)器,用于測(cè)量虛擬的時(shí)間消逝,一個(gè)虛擬計(jì)數(shù)器對(duì)應(yīng)一個(gè)虛擬機(jī);Timer
可以在特定的時(shí)間消逝后觸發(fā)事件,可以設(shè)置成count-up
計(jì)數(shù)或者count-down
計(jì)數(shù);
來看一下Generic Timer
的簡(jiǎn)圖:

或者這個(gè):

System Counter
位于Always-on
電源域,以固定頻率進(jìn)行系統(tǒng)計(jì)數(shù)的增加,System Counter
的值會(huì)廣播給系統(tǒng)中的所有核,所有核也能有一個(gè)共同的基準(zhǔn)了,System Counter
的頻率范圍為1-50MHZ,系統(tǒng)計(jì)數(shù)值的位寬在56-64bit之間;每個(gè)核有一組timer,這些timer都是一些比較器,與
System Counter
廣播過來的系統(tǒng)計(jì)數(shù)值進(jìn)行比較,軟件可以配置固定時(shí)間消逝后觸發(fā)中斷或者觸發(fā)事件;每個(gè)核提供的timer包括:1)
EL1 Physical timer
;2)EL1 Virtual timer
;此外還有在EL2和EL3下提供的timer,具體取決于ARMv8的版本;有兩種方式可以配置和使用一個(gè)timer:1)
CVAL(comparatoer)
寄存器,通過設(shè)置比較器的值,當(dāng)System Count >= CVAL
時(shí)滿足觸發(fā)條件;2)TVAL
寄存器,設(shè)置TVAL
寄存器值后,比較器的值CVAL = TVAL + System Counter
,當(dāng)System Count >= CVAL
時(shí)滿足觸發(fā)條件,TVAL
是一個(gè)有符號(hào)數(shù),當(dāng)遞減到0時(shí)還會(huì)繼續(xù)遞減,因此可以記錄timer是在多久之前觸發(fā)的;timer的中斷是私有中斷
PPI
,其中EL1 Physical Timer
的中斷號(hào)為30,EL1 Virtual Timer
的中斷號(hào)為27;timer可以配置成觸發(fā)事件產(chǎn)生,當(dāng)CPU通過
WFE
進(jìn)入低功耗狀態(tài)時(shí),除了使用SEV
指令喚醒外,還可以通過Generic Timer
產(chǎn)生的事件流來喚醒;
2.2 虛擬化支持
Generic Timer
的虛擬化如下圖:

虛擬的timer,同樣也有一個(gè)count值,計(jì)算關(guān)系:
Virtual Count = Physical Count - <offset>
,其中offset的值放置在CNTVOFF
寄存器中,CNTPCT/CNTVCT
分別用于記錄當(dāng)前物理/虛擬的count值;如果EL2沒有實(shí)現(xiàn),則將offset設(shè)置為0,,物理的計(jì)數(shù)器和虛擬的計(jì)數(shù)器值相等;
Physical Timer
直接與System counter
進(jìn)行比較,Virtual Timer
在Physical Timer
的基礎(chǔ)上再減去一個(gè)偏移;Hypervisor負(fù)責(zé)為當(dāng)前調(diào)度運(yùn)行的vCPU指定對(duì)應(yīng)的偏移,這種方式使得虛擬時(shí)間只會(huì)覆蓋vCPU實(shí)際運(yùn)行的那部分時(shí)間;
示例如下:

6ms的時(shí)間段里,每個(gè)vCPU運(yùn)行3ms,Hypervisor可以使用偏移寄存器來將vCPU的時(shí)間調(diào)整為其實(shí)際的運(yùn)行時(shí)間;
3. 流程分析
3.1 初始化
先簡(jiǎn)單看一下數(shù)據(jù)結(jié)構(gòu)吧:

在ARMv8虛擬化中,使用
struct arch_timer_cpu
來描述Generic Timer
,從結(jié)構(gòu)體中也能很清晰的看到層次結(jié)構(gòu),創(chuàng)建vcpu時(shí),需要去初始化vcpu架構(gòu)相關(guān)的字段,其中就包含了timer;struct arch_timer_cpu
包含了兩個(gè)timer,分別對(duì)應(yīng)物理timer和虛擬timer,此外還有一個(gè)高精度定時(shí)器,用于Guest處在非運(yùn)行時(shí)的計(jì)時(shí)工作;struct arch_timer_context
用于描述一個(gè)timer需要的內(nèi)容,包括了幾個(gè)字段用于存儲(chǔ)寄存器的值,另外還描述了中斷相關(guān)的信息;
初始化分為兩部分:
架構(gòu)相關(guān)的初始化,針對(duì)所有的CPU,在kvm初始化時(shí)設(shè)置:

kvm_timer_hyp_init
函數(shù)完成相應(yīng)的初始化工作;arch_timer_get_kvm_info
從Host Timer驅(qū)動(dòng)中去獲取信息,主要包括了虛擬中斷號(hào)和物理中斷號(hào),以及timecounter信息等;vtimer中斷設(shè)置包括:判斷中斷的觸發(fā)方式(只支持電平觸發(fā)),注冊(cè)中斷處理函數(shù)
kvm_arch_timer_handler
,設(shè)置中斷到vcpu的affinity等;ptimer中斷設(shè)置與vtimer中斷設(shè)置一樣,同時(shí)它的中斷處理函數(shù)也是
kvm_arch_timer_handler
,該處理函數(shù)也比較簡(jiǎn)單,最終會(huì)調(diào)用kvm_vgic_inject_irq
函數(shù)來完成虛擬中斷注入給vcpu;cpuhp_setup_state
用來設(shè)置CPU熱插拔時(shí)timer的響應(yīng)處理,而在kvm_timer_starting_cpu/kvm_timer_dying_cpu
兩個(gè)函數(shù)中實(shí)現(xiàn)的操作就是中斷的打開和關(guān)閉,僅此而已;
vcpu相關(guān)的初始化,在創(chuàng)建vcpu時(shí)進(jìn)行初始化設(shè)置:

針對(duì)vcpu的timer相關(guān)初始化比較簡(jiǎn)單,回到上邊那張數(shù)據(jù)結(jié)構(gòu)圖看一眼就明白了,所有的初始化工作都圍繞著
struct arch_timer_cpu
結(jié)構(gòu)體;vcpu_timer
:用于獲取vcpu包含的struct arch_timer_cpu
結(jié)構(gòu);vcpu_vtimer/vcpu_ptimer
:用于獲取struct arch_timer_cpu
結(jié)構(gòu)體中的struct arch_timer_context
,分別對(duì)應(yīng)vtimer和ptimer;update_vtimer_cntvoff
:用于更新vtimer中的cntvoff值,讀取物理timer的count值,更新VM中所有vcpu的cntvoff值;hrtimer_init
:用于初始化高精度定時(shí)器,包含有三個(gè),struct arch_timer_cpu
結(jié)構(gòu)中有一個(gè)bg_timer
,vtimer和ptimer所對(duì)應(yīng)的struct arch_timer_context
中分別對(duì)應(yīng)一個(gè);kvm_bg_timer_expire
:bg_timer
的到期執(zhí)行函數(shù),當(dāng)需要調(diào)用kvm_vcpu_block
讓vcpu睡眠時(shí),需要先啟動(dòng)bg_timer
,bg_timer
到期時(shí)再將vcpu喚醒;kvm_hrtimer_expire
:vtimer和ptimer的到期執(zhí)行函數(shù),最終通過調(diào)用kvm_timer_update_irq
來向vcpu注入中斷;
3.2 用戶層訪問
可以從用戶態(tài)對(duì)vtimer進(jìn)行讀寫操作,比如Qemu中,流程如下:

用戶態(tài)創(chuàng)建完vcpu后,可以通過vcpu的文件描述符來進(jìn)行寄存器的讀寫操作;
以ARM為例,ioctl通過
KVM_SET_ONE_REG/KVM_GET_ONE_REG
將最終觸發(fā)寄存器的讀寫;如果操作的是timer的相關(guān)寄存器,則通過
kvm_arm_timer_set_reg
和kvm_arm_timer_get_reg
來完成;讀寫的寄存器包括虛擬timer的CTL/CVAL,以及物理timer的CTL/CVAL等;
3.3 Guest訪問
Guest對(duì)Timer的訪問,涉及到系統(tǒng)寄存器的讀寫,將觸發(fā)異常并Trap到Hyp進(jìn)行處理,流程如下:

Guest OS訪問系統(tǒng)寄存器時(shí),Trap到Hypervisor進(jìn)行處理;
Hypervisor對(duì)異常退出進(jìn)行處理,如果發(fā)現(xiàn)是訪問系統(tǒng)寄存器造成的異常,則調(diào)用
kvm_handle_sys_reg
來處理;kvm_handle_sys_reg
:調(diào)用emulate_sys_reg
來對(duì)系統(tǒng)寄存器進(jìn)行模擬,在該函數(shù)中首先會(huì)查找訪問的是哪一個(gè)寄存器,然后再去調(diào)用相應(yīng)的回調(diào)函數(shù);kvm中維護(hù)了
struct sys_reg_desc sys_reg_descs[]
系統(tǒng)寄存器的描述表,其中struct sys_reg_desc
結(jié)構(gòu)體中包含了對(duì)該寄存器操作的函數(shù)指針,用于指向最終的操作函數(shù),比如針對(duì)Timer的kvm_arm_timer_write_sysreg/kvm_arm_timer_read_sysreg
讀寫操作函數(shù);Timer的讀寫操作函數(shù),主要在
kvm_arm_timer_read/kvm_arm_timer_write
中完成,實(shí)現(xiàn)的功能就是根據(jù)物理的count值和offset來計(jì)算等;
原文作者:LoyenWang
