【转载】Linux虚拟化KVM-Qemu分析(六)之中断虚拟化
作者:LoyenWang
出处:https://www.cnblogs.com/LoyenWang/
公众号:LoyenWang
版权:本文版权归作者和博客园共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
背景
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. 概述
本文会将ARM GICv2中断虚拟化的总体框架和流程讲清楚,这个曾经困扰我好几天的问题在被捋清的那一刻,让我有点每有会意,欣然忘食的感觉。
在讲述中断虚拟化之前,我们应该对中断的作用与处理流程有个大致的了解:

- 中断是处理器用于异步处理外围设备请求的一种机制;
- 外设通过硬件管脚连接在中断控制器上,并通过电信号向中断控制器发送请求;
- 中断控制器将外设的中断请求路由到CPU上;
- CPU(以ARM为例)进行模式切换(切换到IRQ/FIQ),保存Context后,根据外设的中断号去查找系统中已经注册好的Handler进行处理,处理完成后再将Context进行恢复,接着之前打断的执行流继续move on;
- 中断的作用不局限于外设的处理,系统的调度,SMP核间交互等,都离不开中断;
中断虚拟化,将从中断信号产生到路由到vCPU的角度来展开,包含以下三种情况:
- 物理设备产生中断信号,路由到vCPU;
- 虚拟外设产生中断信号,路由到vCPU;
- Guest OS中CPU之间产生中断信号(IPI中断);
本文将围绕ARM-GICv2来描述,因此也不会涉及到MSI以及ITS等特性,带着问题出发吧。
2. VGIC
- 在讲中断虚拟化之前,有必要先讲一下ARMv8中Hypervisor的架构,因为涉及到不同的Exception Level的切换;
- 在我阅读源代码时,根据代码去匹配某篇Paper中的理论时,出现了一些理解偏差,曾一度困扰了我好几天;

Non-VHE- Linux ARM架构的Hypervisor在引入时,采用的是左图中的系统架构,以便能充分利用Linux现有的机制,比如scheduler等;
- KVM/ARM的实现采用了
split模式,分成Highvisor和Lowvisor,这样可以充分利用ARM处理器不同模式的好处,比如,Highvisor可以利用Linux Kernel的现有机制,而Lowvisor又可以利用Hyp Mode的特权。此外,带来的好处还包含了不需要大量修改Linux内核的代码,这个在刚引入的时候是更容易被社区所接受的; Lowvisor有三个关键功能:1)对不同的执行Context进行隔离与保护,比如VM之间不会相互影响;2)提供Guest和Host的相互切换,也就是所谓的world switch;3)提供一个虚拟化trap handler,用于处理trap到Hypervisor的中断和异常;
VHEVHE: Virtualization Host Extensions,用于支持Host OS运行在EL2上,Hypervisor和Host OS都运行在EL2,可以减少Context切换带来的开销;- 目前
Cortex-A55, Cortex-A75, Cortex-A76支持VHE,其中VHE的控制是通过HCR_EL2寄存器的操作来实现的;
再补充一个知识点:
- Host如果运行在EL1时,可以通过
HVC(Hypervisor Call)指令,主动trap到EL2中,从而由Hypervisor来接管; - Guest OS可以配置成当有中断或异常时trap到EL2中,在中断或异常产生时,trap到EL2中,从而由Hypervisor来接管;
- EL2可以通过
eret指令,退回到EL1;
本文的讨论基于Non-VHE系统。
2.1 GIC虚拟化支持
GICv2硬件支持虚拟化,来一张旧图:

先看一下物理GIC的功能模块:
- GIC分成两部分:
Distributor和CPU Interfaces,Distributor和CPU Interfaces都是通过MMIO的方式来进行访问; Distributor用于配置GIC,比如中断的enable与disable,SMP中的IPI中断、CPU affinity,优先级处理等;CPU Interfaces用于连接CPU,进行中断的ACK(Acknowledge)以及EOI(End-Of-Interrupt)信号处理等,比如当CPU收到中断信号时,会通过CPU Interfaces进行ACK回应,并且在处理完中断后写入EOI寄存器,而在写EOI之前将不再收到该中断;
简化图如下:

GICv2,提供了硬件上的虚拟化支持,也就是虚拟GIC(VGIC),从而中断的接收不需要通过Hypervisor来软件模拟:
- 针对每个vCPU,VGIC引入了
VGIC CPU Interfaces和对应的Hypervisor控制接口; - 可以通过写Hypervisor控制接口中的LR(List Register)寄存器来产生虚拟中断,
VGIC CPU Interface会将虚拟中断信号送入到Guest中; VGIC CPU Interface支持ACK和EOI,因此这些操作也不需要trap到Hypervisor中来通过软件进行模拟,也减少了CPU接收中断的overhead;Distributor仍然需要trap到Hypervisor中来进行软件模拟,比如,当某个vCPU需要发送虚拟IPI到另一个vCPU时,此时是需要Distributor来辅助完成功能的,这个过程就需要trap到Hypervisor;
2.2 虚拟中断产生流程
本文开始提到的三种中断信号源,如下图所示:

