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》
欢迎关注我的公众号,不按期更新内核相关技术文章