当前的I/O虚拟化技术有其优点和缺点。没有一个是基于任何行业标准的。 业界认识到可替代架构的问题,并正在开发可共享的新设备。这些设备复制每个VM所需的资源,以便直接连接到I/O设备,这样就可以在不涉及VMM的情况下进行数据移动。
本机共享设备通常为它们公开的每个接口提供惟一的内存空间、工作队列、中断和命令处理,同时利用主机接口背后的公共共享资源。这些共享资源仍然需要进行管理,通常将一组管理寄存器公开给VMM中的可信分区。见图1。 Figure 1. Natively and Software Shared
通过拥有独立的工作队列和命令处理,这些设备能够同时接收来自多个源的命令,并在将它们传递到次要结构(例如,以太网或SAS链接)之前将它们合并在一起。虚拟化软件不再需要将I/O请求多路复用到串行流中。 本地共享设备可以通过多种方式实现,包括标准化的和专有的。由于大多数这些设备是通过PCI访问的,PCI- SIG决定创建一个标准方法。PCI-SIG SR-IOV规范定义了创建本机共享设备的标准化机制。 图1显示了一个可能的示例配置,其中三个虚拟机通过虚拟函数原生(直接)访问以太网控制器中的专用资源,同时物理函数也有自己的资源。
PCI-SIG SR-IOV规范的目标是通过为每个虚拟机提供独立的内存空间、中断和DMA流来标准化绕过VMM参与数据移动的方法。SR-IOV体系结构的设计目的是允许设备支持多个虚拟功能(VFs),并将大量注意力放在最小化每个附加功能的硬件成本上。 SR-IOV引入了两种新的函数类型:
物理功能(PFs):这些是完整的PCIe功能,包括SR-IOV扩展功能。该功能用于配置和管理SR-IOV功能。虚拟功能(VFs):这些是“轻量级”PCIe函数,包含数据移动所需的资源,但配置资源的集合被精心最小化。虚拟化的直接分配方法提供了非常快的I/O。但是,它阻止了I/O设备的共享。SR-IOV提供了一种机制,通过这种机制,单个根函数(例如单个以太网端口)可以看起来是多个独立的物理设备。 能够支持SR-IOV的设备可以配置(通常由VMM配置),使其在PCI配置空间中作为多个功能出现,每个功能都有自己的配置空间,带有基址寄存器。VMM通过将实际配置空间VFs映射到VMM提供给虚拟机的配置空间,将一个或多个VFs分配给VM。参见图2。 Figure 2. Mapping VF Configuration
支持SR-IOV的设备提供许多可配置的独立VFs,每个VFs都有自己的PCI配置空间。VMM将一个或多个VF分配给虚拟机。内存转换技术,如Intel®VT-x和Intel®VT-d提供硬件辅助技术,允许直接DMA传输到和从VM,从而绕过VMM的软件切换。
查看系统是否支持虚拟化,egrep -c ‘(vmx|svm)’ /proc/cpuinfo 大于1表示开启了,等于0表示没开启,如果没开启则需要在BIOS中开启intel VT-d 查看kvm模块是否被加载lsmod |grep kvm,如果有信息,则表示加载成功。 Centos系统默认没有启动IOMMU功能,在grub文件的GRUB_CMDLINE_LINUX后面添加intel_iommu=on 检测是否生效 sudo virt-host-validate
https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/virtualization_host_configuration_and_guest_installation_guide/index
网上这方面资料还是比较多的: https://www.cnblogs.com/sammyliu/p/4548194.html
https://www.cnblogs.com/liuhongru/p/11068460.html
https://blog.csdn.net/u010443710/article/details/104756445?utm_medium=distribute.pc_relevant_t0.none-task-blog-OPENSEARCH-1.edu_weight&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-OPENSEARCH-1.edu_weight
lspci -vvs bus:dev.fun, 如果有类似下面的capability,说明支持SR-IOV, 并能看出来支持几个VFs等详细信息。
echo 2 > /sys/bus/pci/devices/0000:5e:00.0/sriov_numvfs 上面红色的是一个支持SR-IOV功能的pcie 网卡,2是enable的数量,从下图看,enable了2个VFs后,多出来了2个pcie设备。
从上面命令上看,入口是每个pcie的sriov_numvfs节点,下面是实现节点的代码:
/* * num_vfs > 0; number of VFs to enable * num_vfs = 0; disable all VFs * * Note: SRIOV spec doesn't allow partial VF * disable, so it's all or none. */ static ssize_t sriov_numvfs_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct pci_dev *pdev = to_pci_dev(dev); int ret; u16 num_vfs; ret = kstrtou16(buf, 0, &num_vfs); if (ret < 0) return ret; if (num_vfs > pci_sriov_get_totalvfs(pdev)) return -ERANGE; device_lock(&pdev->dev); if (num_vfs == pdev->sriov->num_VFs) goto exit; /* is PF driver loaded w/callback */ if (!pdev->driver || !pdev->driver->sriov_configure) { pci_info(pdev, "Driver doesn't support SRIOV configuration via sysfs\n"); ret = -ENOENT; goto exit; } if (num_vfs == 0) { /* disable VFs */ ret = pdev->driver->sriov_configure(pdev, 0); goto exit; } /* enable VFs */ if (pdev->sriov->num_VFs) { pci_warn(pdev, "%d VFs already enabled. Disable before enabling %d VFs\n", pdev->sriov->num_VFs, num_vfs); ret = -EBUSY; goto exit; } ret = pdev->driver->sriov_configure(pdev, num_vfs); if (ret < 0) goto exit; if (ret != num_vfs) pci_warn(pdev, "%d VFs requested; only %d enabled\n", num_vfs, ret); exit: device_unlock(&pdev->dev); if (ret < 0) return ret; return count; }从上面看,支持SR-IOV的driver需要实现下面的函数接口
static struct pci_driver igb_driver = { .name = igb_driver_name, .sriov_configure = `igb_pci_sriov_configure`, / 支持sriov功能需要实现的函数 / }; static int igb_pci_sriov_configure(struct pci_dev *dev, int num_vfs) { #ifdef CONFIG_PCI_IOV if (num_vfs == 0) return igb_pci_disable_sriov(dev); else return igb_pci_enable_sriov(dev, num_vfs); #endif return 0; }最终调用的是pci api: pci_enable_sriov
/** * pci_enable_sriov - enable the SR-IOV capability * @dev: the PCI device * @nr_virtfn: number of virtual functions to enable * * Returns 0 on success, or negative on failure. */ int pci_enable_sriov(struct pci_dev *dev, int nr_virtfn) { might_sleep(); if (!dev->is_physfn) return -ENOSYS; return sriov_enable(dev, nr_virtfn); }下面详细分析下sriov_enable
sriov_enable(struct pci_dev *dev, int nr_virtfn) pci_read_config_word(dev, iov->pos + PCI_SRIOV_INITIAL_VF, &initial); 这里省略数行,主要是用来判断initial,total_VFs,nr_virtfn是否合法 bus = pci_iov_virtfn_bus(dev, nr_virtfn - 1); //计算出来一个bus num分给VFs使用 dev->bus->number + ((dev->devfn + dev->sriov->offset + dev->sriov->stride * vf_id) >> 8); pci_enable_resources(dev, bars);//设置VFs 配置空间的PCI_COMMAND字段 pcibios_sriov_enable(dev, initial); pci_iov_set_numvfs(dev, nr_virtfn);/设置VFs number,读取offset,stride/ iov->ctrl |= PCI_SRIOV_CTRL_VFE | PCI_SRIOV_CTRL_MSE; pci_write_config_word(dev, iov->pos + PCI_SRIOV_CTRL, iov->ctrl);/ VF Enable, VF Memory Space Enable/ sriov_add_vfs(dev, initial); for (i = 0; i < num_vfs; i++) //逐个初始化,add VFs rc = pci_iov_add_virtfn(dev, i); //和pcie初始化的流程基本一致,分配填充结构体,初始化设备,add 设备 virtfn_add_bus(dev->bus, pci_iov_virtfn_bus(dev, id)); virtfn = pci_alloc_dev(bus); virtfn->devfn = pci_iov_virtfn_devfn(dev, id); virtfn->vendor = dev->vendor; virtfn->device = iov->vf_device; virtfn->is_virtfn = 1; virtfn->physfn = pci_dev_get(dev); if (id == 0) pci_read_vf_config_common(virtfn); pci_setup_device(virtfn); //这两个函数就是按照capability初始化设备 中间省掉获取bar空间的代码,从PF上的SR-IVO的bar空间获取 pci_device_add(virtfn, virtfn->bus); sprintf(buf, "virtfn%u", id); rc = sysfs_create_link(&dev->dev.kobj, &virtfn->dev.kobj, buf); rc = sysfs_create_link(&virtfn->dev.kobj, &dev->dev.kobj, "physfn"); kobject_uevent(&virtfn->dev.kobj, KOBJ_CHANGE); pci_bus_add_device(virtfn) kobject_uevent(&dev->dev.kobj, KOBJ_CHANGE);