PCI设备主要有三个空间:配置空间(Configuration Space)、内存空间(Memory Space)和I/O空间(I/O Space)。

1. 配置空间(Configuration Space)

  • 用途:用于设备初始化和配置。
  • 大小:每个PCI设备都有256字节的配置空间。
  • 访问方式:通过总线编号、设备编号和功能编号进行访问。
  • 内容
    • 设备标识信息(如供应商ID、设备ID)。
    • 状态和命令寄存器。
    • 基地址寄存器(BARs),用于映射设备的内存空间和I/O空间。
    • 中断线和中断引脚等信息。

2. 内存空间(Memory Space)

  • 用途:用于访问设备的寄存器和内存,适用于高带宽访问。
  • 大小:由设备制造商定义,映射到系统内存地址空间中。
  • 访问方式:通过内存读写指令进行访问。
  • 内容
    • 设备寄存器:用于控制和状态读取。
    • 设备专用内存:如帧缓冲区、DMA缓冲区等。

3. I/O空间(I/O Space)

  • 用途:用于访问设备的控制寄存器,适用于低带宽访问。
  • 大小:由设备制造商定义,映射到系统的I/O地址空间中。
  • 访问方式:通过特殊的I/O指令(如inout)进行访问。
  • 内容
    • 设备控制寄存器:用于执行特定的I/O操作。

总结

  • 配置空间主要用于设备初始化和配置。
  • 内存空间用于高速访问设备的寄存器和内存。
  • I/O空间用于低速访问设备的控制寄存器。

pci的虚拟化主要是对上述的三个空间做管理。考虑到多数设备中并不存在多条pci总线,且该pci总线的所有权一般归属于zone0,为了保证zone0中pci设备的访问速度,当不存在需要将该总线上的设备分配给其他zone时,hvisor并不会对zone0的pci总线及pci设备做任何处理。

在将PCI设备分配给zone时,我们需要确保zone0中的Linux不再使用它们。只要设备被分配给其他zone,那么zone0就不应该访问这些设备。很遗憾,我们不能仅仅使用PCI热插拔来在运行时移除/重新添加设备,因为Linux可能会重新编程BARs并定位资源到我们不期望或者不允许的位置。因此,需要一个存在于zone0内核中的驱动拦截对这些PCI设备的访问,我们将目光投向了hvisor tool。

hvisor tool会将自己注册为一个PCI虚拟驱动程序,并在其他zone使用这些设备时声明对这些设备的管理权。在创建zone之前,hvisor会让这些设备从他们自己的驱动程序中解绑并绑定到hvisor tool。当一个zone被销毁时,这些设备实际上已经没有zone使用,但是在zone0看来hvisor tool仍然是一个有效的虚拟驱动程序,所以设备的释放需要手动完成。hvisor tool会释放绑定到这些zone的设备,从zone0 linux的角度来看,这些设备不被绑定到任何驱动程序,那么如果需要使用这些设备linux会自动的重新绑定正确的驱动程序。

现在我们需要让zone能够正确的访问到pci设备,为了尽可能简洁的完成这一目标,我们直接复用了pci总线的结构,也就是说,关于pci总线的内容会同时出现在需要使用该总线上设备的设备树中,但是除了真正拥有这条总线的zone以外,其他zone只能通过mmio由hvisor代理访问该设备,当zone试图访问一个PCI设备时,hvisor会检查是否拥有该设备的所有权,所有权在zone创建时一同被声明。如果zone访问一个属于自己的设备的配置空间,hvisor会正确的返回信息。

目前,I/O空间和内存空间的处理方案与配置空间相同。因为bars资源的唯一性,配置空间不可能被直接分配给zone,且对bar空间的访问频率很低,并不会过多的影响效率。但是I/O空间和内存空间的直接分配是理论上可行,进一步会将I/O空间和内存空间直接分配给对应的zone以提高访问速度。

为了方便在QEMU中测试PCI虚拟化,我们编写了一个PCI设备。