- ①:物理外设产生虚拟中断流程:
- 外设中断信号(Hypervisor已经将其配置成虚拟中断)到达GIC;
- GIC Distributor将该物理IRQ发送至CPU;
- CPU trap到Hyp模式,此时将会退出Guest OS的运行,并返回到Host OS;
- Host OS将响应该物理中断,也就是Host OS驱动响应外设中断信号;
- Hypervisor往
List Register写入虚拟中断,Virtual CPU interface将virtual irq信号发送至vCPU; - CPU将处理该异常,Guest OS会从Virtual CPU Interface读取中断状态进行响应;
- ②:虚拟外设产生虚拟中断流程:
- Qemu模拟外设,通过
irqfd来触发Hypervisor进行中断注入; - Hypervisor往
List Register写入虚拟中断,Virtual CPU interface将virtual irq信号发送至vCPU; - CPU将处理该异常,Guest OS会从Virtual CPU Interface读取中断状态进行响应;
- Qemu模拟外设,通过
- ③:vCPU IPI中断流程:
- Guest OS访问Virtual Distributor,触发异常,trap到Hypervisor;
- Hypervisor进行IO异常响应,并最终将虚拟中断写入到List Register中,Virtual CPU interface将virtual irq信号发送至vCPU;
- CPU将处理该异常,Guest OS会从Virtual CPU Interface读取中断状态进行响应;
上述描述的流程,实际中需要和虚拟外设去交互,包括虚拟外设框架(比如VFIO)等,而本文只是从中断的角度来分析,省去了外设部分。
理论部分讲完了,下边就开始从源码中去印证理论了。
3. 软件实现流程
3.1 VGIC初始化

kvm_init为总入口,进入vgic_v2_probe函数,完成GICv2的初始化操作,此处还会跟GICV2内核中的驱动交互,去获取gic_kvm_info信息,主要包括基地址信息等,便于后续操作中去进行配置操作;- 从蓝色部分的函数调用可以看出,初始化完成后,会注册一个
kvm_device_ops的操作函数集,以便响应用户层的ioctl操作; - 用户层调用
ioctl(vm_fd, KVM_CREATE_DEVICE, 0),最终将调用vgic_create函数,完成VGIC设备的创建,在该创建函数中也会注册kvm_device_fops操作函数集,用于设备属性的设置和获取; - 用户层通过
ioctl(dev_fd, KVM_SET_DEVICE_ATTR, 0)/ioctl(dev_fd, KVM_GET_DEVICE_ATTR, 0)来进行属性的设置和获取,最终也会调用vgic_v2_set_attr/vgic_v2_get_attr,以便完成对VGIC的设置;
3.2 物理外设产生中断
假设你已经看过之前CPU的虚拟化文章了,按照惯例,先上图:

- 先来一个先决条件:
HCR_EL2.IMO设置为1,所有的IRQ都将trap到Hyp模式,因此,当Guest OS运行在vCPU上时,物理外设触发中断信号时,此时将切换到EL2,然后执行el1_irq; - 在Host中,当用户态通过
KVM_RUN控制vCPU运行时,在kvm_call_hyp_ret将触发Exception Level的切换,切换到Hyp模式并调用__kvm_vcpu_run_nvhe,在该函数中__guest_enter将切换到Guest OS的context,并最终通过eret返回到EL1,Guest OS正式开始运行; - 中断触发后
el1_irq将执行__guest_exit,这个过程将进行Context切换,也就是跳转到Host切入Guest的那个点,恢复Host的执行。注意了,这里边有个点很迷惑,el1_irq和__guest_exit的执行都是在EL2中,而Host在EL1执行,之前我一直没有找到eret来进行Exception Level的切换,最终发现原来是kvm_call_hyp_ret调用时,去异常向量表中找到对应的执行函数,实际会调用do_el2_call,在该函数中完成了Exception Level的切换,最终回到了EL1; - 切回到Host中时,当
local_irq_enable打开中断后,物理pending的中断就可以被Host欢快的响应了;
那虚拟中断是什么时候注入的呢?没错,图中的kvm_vgic_flush_hwstate会将虚拟中断注入,并且在__guest_enter切换回Guest OS时进行响应:

