首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

KVM 虚拟化技术在 AMD 平台上的实现(6)

KVM 虚拟化技术在 AMD 平台上的实现(6)

IO 设备的虚拟化物理设备仿真    对传统物理设备进行仿真是 KVM 最早期采用的支持虚拟机 IO 的方式。 这种方式的特点是客操作系统看到的设备和通常裸机操作系统上看到的设备有相同的格式和规范,如同样格式的 PCI 配置空间,同样的 MMIO 区域尺寸和格式,同样的 PIO 寄存器和功能。 因此客操作系统能使用已有的传统物理设备的驱动来访问这些仿真的设备。这种方式的好处是不要求客操作系统做任何软件上的改变。
    设备仿真的实现过程主要是让 VMM 截取客操作系统对设备 MMIO 和 PIO 空间的访问,然后分派不同的后端来处理与 MMIO 或 PIO 访问对应的逻辑。 就 KVM 而言,一般平台型设备,如 PIT, PIC, APIC 等是直接在 KVM 内核中实现后端的代码的, 而 PCI 总线上的设备或更外围的方式连接的设备,由于其逻辑的复杂性,都是通过 qemu-kvm 在用户空间来仿真的。在 KVM 内核空间的 MMIO 和 PIO 实现比较简单, KVM 实现了一个抽象的 IO_BUS 模型,把所有的设备都按 struct kvm_io_device 抽象实现,要求每个都提供自己的 struct kvm_io_dev_ops 接口的实现, 每个平台设备的 struct kvm_io_dev_ops 的 read/write 函数的主要功能就是根据 IO 地址读写该设备内核数据结构的不同部分, 并根据数据的值变化实现一定的逻辑。
    就 qemu-kvm 用户空间而言,最重要的工作是仿真设备的 MMIO 内存和 PIO 端口。  设备的 MMIO 内存可实现成三种方式 :
  • “physical_memory”。 这种方式所仿真的 MMIO 和虚拟机的 RAM 是一样的,即和 RAM 一样由 KVM 进行缺页处理并分配物理页, 后续的读写不用截取,这种方式经常用于 VGA 设备, 也可用于那种读写以后不需要同步通知 qemu-kvm 后端或用其他方式通知 qemu-kvm 后端的 MMIO 区域。
  • “io_memory”。 这种方式所仿真的 MMIO 会被 KVM 内核截取,执行路径会跳出 KVM 内核而回到用户空间,由 qemu-kvm 来完成 MMIO 读写的仿真。 这种方式下,一般不同类型的设备都提供了自己特定的 read/write 函数。 为支持这种方式,KVM 内核中代表每个 VCPU 的 struct kvm_run 区域被 mmap 到了用户空间,qemu-kvm 进程可根据 struct kvm_run 中的信息知道当前需要仿真的 MMIO 读写操作涉及的地址和尺寸。
  • “coalesced_memory”。  这种方式所仿真的 MMIO 会被 KVM 内核截取,但 KVM 并不会立即跳出到 qemu-kvm 用户空间,KVM 将需要仿真的读写操作形成一个记录 (struct kvm_coalesced_mmio), 放在在代表整个 VM 的 struct kvm 所指向的一个环形缓冲区中 (struct kvm_coalesced_mmio_ring), 这个环形缓冲区被 mmap 到了用户空间。 当下一次代表某个 VCPU 的 qemu-kvm 线程返回到用户空间后,就会对环形缓冲区中的记录进行处理,执行 MMIO 读写仿真。 也就是说,对于 “coalesced_memory” 方式, qemu-kvm 一次仿真的可能是已经被积累起来的多个 MMIO 读写操作, 显然这种方式是一种性能优化,它适合于对响应时间要求不是很严格的 MMIO 写操作。
   PIO 的仿真更简单。所有的 PIO 都会被 KVM 所截取,不能在内核处理的 PIO 使 KVM 跳出到 qemu-kvm 用户空间, 由 qemu-kvm 来完成 PIO 读写的仿真。 Qemu-kvm 同样是根据用户空间已经 mmap 了的每个 struct kvm_vcpu 的 struct kvm_run 区域以及 pid_data 区域知道需要仿真的 PIO 读写操作涉及的端口地址、尺寸及数据。
    当然 Qemu-kvm 设备仿真要做的更大量工作是在逻辑层,如对于网络设备,需要考虑仿真的网络包怎样经过 Host 的网络进行发送和接收, 桥接的还是 NAT 的; 对于块设备,需要考虑客操作系统写过来的磁盘块以怎样的方式组织到设备 Image 文件中, Qcow2 或 QED 的选择; 对于 VGA 设备,需要实现怎样把图像缓冲区中的内容通过远程表现处理,VNC 或 SPICE 等。 设备逻辑层所需要做的工作应该是目前开发空间比较大的地方, 这部分和 KVM 虚拟化的基本机制没太大关系,内容太多,在此就不描述了。
