Linux虚拟化之mdev框架
引言
sriov是设备虚拟化的一种很好的实现方式,但是它也有一些问题:
- 需要设备硬件去实现相关的虚拟功能号等逻辑;
- 需要PCI配置搓开虚拟设备的bus号和设备号,这样导致总线资源不够;
- 不支持热迁移。
mdev提供了一种不用硬件设备提供虚拟功能相关实现,由软件去模拟虚拟设备的机制。linux中mdev框架是结合vfio实现的。
注:后述源码mdev框架结合mtty为例讲解
1 用户接口
首先,通过uuid工具生成一组序列,假设为:53b2f4fa-80af-632a-3a1e-b6bfe1fa1221。
- 执行如下命令创建mtty上面对应的虚拟设备
echo "53b2f4fa-80af-632a-3a1e-b6bfe1fa1221" > /sys/devices/virtual/mtty/mtty/mdev_supported_types/mtty-2/create
- 打开/etc/libvirt/qemu/ubuntu20.04-01.xml在devices节点追加如下子节点:
<hostdev mode='subsystem' type='mdev' managed='no' model='vfio-pci' display='off'>
<source>
<address uuid='53b2f4fa-80af-632a-3a1e-b6bfe1fa1221'/>
</source>
</hostdev>
最后,就成功将创建的虚拟设备配置给虚拟机
2 内核实现
在mtty_dev_init函数中,创建了一个mtty的字符设备mtty_dev.dev;然后再将该设备通过函数mdev_register_device注册到mdev框架中,其回调为struct mdev_parent_ops mdev_fops。该回调实现了将虚拟设备模拟为PCI设备。mdev_fops定义如下:
// dev_attr_groups定义
static DEVICE_ATTR_RO(sample_mtty_dev);
static struct attribute *mtty_dev_attrs[] = {
&dev_attr_sample_mtty_dev.attr,
NULL,
};
static const struct attribute_group mtty_dev_group = {
.name = "mtty_dev",
.attrs = mtty_dev_attrs,
};
static const struct attribute_group *mtty_dev_groups[] = {
&mtty_dev_group,
NULL,
};
// mdev_attr_groups定义
static DEVICE_ATTR_RO(sample_mdev_dev);
static struct attribute *mdev_dev_attrs[] = {
&dev_attr_sample_mdev_dev.attr,
NULL,
};
static const struct attribute_group mdev_dev_group = {
.name = "vendor",
.attrs = mdev_dev_attrs,
};
static const struct attribute_group *mdev_dev_groups[] = {
&mdev_dev_group,
NULL,
};
// supported_type_groups定义
static MDEV_TYPE_ATTR_RO(name);
static MDEV_TYPE_ATTR_RO(available_instances);
static MDEV_TYPE_ATTR_RO(device_api);
static struct attribute_group mdev_type_group1 = {
.name = "1",
.attrs = mdev_types_attrs,
};
static struct attribute_group mdev_type_group2 = {
.name = "2",
.attrs = mdev_types_attrs,
};
static struct attribute_group *mdev_type_groups[] = {
&mdev_type_group1,
&mdev_type_group2,
NULL,
};
// mdev设备回调
static const struct mdev_parent_ops mdev_fops = {
.owner = THIS_MODULE,
.dev_attr_groups = mtty_dev_groups,
.mdev_attr_groups = mdev_dev_groups,
.supported_type_groups = mdev_type_groups,
.create = mtty_create,
.remove = mtty_remove,
.open = mtty_open,
.release = mtty_close,
.read = mtty_read,
.write = mtty_write,
.ioctl = mtty_ioctl,
};
注:mtty_*系列回调实现了pci配置空间、BAR空间的模拟
2.1 mdev框架初始化
在vfio_mdev_init函数中,直接通过mdev_register_driver注册了一个设备驱动vfio_mdev_driver;在mdev_register_driver内部,将vfio_mdev_driver.driver.bus设置为mdev_bus_type。
- 首先,mdev_bus_type匹配到一个mdev设备的时候(也就是用户层通过create文件生成设备的时候),就会创建一个mdev设备,并调用mdev_bus_type.probe回调;mdev设备移除的时候也会调用mdev_bus_type.remove;
- mdev_bus_type的probe和remove回调为mdev_probe和mdev_remove。前者是将mdev设备attach到IOMMU后,调用vfio_mdev_driver.probe;后者是前者的反操作,也是会调用vfio_mdev_driver.remove。
2.2 mdev父设备注册
需要明确,mdev_register_device是对host端的真实设备的注册、而非对mdev设备的注册。在mdev_register_device函数内:
- 首先,基于真实设备dev创建struct mdev_parent *parent,本质上就是parent->dev指向dev;
- 通过函数parent_create_sysfs_files在设备目录下创建mdev_supported_types文件夹:
通过parent_create_sysfs_files->add_mdev_supported_type_groups->add_mdev_supported_type->kobject_init_and_add调用链在mdev_supported_types目录下创建mdev虚拟设备组目录mtty-1和mtty-2;
同时通过parent_create_sysfs_files->add_mdev_supported_type_groups->add_mdev_supported_type->sysfs_create_file调用链在mtty-1和mtty-2目录下创建create文件;
同时通过parent_create_sysfs_files->add_mdev_supported_type_groups->add_mdev_supported_type->kobject_create_and_add调用链在mtty-1和mtty-2目录下创建devices目录;
2.3 mdev设备创建
当执行echo uuid > /sys/devices/virtual/mtty/mtty/mdev_supported_types/mtty-2/create时,在回调中会调用mdev_device_create实现mdev设备创建:
- 首先,基于真实设备(或者说父设备)创建虚拟设备mdev;
- 调用父设备注册的create方法;
- 调用device_add将mdev注册,会触发总线probe方法,依次执行mdev_probe->vfio_mdev_probe->vfio_add_group_dev;
- 在vfio_add_group_dev将mdev注册到vfio框架中,设备回调为struct vfio_device_ops vfio_mdev_dev_ops函数集;
- 通过函数mdev_create_sysfs_files创建mdev设备的remove回调,会在sys目录的mdev设备目录创建remove文件接口。
注:vfio_mdev_dev_ops各个函数集是对父设备注册的函数集合的调用包装。