云计算国产化之路 vnc登录 文件系统直通(virtio-9p) 扩展qemu接口 gpg WARNING 珍藏博客 虚拟内存情况dommemstat分析 免密码自动登录脚本 Linux网桥 测试网段IP占用情况 Linux 进程状态 systemc强制依赖 调试openstack ut uefi p2v 重做ubuntu内核 virsh创建虚拟机简介 virtio IO路径 虚拟化层升级后磁盘无法卸载卷 vmtouch使用 Taint flags 主机和虚拟机文件共享处理的几种方法 kvm分析工具 kvm中对磁盘的io cache 虚拟化不同导致的guestos中软件安装差异(未解决) 设备直通(PCI Assignment)到底是咋整的 virtio到底是咋整的 内核启动参数 虚拟化实时性提升(零)之配置步骤 中断虚拟化(pic)到底是咋整的 中断虚拟化(apic)到底是咋整的 Raid卡配置丢失导致服务器无法启动 tainted kernels cpu stuck for 23s问题分析 虚拟化实时性提升(一)之hostOS切换为强实时系统 内存虚拟化到底是咋整的 qemu-kvm中vcpu虚拟化到底是咋整的 风河虚拟化技术点分析 使用qga的好处 qemu迁移代码分析 虚拟机串口配置及其导出到主机pts和console.log System-based I/O vs. Raw I/O 虚拟机使用Hugepage(大页) 硬件辅助分页(hardware assisted paging) 修改centos7默认启动项目 virtio的工作流程——kernel中virtio-pci初始化(2) virtio的工作流程——qemu中virtio-backend初始化(1) qmp ceilometer取不到memory.usage指标 Virtio-Balloon超详细分析 slabtop输出 虚拟机磁盘cache导致的host os kernel崩溃 虚拟机cpu和memory性能优化测评 PCI配置空间(PCI Configuration Space) centos下网卡设备直通(VT-dpci passthrough)遇到的问题及其解决思路 libguestfs详解 yum卸载软件包及其依赖 通过原始Centos ISO来定制自己的ISO centos下网卡设备直通(VT-d,pci passthrough) (占位符)window虚拟机中拔盘如何通知到libvirt和qemu后端的 cirrus漏洞分析CVE-2017-2615 XSA-208 qcow2随笔 控制寄存器概览 ceilometer对接postgresql 解压initrd和vmlinuz qemu guest agent验证 QEMU升级指南(待续) ubuntu中kdump的配置 qemu(2.3)接口梳理 热迁移导致的FC存储场景下的multipath卷残留问题分析 virsh命令(4)secret,snapshot,pool,volume部分 virsh命令(3)之interface,filter,network virsh命令(2)monitor,host,nodedev部分 virsh命令(1)之domain部分 QEMU内存管理之FlatView模型(QEMU2.0.0) ovirt基于sanock的高可用(主机粒度HA) Sanlock防脑裂场景功能测试用例 gnocchi配置及与ceilometer对接指南 make patch for libvirt in centos centos使用sanlock指导 高可用nfs资料 ubuntu14中使用sanlock指导 LVM操作指南 sanlock相关功能验证流程汇总 make patch for libvirt in ubuntu libvirt.so.0-version `LIBVIRT_PRIVATE_1.2.7' not found gdb debug libvirt 基于ubuntu社区源码包编译libvirt compile libvirt(centos) No PCI buses available nfs lead to Linux halt nfs install and config anti-virus for cloud platform nova fetch image from glance(something about _base) token auth process ovs入门指南 virt software anti-virus something about ceilometer disk sample context interview questions openstack vm injection openstack Restful and RPC murano 概览 创建虚拟机流程(compute节点)之网络创建 创建虚拟机流程之compute_api之虚拟机实例填充之配额检测 创建虚拟机流程之compute_api之基本参数生成 创建虚拟机流程之compute_api 创建虚拟机流程(主) 创建虚拟机之image 创建虚拟机流程之准备网桥 创建虚拟机流程之virt 创建虚拟机流程之compute节点 CI/CD研发流程之工程创建 CI/CD研发流程之代码合入 CI/CD研发流程之UT(单元测试) 向openstack社区合入代码记 openstack/ceilometer/gnocchi杂谈 影子页表原理 mem_add(exec.c) qemu编译安装调试 openstack/ceilometer/gnocchi之Grafana简介 openstack wiki etcd openstack计量ceilometer openstack计费cloudKitty enventlet backdoor USB 安装VMWARE ESX pycharm设置指南 无法执行modprobe -a kvm-intel解决办法 QEMU配置项 网络不通小记 libvirt之XML(虚拟机定义文件) openstack-horizon 证书认证 ceilometer与ceph对接 openstack定时任务剖析(TODO) 服务器重启后mongodb进程无法启动 ubuntu14下新增openstack服务到service的导引 ERROR 1045(28000)-数据库连不上 Python两个内置函数—locals和globals unknown exit, hardware reason 31

中断虚拟化(pic)到底是咋整的

2017年05月20日

背景

本文叙述了中断控制器(pic 8259)的创建
中断注入处理的原理
qemu用户态和内核态的协作
最后举例说明中断处理的过程

虚拟中断控制器的创建

初始化

(1) 用户态空间中断模块初始化

kvm_init==>s->irq_set_ioctl = KVM_IRQ_LINE_STATUS;
kvm_init==>kvm_irqchip_create

a) kvm_vm_ioctl(s,KVM_CREATE_IRQCHIP) 让内核态创建一个中断控制器
b) kvm_init_irq_routing
==> kvm_check_extension(s,KVM_CAP_IRQ_ROUTING) 检察内核态的Irq routing.
–(进入内核态)–>kvm_vm_ioctl_check_extension_generic
–> case KVM_CAP_IRQ_ROUTING:return KVM_MAX_IRQ_ROUTES; (4096)

(2) 内核态空间中断模块初始化

进入kvm_arch_vm_ioctl方法中

(3.1) kvm_create_pic

  1. 创建虚拟设备

    kvm_iodevice_init(&s->dev_master,&picdev_master_ops); kvm_iodevice_init(&s->dev_slave, &picdev_slave_ops); kvm_iodevice_init(&s->dev_eclr, &picdev_eclr_ops);

  2. 注册端口操作

    kvm_io_bus_register_dev(kvm, KVM_PIO_BUS, 0x20, 2, &s->dev_master); kvm_io_bus_register_dev(kvm,KVM_PIO_BUS, 0xa0, 2, &s->dev_slave); kvm_io_bus_register_dev(kvm,KVM_PIO_BUS, 0x4d0, 2, &s->dev_eclr);

  3. 端口操作

当guestos 对8259做IOport操作时,发生VM-Exit

[EXIT_REASON_IO_INSTRUCTION]= handle_io, 会进入函数handle_io

handle_io
==> emulate_instruction
==>x86_emulate_instruction
==> emulator_pio_in_out
==> kernel_pio
==>kvm_io_bus_read/write(kvm_main.c)

kvm_io_bus_read/write 会首先根据port地址取到dev;然后调用dev->ops->read/write.
也就是对应调用以下ops

kvm_iodevice_init(&s->dev_master, &picdev_master_ops);
kvm_iodevice_init(&s->dev_slave, &picdev_slave_ops);
kvm_iodevice_init(&s->dev_eclr, &picdev_eclr_ops);

(3.2). kvm_ioapic_init

(3.3). kvm_setup_default_irq_routing

–>kvm_set_irq_routing
–>setup_routing_entry
–>kvm_set_routing_entry

kvm_setup_default_irq_routing创建默认路由表,中断路由表存在kvm->irq_routing中

default_routing是一个数组,里面是各个中断,每个中断是一个kvm_irq_routing_entry结构体
kvm_setup_default_irq_routing作用是组装对象,放到kvm->irq_routing

struct kvm_irq_routing_table {
       int chip[KVM_NR_IRQCHIPS][KVM_IRQCHIP_NUM_PINS];
       struct kvm_kernel_irq_routing_entry *rt_entries;
u32 nr_rt_entries;
struct hlist_headmap[0];
};

上面结构体中有kvm_kernel_irq_routing_entry
下面先看看与中断处理相关的几个概念(IRQ, GSI, Vector):

IRQ是PIC时代的产物,由于ISA设备通常是连接到固定的PIC管脚,所以说一个设备的IRQ实际是指它连接的PIC管脚号。
IRQ暗示着中断优先级,例如IRQ0比IRQ3有着更高的优先级。
当前进到APIC时代后,或许是出于习惯,人们仍习惯用IRQ表示一个设备的中断号,但对于16以下的IRQ,它们可能不再与IOAPIC的管脚对应,例如PIT此时接的是2号管脚。
Pin是管脚号,通常它表示IOAPIC的管脚(前面说了,PIC时代我们用IRQ)。Pin的最大值受IOAPIC管脚数限制,目前取值范围是[0,23]。
GSI是ACPI引入的概念,全称是GlobalSystemInterrupt。它为系统中每个中断源指定一个唯一的中断号.
Vector是CPU的概念,是中断在IDT表中的索引。每个IRQ(或GSI)都对应一个Vector。

在PIC模式下,IRQ对应的vector=startvector+IRQ;
在APIC模式下,IRQ/GSI的vector由操作系统分配

struct kvm_kernel_irq_routing_entry {
u32 gsi; //该中断项gsi的值
u32 type; //irq chip 还是msi
int (*set)(structkvm_kernel_irq_routing_entry *e,
struct kvm *kvm, int irq_source_id, intlevel,
bool line_status); //Set是中断的触发函数
union {
   struct {
   unsigned irqchip;
   unsigned pin;
   } irqchip;
   struct msi_msg msi;//msi消息中断将在第5张pci一节分析
   ......
};
struct hlist_node link;
};

set方法(中断触发)是按照如下方式指定的
也就是说中断触发后,会根据不同的中断调用到不同方法中

创建关联

(1) 用户空间中断控制器与虚拟pc的关联

pc_init1

  1. kvm_pc_setup_irq_routing
    将中断信息存储到KVMState->irq_routes中
    并通过kvm_irqchip_commit_routes将中断情况同步到内核中

  2. qemu_allocate_irqs
    生成IRQState列表由gsi指针持有
    后续初始化其他设备的时候将pic通过gsi传送过去

  3. kvm_i8259_init
    初始化8259设备,8259的handler 是kvm_pic_set_irq

(2). 进入内核态
内核态irq routing

kvm_vm_ioctl
==> case KVM_SET_GSI_ROUTING
==> kvm_set_irq_routing(irqchip.c)

将用户态设置的entry 更新到kvm->irq_routing中。

对8259而言设置如下:irq 0-7 关联到PIC_MASTER, 8-15关联到PIC_SLAVE

kvm_set_irq_routing
==> setup_routing_entry
==> kvm_set_routing_entry(irq-_commn.c)

中断注入

qemu调用irq_set_ioctl向内核中注入中断
内核中接受到请求后产生中断请求
cpu-entry的时候检测到中断请求,将中断刷入vmcs向cpu产生中断

(1). 用户空间

通过qemu_set_irq触发中断

通过前文我们知道handler为kvm_pic_set_irq

s->irq_set_ioctl在前文中已经初始化为了KVM_IRQ_LINE_STATUS

(2). 内核态

kvm_vm_ioctl
==>case KVM_IRQ_LINE_STATUS: case KVM_IRQ_LINE:
==>kvm_vm_ioctl_irq_line
==>kvm_set_irq(kvm,KVM_USERSPACE_IRQ_SOURCE_ID,irq_event->irq,irq_event->level, line_status);
==>irq_set[i].set(&irq_set[i], kvm, irq_source_id, level,line_status);

set 对于8259为:kvm_set_pic_irq
kvm_pic_set_irq
==>pic_update_irq(i8259.c)

在VM-Entry时vcpu_enter_guest方法中

==>kvm_cpu_has_injectable_intr
==>kvm_cpu_has_extint

其中output 会在下面这个方法中被设置

因而kvm_x86_ops->enable_irq_window
也就是vmx.c中的enable_irq_window会被调用
将相关中断信息写入vmcs中提供给cpu实现中断

示例:hpet中断

本节以hpet虚拟驱动为例分析虚拟驱动如何来注入中断

hpet_init中完成了mmio的注册

hpet_ram_write是hpet mmio write的实现函数
在该函数中根据寄存器状态决定是否产生中断
中断的注入函数是qemu_set_irq

hpet_realize中会实例化对应的s->irqs[i]

下面剩下的问题就是s->irqs[RTC_ISA_IRQ对应的handler是如何初始化的

pc_basic_device_init中初始化的hpet设备

可见通过中断最终和gsi进行关联的

通过前文我们知道gsi的hander是kvm_pc_gsi_handler
gsi = qemu_allocate_irqs(kvm_pc_gsi_handler, gsi_state,GSI_NUM_PINS);