【原创】Linux虚拟化KVM-Qemu分析(七)之timer虚拟化
背景
Read the fucking source code!
--By 鲁迅A picture is worth a thousand words.
--By 高尔基
说明:
- KVM版本:5.9.1
- QEMU版本:5.0.0
- 工具:Source Insight 3.5, Visio
- 文章同步在博客园:
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虚拟化来展开。
2. ARMv8 Timer虚拟化
2.1 Generic Timer
看一下ARMv8架构下的CPU内部图:
Generic Timer
提供了一个系统计数器,用于测量真实时间的消逝;Generic Timer
支持虚拟计数器,用于测量虚拟的时间消逝,一个虚拟计数器对应一个虚拟机;Timer
可以在特定的时间消逝后触发事件,可以设置成count-up
计数或者count-down
计数;
来看一下Generic Timer
的简图:
或者这个:
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 Timer
在Physical 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需要的内容,包括了几个字段用于存储寄存器的值,另外还描述了中断相关的信息;
初始化分为两部分:
- 架构相关的初始化,针对所有的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
两个函数中实现的操作就是中断的打开和关闭,仅此而已;
- 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_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中,流程如下:
- 用户态创建完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进行处理,流程如下:
- 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》
欢迎关注个人公众号,不定期更新内核相关技术文章
【原创】Linux虚拟化KVM-Qemu分析(七)之timer虚拟化的更多相关文章
- KVM 介绍(3):I/O 全虚拟化和准虚拟化 [KVM I/O QEMU Full-Virtualizaiton Para-virtualization]
学习 KVM 的系列文章: (1)介绍和安装 (2)CPU 和 内存虚拟化 (3)I/O QEMU 全虚拟化和准虚拟化(Para-virtulizaiton) (4)I/O PCI/PCIe设备直接分 ...
- KVM+QEMU虚拟化概念
概念: KVM,即Kernel-basedvirtual machine,由redhat开发,是一种开源.免费的虚拟化技术.对企业来说,是一种可选的虚拟化解决方案. 定义:基于Linux内核的虚拟机 ...
- Linux实战教学笔记53:开源虚拟化KVM(一)搭建部署与概述
一,KVM概述 1.1 虚拟化概述 在计算机技术中,虚拟化意味着创建设备或资源的虚拟版本,如服务器,存储设备,网络或者操作系统等等 [x] 虚拟化技术分类: 系统虚拟化(我们主要讨论的反向) 存储虚拟 ...
- 深度分析Linux下双网卡绑定七种模式 多网卡的7种bond模式原理
http://blog.csdn.net/abc_ii/article/details/9991845多网卡的7种bond模式原理 Linux网卡绑定mode共有七种(~) bond0.bond1.b ...
- 2018-2019-1 20189221 《Linux内核原理与分析》第七周作业
2018-2019-1 20189221 <Linux内核原理与分析>第七周作业 实验六 分析Linux内核创建一个新进程的过程 代码分析 task_struct: struct task ...
- 2017-2018-1 20179209《Linux内核原理与分析》第七周作业
一.实验 1.1task_struct数据结构 Linux内核通过一个被称为进程描述符的task_struct结构体来管理进程,这个结构体包含了一个进程所需的所有信息.它定义在linux-3.18.6 ...
- 2019-2020-1 20199329《Linux内核原理与分析》第七周作业
<Linux内核原理与分析>第七周作业 一.本周内容概述: 对Linux系统如何创建一个新进程进行追踪 分析Linux内核创建一个新进程的过程 二.本周学习内容: 1.学习进程的描述 操作 ...
- 【原创】Linux PCI驱动框架分析(三)
背 景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本 ...
- 【原创】Linux PCI驱动框架分析(二)
背 景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本 ...
随机推荐
- 漫谈:从APP崩溃率标准,到Monkey介绍拓展Maxim,及Jenkins自动化配置,持续集成获取崩溃monkey日志
漫谈:从APP崩溃率标准,到Monkey介绍拓展Maxim,及Jenkins自动化配置,持续集成获取崩溃日志 1.APP崩溃率标准 开发或测试app的同学,对于app崩溃肯定非常熟悉,频繁的线上崩溃属 ...
- Java 解决Enum.valueOf找不到枚举出现的异常
由于Enum.valueOf匹配不到枚举时会出现异常,这个可以用try...catch来解决,但是这样会导致代码往臃肿的道路上越走越远. 本文与其说是解决Enum.valueOf找不到枚举出现的异常还 ...
- HashMap的put kv,是如何扩容的?
HashMap的put kv,是如何扩容的? 描述下HashMap put(k,v)的流程? 它的扩容流程是怎么样的? HashMap put(k,v)流程 通过hash(key方法)获取到key的h ...
- Python学习笔记2:基本数据类型
Python中的变量不需要声明.每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建. 在 Python 中,变量就是变量,它没有类型,我们所说的"类型"是变量所指的内存中对象 ...
- js数组方法(管饱)
有一些数组方法是ECMAScript新增的,一定要注意浏览器的兼容!! Array对象属性: 属性 说明 constructor 返回对创建此对象的函数引用 length 返回集合内的元素的所有长度 ...
- ECMAScript6标准-简介
Introduction This Ecma Standard defines the ECMAScript 2015 Language. It is the sixth edition of the ...
- Docker - 解决在容器内删除和主机映射的目录而报错 rm: cannot remove 'webapps': Device or resource busy 的问题
问题背景 docker run -d --name tomcat7 -v /usr/local/tomcat/webapps:/usr/local/tomcat/webapps tomcat:7 使用 ...
- explain命令---查看mysql执行计划
引言: 实际项目开发中,由于我们不知道实际查询的时候数据库里发生了什么事情,数据库软件是怎样扫描表.怎样使用索引的,因此,我们能感知到的就只有 sql语句运行的时间,在数据规模不大时,查询是瞬间的,因 ...
- CTDB使用radosobject作为lockfile
前言 服务器的服务做HA有很多种方式,其中有一种就是是用CTDB,之前这个是独立的软件来做HA的,现在已经跟着SAMBA主线里面了,也就是跟着samba发行包一起发行 之前CTDB的模式是需要有一个共 ...
- SecureCRT下载和安装
1.下载地址在我的百度网盘中 链接:https://pan.baidu.com/s/1tscAAS7QnWEQMNtnvGGI_A 提取码:exp9 2.解压后,运行 选中SecureCRT运行程序 ...