设为首页 收藏本站
查看: 1805|回复: 0

[经验分享] kvm-qemu 设备IO虚拟化

[复制链接]
发表于 2015-12-24 15:32:57 | 显示全部楼层 |阅读模式
  1. 虚拟设备的IO地址注册
  
  如我们所知,KVM虚拟机的设备模拟是在QEMU中实现的,而KVM实现的实质上只是IO的拦截。换句话说,真正的虚拟设备IO地址注册是在QEMU代码里面实现的。
  在QEMU中,在初始化我们的硬件设备的时候需要注册我们的IO空间,在这里有下面两种IO注册方法:
(1) PIO(port IO) 端口IO
(2) MIO(memory may IO)内存映射IO
  为了说明原理,本文只讨论PIO相关的实现,MMIO类似。注册IO对对于一般的ISA设备我们可以直接调用下面的函数进行IO地址的注册,使用起来非常的简单。
  int register_ioport_read(pio_addr_t start, int length, int size,IOPortReadFunc *func, void *opaque);
  int register_ioport_write(pio_addr_t start, int length, int size, IOPortWriteFunc *func, void *opaque);
  
  对于PCI设备来说,IO地址注册就要多一步,因为要进行PCI bar地址与IO的映射,所以必须先调用下面函数来给bar注册PCI地址。
  void pci_register_bar(PCIDevice *pci_dev, int region_num,
                            pcibus_t size, uint8_t type,
                            PCIMapIORegionFunc *map_func);
  关键参数说明:第一个是PCI设备指针,第三个是我们需要注册IO地址的空间长度,最后一个是我们要进行IO操作映射的初始化函数指针。
  
  static void map_func(PCIDevice *pci_dev,int region_num, pcibus_t addr,pcibus_t size,int type);
  关键参数说明:第一个依然是PCI设备指针,第三个是PCI地址映射的PIO起始地址,这个起始地址是在我们注册PCI地址的时候,PCI总线通过计算比较PIO地址空间得到的一个PIO地址起始空间,所以这里不能够随便的改变,因为PCI地址空间需要和PIO空间进行映射。所以在我们注册设备PIO空间的时候必须将这个地址作为注册IO空间的起始地址。这个函数实在更新bar映射的时候被调用的,实际上它的作用就是给PCI设备安装IO读写函数,能够操作IO,如果在KVM里面实现IO拦截,这里的函数似乎就失去意义了。
  
  举个例子进一步说明:
  1.注册PIC地址。空间0x800,映射函数xche_ioport_map。
  pci_register_bar(&s->dev,1,0x800,PCI_BASE_ADDRESS_SPACE_IO,xche_ioport_map);
  
  2.实现映射函数,PCI bar地址初始化以后会将映射IO的起始地址作为addr参数传到映射函数,然后通过之前的register函数注册IO地址空间,在这个操作以后,一旦这些位的IO发生读写,虚拟机就会产生VM-exit,进而我们的ioread和iowrite就能够被调用。
  static void xche_ioport_map(PCIDevice *pci_dev,int region_num,pcibus_t addr,pcibus_t size,int type)
{
      CXState *s = DO_UPCAST(CXState,dev,pci_dev);
      register_ioport_write(addr,0x800,1,xche_ioport_writeb,s);
      register_ioport_read(addr,0x800,1,xche_ioport_readb,s);
}
  这样,我们虚拟的IO空间就成功的注册了。
  
  
  2. KVM IO地址的拦截
  
  我们之前已经知道,QEMU运行在用户空间,KVM运行在内核空间,客户机运行在KVM内部,QEMU通过IOCTL与KVM进行交互,从这里可以看出,KVM直接与客户机进行交互。所以客户机的IO操作,KVM先得到,可以进行拦截,这个也是我们能实现拦截的前提条件。下面通过一个我自己实现的实例来说明怎么在KVM里进行IO拦截。
  
(1)通过IOCTL,可以在QEMU中调用KVM的初始化函数,初始化KVM设备
QEMU:
kvm_vm_ioctl(kvm_state, KVM_CREATE_XCHE);
KVM:
case KVM_CREATE_XCHE:
          kvm->arch.vxche = kvm_create_xche(kvm,0x1000);