虚拟的功能设备    实现虚拟的功能设备是支持虚拟机 IO 的第二种方法。 虚拟的功能设备就是说虚拟机使用的 IO 设备不一定要遵守已有的设备标准如 SCSI 协议,Intel 以太网卡的规范等等, 只要实现上能完成操作系统想要的功能即可,如支持磁盘数据块的传送,TCP/IP 网络包的传送,实现 IO 完成的通知等。 这种方式由于不遵守已有的设备格式和规范,无法使用客操作系统上已有的物理设备驱动, 在 VMM 上虚拟化设备的同时,在客操作系统上也需要专有的驱动程序配合。 不同的 VMM 实现往往有自己独立的虚拟设备的实现机制,目前 KVM 采用的是称为 virtIO 的技术。 VirtIO 是 IBM 的 Rusty Russell 提出的实现虚拟设备的规范,其核心思想是通过定义一个公共的 ABI, 让客操作系统以简易的方式向 VMM 告知其数据缓冲区,并以这些缓冲区为基础承载不同类型虚拟设备的数据交换。 在 virtIO 的思想中,虽然逻辑数据是客主双方交换的,但缓冲区总是由客操作系统方面提供的。  以 Linux 客操作系统为例,VirtIO 的实现可简要描述如下 :
  • KVM 以仿真的 PCI 设备的方式向客操作系统呈现每一个 VirtIO 设备。  所有类型的 VirtIO 设备都使用同一个 Device ID。 VirtIO 设备的类型由 PCI 设备的 subsystem Vendor Id 及 subsystem Device Id 来区分。 目前已经实现的 VirtIO 设备包括 virtio_net, virtio_blk, virtio_baloon, virtio_console 及 virtio_hw_random。 在 Linux 上 virtio_pci 驱动的设备 probe 函数 virtio_pci_probe 最终会调用具体的 VirtIO 设备的 probe 函数 ( 如 virtnet_probe) 来识别其设备,建立相关的数据结构。
  • 数据结构 struct virtio_device 用来代表被识别的每一个 virtIO 设备, 和该结构关联的是一组操作接口, 包括 get, set, get_status, set_status, get_features,finalize_features,reset, find_vqs 及 del_vqs。 这些接口的作用就是通过对 virtIO 设备的配置空间的读写,来和设备进行交互,或对设备进行控制。  其中 find_vqs 就是用来发现该 virtIO 设备的后端提高了那些 virtioqueue。
  • 在 virtIO 的框架中,用来在客操作系统和 VMM 之间实现数据传输的抽象机制就称为 virtioqueue。 Virtioqueue 就是一组操作接口, 包括 add_buf, get_buf, disable_cb, enable_cb, notify 以及 callback。  其中 callback 是由具体类型的 virtIO 驱动实现的,其他几个操作的实现則由特定 Linux 版本的 virtioqueue 采用的具体实现方式确定。
  • virtioqueue 针对于不通的 VMM,是可以有不同的数据传输实现方式的。 对于目前的 KVM 来说,virtioqueue 采用的实现方式称为 vring。 Vring 规定了客操作系统发布到 virtioqueue 的 buf 的组织方式。物理的 vring 包括 vring_desc 数组, 可用的 vring_desc 环,已用的 vring_desc 环三个部分。 其中 vring_desc 数组用来将客操作系统向 virtIO 设备发送的 IO Request 组织称 vring_desc 链表的形式;  可用的 vring_desc 环用来标识那些 VMM 端当前可以处理的描述符; 已用的 vring_desc 环是 VMM 用来标识当前客操作系统端可处理的描述符。 Vring 的这三个部分处于一个连续的客操作系统物理内存块上,其 gpa 由 virtioqueue 在初始化时确定。 vring 的三个部分中用到的全部指针,包括客操作系统分配的 buf, 协议控制头数据,状态块数据的指针也都是用的 gpa,所以 VMM 端不存在困难理解 vring。  Vring 和 virtio 设备的 PCI 配置空间一起,提供了一个明确的实现虚拟设备机制的 ABI。
  • virtioqueue 的 notify 接口是客操作系统端用来向 VMM 发通知的,如一组数据相关的若干 vring_desc 写入到 vring 后,客操作系统可通过 notify 通知 VMM,以便 VMM 能立即处理。 Notify 的实现实际上就是向 virtio 设备的 PCI 配置空间的某寄存器写入某个值,VMM 通过 MMIO 捕获机制接收到该通知。 另外 virtio 后端在合适的时候还会通过虚拟中断的方式向客操作系统发中断,virtio 驱动提供的 callback 函数就是由中断处理程序激活了 work_queue 的方式执行的。
    目前 virtIO 设备的后端主要在 qemu-kvm 用户空间实现,但对于 virtio-net 设备,RHEL6.2 已经将后端驱动的代码移植到了内核层,称为 vhost-net, 这样做消除了不必要的用户和内核空间的交互,能明显提高 virtio-net 的性能。
   目前 KVM 上除 virtIO 外,还有其他的方式的虚拟设备的实现,如 kvmclock。 kvmclock 实现的是一个时钟源 kvm_clock, 为客操作系统提供精确的 System Time 和 Wall Clock。 kvm_clock 的实现使用了硬件的支持,如 AMD-V 的 VMCB.CONTROL 提供的 TSC_OFFSET, 以及 KVM 使用的一些技巧,如用两个定制的 MSR 寄存器 MSR_KVM_WALL_CLOCK 和 MSR_KVM_SYSTIME_TIME, 通过截取这两个定制的 MSR 让 KVM 来直接访问客操作系统的时间变量。 在 KVM 上,和时间源及定时器硬件相关的虚拟化本身可构成一个独立的话题,笔者打算以后用专门的文章介绍。
返回列表