vgic_cpu结构体中的ap_list_head链表用于存放Active和Pending状态的中断,这也就是命名为ap_list_head的原因;kvm_vgic_flush_hwstate函数会遍历ap_list_head中的中断信息,并填入到vgic_lr数组中,最终会通过vgic_restore_state函数将数组中的内容更新到GIC的硬件中,也就完成了中断的注入了,当__guest_enter执行后,切换到Guest OS,便可以响应虚拟中断了;- 当从Guest OS退出后,此时需要调用
kvm_vgic_sync_hwstate,这个操作相当于kvm_vgic_flush_hwstate的逆操作,将硬件信息进行保存,并对短期内不会处理的中断进行剔除;
3.3 虚拟外设产生中断

- irqfd提供了一种机制用于注入虚拟中断,而这个中断源可以来自虚拟外设;
- irqfd是基于eventfd的机制来实现的,用于用户态与内核态,以及内核态之间的事件通知;
- 事件源可以是虚拟设备,比如VFIO框架等,这个模块还没有去深入了解过,不敢妄言,后续系列会跟进;
软件流程如下图:

- 初始化的操作包括两部分:1)设置Routing entry(【a】vgic_init初始化的时候创建默认的entry;【b】:用户层通过KVM_SET_GSI_ROUTING来设置);2)设置irqfd;
- 初始化设置完成后,系统可以随时响应事件触发了,当事件源触发时,将调度到
irqfd_inject函数; irqfd_inject函数完成虚拟中断的注入操作,在该函数中会去回调set函数,而set函数是在Routing entry初始化的时候设置好的;- 实际的注入操作在
vgic_irqfd_set_irq函数中完成; kvm_vcpu_kick函数,将Guest OS切回到Host OS,中断注入后再切回到Guest OS就可以响应了;
3.4 vCPU IPI

- Host对VGIC的
Distributor进行了模拟,当Guest尝试访问VGIC Distributor时,将触发异常操作,trap到Hyp模式; - Hypervisor对异常进行处理,完成写入操作,并最终切回到Guest OS进行响应;
- 简单来说,Hypervisor就是要对中断进行管理,没错,就是这么强势;
软件流程如下:

- 上层调用
ioctl(vcpu_fd, KVM_RUN, 0)时,最终将调用到vgic_register_dist_iodev函数,该函数完成的作用就是将VGIC的Distributor注册为IO设备,以便给Guest OS来进行访问; vgic_register_dist_iodev分为两个功能模块:1)初始化struct vgic_registers_region结构体字段和操作函数集;2)注册为MMIO总线设备;struct vgic_registers_region定义好了不同的寄存器区域,以及相应的读写函数,vgic_v2_dist_registers数组最终会提供给dispach_mmio_read/dispach_mmio_write函数来查询与调用;- 当Guest OS访问
Distributor时,触发IO异常并切换回Host进行处理,io_mem_abort会根据总线的类型(MMIO)去查找到对应的读写函数进行操作,也就是图中对应的dispatch_mmio_read/dispach_mmio_write函数,最终完成寄存器区域的读写; - 图中的红色线,代表的就是异常处理的执行流,可以说是一目了然了。
耗时耗力耗心血的一篇文章终于写完了,ARMv8 GICv2中断虚拟化的总体框架和流程应该算是理顺了,全网相关主题的文章并不多,希望能给带来点帮助吧。
如果对你有用的话,在看,分享,打赏三连吧。
参考
《arm_gic_architecture_specification》
《ARM_Interrupt_Virtualization》
《VM-Support-ARM》
《CoreLink GIC-400 Generic Interrupt Controller》
《Virtualization in the ARM Architecture》
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=721eecbf4fe995ca94a9edec0c9843b1cc0eaaf3
欢迎关注个人公众号,不定期更新内核相关技术文章