(2)注册KVM设备。主要就是进行内存的分配和IO总线的注册。
static const struct kvm_io_device_ops xche_dev_ops = {
   .read     = xche_ioport_read,
   .write    = xche_ioport_write,
};
/* Caller must hold slots_lock */
struct kvm_xche *kvm_create_xche(struct kvm *kvm, gpa_t base_addr, gpa_t length)
{
     struct kvm_xche *xche;
     int ret;
      xche = kzalloc(sizeof(struct kvm_xche), GFP_KERNEL);
     if (!xche)
        return NULL;
     /*获取中断资源id,在KVM中注册的设备这个ID都是唯一的,对应着QEMU和KVM里面的设备*/
     xche->irq_source_id = kvm_request_irq_source_id(kvm);
     if (xche->irq_source_id < 0) {
        kfree(xche);
        return NULL;
     }
     xche->kvm = kvm;
     kvm_iodevice_init(&xche->dev, &xche_dev_ops);
    /*将设备注册到KVM里面的PIO总线*/
    ret = kvm_io_bus_register_dev(kvm, KVM_PIO_BUS, xche->dev);
     return xche;
}

  通过上面的步骤我们就成功的注册了KVM设备,并且将我们的IO读写函数挂到了KVM的PIO总线,这样,当虚拟机退出的时候,分析需要处理IO,就会遍历所有挂在PIO总线上的设备,分别调用它们的读写函数,这样就实现了IO操作的触发,而在虚拟机退出以后还会判断此段IO是否挂载设备,如果设备不存在就会退回QEMU处理,否则直接在KVM内部处理,这样就实现了IO的拦截。
  KVM拦截流程如下图所示:
DSC0000.png

图1 KVMIO拦截



3.KVM IO读写处理


前面完成了KVM对IO设备的添加和对IO操作的拦截,现在当我们成功拦截到IO以后应该如何操作呢?

IO操作会主动的调用我们之前设置的读写函数xche_ioport_read和xche_ioport_write。那我们需要做的就是实现这两个函数,在这里本文只简单描述实现这两个函数的框架,具体实现和具体设备相关。

下面用一个read函数来进行说明:

这个读函数,第一个是设备指针,第二个是PIO发生读写的地址,第三个是地址数据指针,我们通过改变这个指针就能实现客户机读读数据的功能。

static int xche_ioport_read(struct kvm_io_device *this,   gpa_t addr, int len, void *data)
{
     struct kvm_xche *xche = dev_to_xche(this);
     struct kvm *kvm = xche->kvm;
     u32 val = *(u32 *) data;
     int pos,ret;

     /*判断是否是这个设备的IO事件,实现IO地址过滤*/
     if (!xche_in_range(addr))
          return -EOPNOTSUPP;


     val  &= 0x00ff;

     /*通过掩码进一步提取地址*/
     pos = addr&0x1F;

     /*根据不同的地址执行不同的操作*/

     switch (pos){

         case:

         break;

         ...

         ...

      }

      if (len > sizeof(ret))
         len = sizeof(ret);
      /*将数据拷贝到读取的数据地址/
      memcpy(data, (char *)&ret, len);

      return 0;

}

在这个函数中,因为所有的IO退出都会触发每一个挂在在IO总线上面的设备读写函数,所以在这里要进行一个IO地址过滤,只处理本设备映射的地址。这样通过这个函数我们就实现了IO读的虚拟化,模拟了硬件的各种IO操作,主要的模拟也就在switch中实现,因为设备不同操作也不同,所以就不举例说明了。同样写函数的实现也类似,只是少一个操作不需要向IO地址写入数据。


  总结:通过本文的描述就能够在KVM中实现添加一个自己想要虚拟的设备,这需要再QEMU挂载真是模拟的设备,并且在KVM中进行拦截,然而KVM中的拦截是个可选过程,同样在QEMU中也能实现。不过在KVM中实现,可能让虚拟机不用再退回到用户空间,提高一定的效率。当然不是所有的设备都适合在kVM中进行IO的拦截和处理。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-155839-1-1.html 上篇帖子: KVM源代码分析1:基本工作原理 下篇帖子: KVM基本原理及架构八-KVM内核模块重要流程分析
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表