【转载】Linux虚拟化KVM-Qemu分析(六)之中断虚拟化的更多相关文章
- [转载]Linux 线程实现机制分析
本文转自http://www.ibm.com/developerworks/cn/linux/kernel/l-thread/ 支持原创.尊重原创,分享知识! 自从多线程编程的概念出现在 Linux ...
- 【原创】Linux虚拟化KVM-Qemu分析(六)之中断虚拟化
背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: KVM版本:5.9 ...
- KVM+QEMU虚拟化概念
概念: KVM,即Kernel-basedvirtual machine,由redhat开发,是一种开源.免费的虚拟化技术.对企业来说,是一种可选的虚拟化解决方案. 定义:基于Linux内核的虚拟机 ...
- 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设备直接分 ...
- 2018-2019-1 20189221 《Linux内核原理与分析》第六周作业
2018-2019-1 20189221 <Linux内核原理与分析>第六周作业 实验五 实验过程 将Fork函数移植到Linux的MenuOS fork()函数通过系统调用创建一个与原来 ...
- Linux实战教学笔记53:开源虚拟化KVM(一)搭建部署与概述
一,KVM概述 1.1 虚拟化概述 在计算机技术中,虚拟化意味着创建设备或资源的虚拟版本,如服务器,存储设备,网络或者操作系统等等 [x] 虚拟化技术分类: 系统虚拟化(我们主要讨论的反向) 存储虚拟 ...
- 2019-2020-1 20199329《Linux内核原理与分析》第六周作业
<Linux内核原理与分析>第六周作业 一.本周内容概述: 学习系统调用的相关理论知识,并使用库函数API和C代码中嵌入汇编代码两种方式使用getpid()系统调用 学习系统调用syste ...
- 理解 Linux 网络栈(2):非虚拟化Linux 环境中的 Segmentation Offloading 技术
本系列文章总结 Linux 网络栈,包括: (1)Linux 网络协议栈总结 (2)非虚拟化Linux环境中的网络分段卸载技术 GSO/TSO/UFO/LRO/GRO (3)QEMU/KVM + Vx ...
- KVM 介绍(5):libvirt 介绍 [ Libvrit for KVM/QEMU ]
学习 KVM 的系列文章: (1)介绍和安装 (2)CPU 和 内存虚拟化 (3)I/O QEMU 全虚拟化和准虚拟化(Para-virtulizaiton) (4)I/O PCI/PCIe设备直接分 ...
- KVM/QEMU桥接网络设置及kvm资料
KVM/QEMU桥接网络设置 配置kvm的网络有2种方法.其一,默认方式为用户模式网络(Usermode Networking),数据包由NAT方式通过主机的接口进行传送.其二,使用桥接方式(Brid ...
随机推荐
- [Wechat]概念辨析:微信的生态平台/运管平台
0 引言 微信的各类XX社区.XX文档.XX平台,实在是太多,让人眼花缭乱.必须得理一理了. 1 微信公众平台 https://mp.weixin.qq.com/ 即 微信公众号(小程序 / 订阅号 ...
- 笔记:设置redhat 7.2 默认root用户启动以及网络服务自启动
笔记:设置redhat 7.2 默认root用户启动以及网络服务自启动 1.root用户启动 root用户下打开 /etc/gdm/custom.conf文件,添加字段如下: [daemo ...
- web 页面/内容 触摸/点击滑动
监听标签的触摸/鼠标滑动事件,添加元素的切换动画,效果如下: 事件监听 鼠标事件和触摸事件监听: 1 componentDidMount() { 2 var teachingReportDiv = d ...
- JUC并发编程原理精讲(源码分析)
1. JUC前言知识 JUC即 java.util.concurrent 涉及三个包: java.util.concurrent java.util.concurrent.atomic java.ut ...
- Centos7 部署Django项目 uwsgi + nginx
启动 首先确保你的django项目是可以在虚拟环境中跑起来的,环境管理窝用的是pyenv,pyenv不知道什么东西的可以参考窝之前写过的Pyenv环境管理的安装文. 项目启动 python manag ...
- 2023-04-28:将一个给定字符串 s 根据给定的行数 numRows 以从上往下、从左到右进行 Z 字形排列 比如输入字符串为 “PAYPALISHIRING“ 行数为 3 时,排列如下 P
2023-04-28:将一个给定字符串 s 根据给定的行数 numRows 以从上往下.从左到右进行 Z 字形排列 比如输入字符串为 "PAYPALISHIRING" 行数为 3 ...
- 2021-02-07:给定两棵二叉树的头节点head1和head2,如何判断head1中是否有某个子树的结构和head2完全一样?
福哥答案2021-02-07: 对head1和head2序列化为str1和str2.然后用kmp算法去判断str2是否是str1的子串.如果是,head2是子树:如果不是,head2不是子树. 代码用 ...
- 2021-03-05:go中,io密集型的应用,比如有很多文件io,磁盘io,网络io,调大GOMAXPROCS,会不会对性能有帮助?为什么?
2021-03-05:go中,io密集型的应用,比如有很多文件io,磁盘io,网络io,调大GOMAXPROCS,会不会对性能有帮助?为什么? 福哥答案2021-03-05: 这是面试中被问到的.实力 ...
- 2022-01-03:比如arr = {3,1,2,4}, 下标对应是:0 1 2 3, 你最开始选择一个下标进行操作,一旦最开始确定了是哪个下标,以后都只能在这个下标上进行操作。 比如你选定1下标,
2022-01-03:比如arr = {3,1,2,4}, 下标对应是:0 1 2 3, 你最开始选择一个下标进行操作,一旦最开始确定了是哪个下标,以后都只能在这个下标上进行操作. 比如你选定1下标, ...
- Django4全栈进阶之路1 Django4下载与安装
python 下载安装: 下载网址:https://www.python.org/downloads/ 安装方法:https://www.cnblogs.com/beichengshiqiao/p/1 ...