临清网站优化,陕西建设执业中心网站,做网站用什么cms,滑县住房城乡建设厅门户网站文章目录 问题现象定位过程日志分析源端目的端 原理分析基本原理上下文分析复现分析patch分析 总结解决方案 问题现象
集群升级虚拟化组件版本#xff0c;升级前存量运行并挂载了virtio磁盘的虚拟机集群内热迁移到升级后的节点失败#xff0c;QEMU报错如下#xff1a;
202… 文章目录 问题现象定位过程日志分析源端目的端 原理分析基本原理上下文分析复现分析patch分析 总结解决方案 问题现象
集群升级虚拟化组件版本升级前存量运行并挂载了virtio磁盘的虚拟机集群内热迁移到升级后的节点失败QEMU报错如下
2023-09-15T04:52:39.221053Z qemu-kvm: get_pci_config_device: Bad config data: i0x10 read: c1 device: 1 cmask: ff wmask: 80 w1cmask:0
2023-09-15T04:52:39.221140Z qemu-kvm: Failed to load PCIDevice:config
2023-09-15T04:52:39.221148Z qemu-kvm: Failed to load virtio-blk:virtio
2023-09-15T04:52:39.221154Z qemu-kvm: error while loading state for instance 0x0 of device 0000:00:0b.0/virtio-blk定位过程
通过“Failed to load”关键字可以确认迁移目的端报错且该日志为目的端虚机启动是Qemu进程日志通过日志关键字“Bad config data”搜索集群其它节点是否有相同报错的虚机搜索到另一个虚机迁移失败有相同报错问题有机率复现。
日志分析
源端
选取其中一个问题虚机查看源端虚机启动和热迁移发起的时间
/* 虚机启动时间 */
2023-09-15 02:14:55.2530000: starting up libvirt version:xxx
...
/* 虚机启动时pci号最大的一块virtio磁盘 */
-device virtio-blk-pci,scsioff,buspci.0,addr0x9,drivedrive-virtio-disk0,idvirtio-disk0,bootindex1,write-cacheon \
...
/* 虚机启动时pci号最大的设备 */
-device virtio-balloon-pci,idballoon0,buspci.0,addr0xa \
/* 虚机第一次迁移时间 */
2023-09-15 04:52:30.3830000: initiating migration
/* 虚机第二次迁移时间 */
2023-09-15 05:46:23.2780000: initiating migration从界面事件日志看虚机在10:14被克隆后启动运行Qemu日志比前端界面时间早8小时启动时有一个磁盘ID为virtio-disk0pci的bdf号为0:9.0启动后10:15界面显示立即热添加了一块磁盘。QEMU日志无记录。虚拟机分别在12:52和13:46发起热迁移都失败对应QEMU日志
2023-09-15 04:52:30.3830000: initiating migration
2023-09-15 05:46:23.2780000: initiating migration目的端
目的端虚机第一次启动时间
2023-09-15 04:52:30.0700000: starting up libvirt version:xxx
...
/* 源端启动时挂载的virtio磁盘*/
-device virtio-blk-pci,scsioff,buspci.0,addr0x9,drivedrive-virtio-disk0,idvirtio-disk0,bootindex1,write-cacheon \
...
/* 源端启动后热添加的virtio磁盘 */
-device virtio-blk-pci,scsioff,buspci.0,addr0xb,drivedrive-virtio-disk1,idvirtio-disk1,bootindex4,write-cacheon \
...
/* 源端启动后热添加的virtio网卡 */
-device virtio-net-pci,mqon,vectors10,rx_queue_size1024,netdevhostnet1,idnet1,mac52:54:00:6a:ef:94,buspci.0,addr0xc \
/* 热迁移目的端启动的虚机命令行中会增加-incoming defer参数 */
/* 表示虚机内存的读取通过启动后的migrate_incoming qmp 命令指定 */
-incoming defer \
-device virtio-balloon-pci,idballoon0,buspci.0,addr0xa \
/* 第一次迁移报错 */
2023-09-15T04:52:39.221053Z qemu-kvm: get_pci_config_device: Bad config data: i0x10 read: c1 device: 1 cmask: ff wmask: 80 w1cmask:0
2023-09-15T04:52:39.221140Z qemu-kvm: Failed to load PCIDevice:config
2023-09-15T04:52:39.221148Z qemu-kvm: Failed to load virtio-blk:virtio
2023-09-15T04:52:39.221154Z qemu-kvm: error while loading state for instance 0x0 of device 0000:00:0b.0/virtio-blk
...第一次迁移失败时间点2023-09-15 04:52目的端报错
get_pci_config_device: Bad config data: i0x10 read: c1 device: 1 cmask: ff wmask: 80 w1cmask:0第二次迁移失败时间点2023-09-15 05:46目的端报相同错误。另外目的端QEMU启动命令行比源端多出两个-device分别是热添加的virtio-disk1磁盘和net1网卡bdf号分别是00:b.0,00:c.0。
原理分析
通过上面的日志我们仅知道迁移报错了报错的关键函数是get_pci_config_device这个函数在迁移中起什么作用呢为什么会有pci配置空间的报错这个报错涉及哪些基本原理呢下面我们简单分析关于virtio-pci设备的基本原理。
基本原理
pci配置空间布局 pci规范定义pci配置空间长度为256byte其中通用头部为64byte也称为预定义空间通用头部的前16byte格式如下之后就是bar空间及其它内容偏移0x5字节定义了status字段其中有1bit定义为Capabilities List它是pci规范定义的附加空间标志位Capabilities List的意义是允许在pci设备配置空间之后加上额外的寄存器这些寄存器由Capability List组织起来用来实现特定的功能virtio-pci基于该特性实现各类设备附加空间在64字节配置空间之后该bit为1表示在capabilities pointer偏移处0x34存放了附加寄存器组的起始偏移。 virtio-pci配置空间布局 virtio-pci通过capabilities list存放规范中定义的数据结构结构如下 list由若干元素连接而成每个元素的头三个字节有通用的格式第1 byte为capability ID表示实现了何种capability对于virtio-blk其ID为0x90; 第2 byte为list中下一个元素的偏移如果list结束第2 byte为0第3 byte为元素的长度。以virtio-blk为例每个元素格式如下
/* This is the PCI capability header: */
struct virtio_pci_cap {__u8 cap_vndr; /* Generic PCI field: PCI_CAP_ID_VNDR */__u8 cap_next; /* Generic PCI field: next ptr. */__u8 cap_len; /* Generic PCI field: capability length */__u8 cfg_type; /* Identifies the structure. */__u8 bar; /* Where to find it. */__u8 id; /* Multiple capabilities of the same type */__u8 padding[2]; /* Pad to full dword. */__le32 offset; /* Offset within bar. */__le32 length; /* Length of the structure, in bytes. */
};可以看到virtio-pci规范也遵循了pci的规范从第四个字节开始为virtio-blk特有内容其中cfg_type用于标识virtio-pci定义的数据结构类型定义如下
/* Common configuration */
#define VIRTIO_PCI_CAP_COMMON_CFG 1
/* Notifications */
#define VIRTIO_PCI_CAP_NOTIFY_CFG 2
/* ISR Status */
#define VIRTIO_PCI_CAP_ISR_CFG 3
/* Device specific configuration */
#define VIRTIO_PCI_CAP_DEVICE_CFG 4
/* PCI configuration access */
#define VIRTIO_PCI_CAP_PCI_CFG 5
/* Shared memory region */
#define VIRTIO_PCI_CAP_SHARED_MEMORY_CFG 8
/* Vendor-specific data */
#define VIRTIO_PCI_CAP_VENDOR_CFG 9整个virtio-blk配置空间内容如下 上面是virtio pci设备的简单介绍完整的分析可以参考VirtIO实现原理——PCI基础
上下文分析
了解基础的virtio pci设备配置空间后继续分析迁移报错的出错上下文
static int get_pci_config_device(QEMUFile *f, void *pv, size_t size,VMStateField *field)
{PCIDevice *s container_of(pv, PCIDevice, config);PCIDeviceClass *pc PCI_DEVICE_GET_CLASS(s);uint8_t *config; /* 1byte的指针 */int i;assert(size pci_config_size(s));config g_malloc(size); /* 分配0x100256字节内存用来存放pci的配置空间信息 */qemu_get_buffer(f, config, size); /* 从迁移流中读取256字节的pci配置空间内容*/for (i 0; i size; i) { /* 按字节移动逐一比较配置空间内容 */if ((config[i] ^ s-config[i]) /* 如果配置空间不相同报错 */s-cmask[i] ~s-wmask[i] ~s-w1cmask[i]) {error_report(%s: Bad config data: i0x%x read: %x device: %x cmask: %x wmask: %x w1cmask:%x, __func__,i, config[i], s-config[i],s-cmask[i], s-wmask[i], s-w1cmask[i]);g_free(config);return -EINVAL;}}memcpy(s-config, config, size);......
}get_pci_config_device实现目的端加载pci设备配置空间从迁移的流中读取源端传来的pci设备配置空间内容。再比较本地QEMU实例化pci设备得到的pci空间内容如果两个值不同说明源端pci设备的配置空间内容和目标端初始化的pci设备配置空间内容不同通常该问题是由于迁移两端的同一个virtio-pci设备有不同的配置导致比如virtio队列数配置不同会报类似错误
get_pci_config_device: Bad config data: i0x9a read: 1 device: 2 cmask: ff wmask: 0 w1cmask:0报错原因是从源端读取到的virtio队列数为1但目标端初始化队列数为2。其中i0x90表示读取内容在pci配置空间总长256byte的偏移。分析本次报错
get_pci_config_device: Bad config data: i0x10 read: c1 device: 1 cmask: ff wmask: 80 w1cmask:0目的端在配置空间偏移0x10的地方读取了一字节的数据源端内容为0xc1目的端内容为0x1高4bit的内容不同源端为0xc目的端为0x0。pci配置空间0x10及BAR0的内容参考基本原理一节因此进一步确定是迁移两端的同一个virtio-pci设备BAR0内容不同导致的报错。BAR0中存放的是什么地址呢 BAR0映射了1个virtio-pci规范定义的IO空间该IO空间用于实现对virtio设备配置空间访问的一个可选方法IO空间格式如下
struct virtio_pci_cfg_cap {struct virtio_pci_cap cap;u8 pci_cfg_data[4]; /* Data for BAR access. */
};当Guest驱动想访问某个virtio-blk配置空间的某个区域common configuration, notification, ISR and device-specific configuration时首先获取要访问的bar号(cap.bar)长度cap.length和偏移cap.offset将其设置到cap中其它字段: cap.cap_vndr:0x9, cap.cap_next:70,cap._cap_len:14,cap.cfg_type: 05与capabilies list中的VIRTIO_PCI_CAP_PCI_CFG元素相同实际动作是往BAR0中记录的IO地址写上述内容QEMU会将对应的信息放到pci_cfg_data中供Guest驱动读取。通过这样的方式Guest驱动可以实现对virtio-blk配置空间数据的访问。从上可知迁移的virtio设备由于源端上BAR0地址的bit[4,7]内容为0xc目的端BAR0地址的bit[4,7]内容为0分析目的端日志 2023-09-15T04:52:39.221154Z qemu-kvm: error while loading state for instance 0x0 of device ‘0000:00:0b.0/virtio-blk’ 迁移的设备是1个磁盘设备其bdf号为00:0b.0对比源端的virtio-pci设备
-device virtio-blk-pci,scsioff,buspci.0,addr0x9,drivedrive-virtio-disk0,idvirtio-disk0,bootindex1,write-cacheon
-device virtio-balloon-pci,idballoon0,buspci.0,addr0xa第1个磁盘的pci号为00:09.0随后是virtio-balloon-pci设备分配到00:0a.0pci bdf为00:0b.0的设备在QEMU源端日志的启动命令行中并没有出现因此只可能是热插拔的设备分配到了该bdf号从界面看虚机启动后有热添加设备事件首先是热添加磁盘之后是热添加网卡。假定pci号按顺序被分配通常如此则热添加的virtio磁盘分配到的bdf号为00:0b.0热添加的virtio网卡分配到的bdf号为00:0c.0。进一步描述迁移失败过程是在旧版本环境中通过克隆创建的虚机磁盘热添加后热迁移到升级后版本的节点报错。按照该方法可以稳定复现该问题。
复现分析
查看复现的虚机PCI设备空间布局
virsh qemu-monitor-command {vm_uuid} --hmp info pci
...Bus 0, device 9, function 0:SCSI controller: PCI device 1af4:1001IRQ 0.BAR0: I/O at 0xd080 [0xd0bf].BAR1: 32 bit memory at 0xfea59000 [0xfea59fff].BAR4: 64 bit prefetchable memory at 0xfe208000 [0xfe20bfff].id virtio-disk0Bus 0, device 10, function 0:Class 0255: PCI device 1af4:1002IRQ 10.BAR0: I/O at 0xd100 [0xd11f].BAR4: 64 bit prefetchable memory at 0xfe20c000 [0xfe20ffff].id balloon0Bus 0, device 11, function 0:SCSI controller: PCI device 1af4:1001IRQ 0.BAR0: I/O at 0xffc0 [0xffff].BAR1: 32 bit memory at 0xfebff000 [0xfebfffff].BAR4: 64 bit prefetchable memory at 0x4287fffc000 [0x4287fffffff].id virtio-disk1
...可以看到第一个磁盘的BAR0空间bit[4,7]内容为0xc与QEMU日志报错信息匹配
get_pci_config_device: Bad config data: i0x10 read: c1 device: 1 cmask: ff wmask: 80 w1cmask:0源端选取一个相同硬件配置的虚机查看第二个virtio磁盘的pci信息BAR0地址相同 Bus 0, device 11, function 0:SCSI controller: PCI device 1af4:1001IRQ 0.BAR0: I/O at 0xffc0 [0xffff].BAR1: 32 bit memory at 0xfebff000 [0xfebfffff].BAR4: 64 bit prefetchable memory at 0x4287fffc000 [0x4287fffffff].id virtio-disk1冷重启该虚机为保证QEMU分配的pci号不变以下面的步骤冷重启该虚机
virsh dumpxml {vm_uuid} {vm_uuid}.xml
virsh destroy {vm_uuid}
virsh undefine {vm_uuid}
virsh define {vm_uuid}.xml
virsh start {vm_uuid}查看冷重启后虚机的第2块盘的pci信息如下 Bus 0, device 11, function 0:SCSI controller: PCI device 1af4:1001IRQ 0.BAR0: I/O at 0xd080 [0xd0ff].BAR1: 32 bit memory at 0xfea9a000 [0xfea9afff].BAR4: 64 bit prefetchable memory at 0xfe210000 [0xfe213fff].id virtio-disk1两个版本设备的IO空间的确发生了变化。新版本BAR0的IO空间边长。对比存量运行虚机和冷重启后虚机设备的IO空间冷重启后的虚机磁盘的BAR0 IO空间从3f扩大为7f。
patch分析
分析新版本引入的特性中与virtio-blk设备相关的patch只有TRIM/UNMAP特性社区patch如下
37b06f8d46 virtio-blk: add DISCARD and WRITE_ZEROES features
20764be042 virtio-blk: set config size depending on the features enabled
ba550851f5 virtio-net: make VirtIOFeature usable for other virtio devices
5c81161f80 virtio-blk: add discard and write-zeroes properties
bbe8bd4d85 virtio-blk: add host_features field in VirtIOBlock
00f639fb8f virtio-blk: add acct_failed param to virtio_blk_handle_rw_error该commit是vhost-user-blk实现TRIM/UNMAP的核心修改为什么这个commit会导致virito-blk的BAR0 IO空间变长因为这个commit需要定义virtio spec要求的discard/write_zeroes相关参数因此扩展了virtio-blk的配置空间配置空间原来提供的内容以下数据结构:
struct virtio_blk_config {/* The capacity (in 512-byte sectors). */uint64_t capacity;/* The maximum segment size (if VIRTIO_BLK_F_SIZE_MAX) */uint32_t size_max;/* The maximum number of segments (if VIRTIO_BLK_F_SEG_MAX) */uint32_t seg_max;/* geometry of the device (if VIRTIO_BLK_F_GEOMETRY) */struct virtio_blk_geometry {uint16_t cylinders;uint8_t heads;uint8_t sectors;} geometry;/* block size of device (if VIRTIO_BLK_F_BLK_SIZE) */uint32_t blk_size;/* the next 4 entries are guarded by VIRTIO_BLK_F_TOPOLOGY *//* exponent for physical block per logical block. */uint8_t physical_block_exp;/* alignment offset in logical blocks. */uint8_t alignment_offset;/* minimum I/O size without performance penalty in logical blocks. */uint16_t min_io_size;/* optimal sustained I/O size in logical blocks. */uint32_t opt_io_size;/* writeback mode (if VIRTIO_BLK_F_CONFIG_WCE) */uint8_t wce;uint8_t unused;/* number of vqs, only available when VIRTIO_BLK_F_MQ is set */uint16_t num_queues;
}该数据结构总长度为36字节再加上struct virtio_pci_cap的长度16字节总计52字节。IO空间为64字节可以满足IO访问要求。当引入discard/write_zeroes之后数据结构struct virtio_blk_config新增了以下字段 /* the next 3 entries are guarded by VIRTIO_BLK_F_DISCARD *//** The maximum discard sectors (in 512-byte sectors) for* one segment.*/uint32_t max_discard_sectors;/** The maximum number of discard segments in a* discard command.*/uint32_t max_discard_seg;/* Discard commands must be aligned to this number of sectors. */uint32_t discard_sector_alignment;/* the next 3 entries are guarded by VIRTIO_BLK_F_WRITE_ZEROES *//** The maximum number of write zeroes sectors (in 512-byte sectors) in* one segment.*/uint32_t max_write_zeroes_sectors;/** The maximum number of segments in a write zeroes* command.*/uint32_t max_write_zeroes_seg;/** Set if a VIRTIO_BLK_T_WRITE_ZEROES request may result in the* deallocation of one or more of the sectors.*/uint8_t write_zeroes_may_unmap;uint8_t unused1[3];总计增加了24字节。因此IO空间至少需要5224 76字节才能满足访问要求。QEMU按照64字节对齐如果新版本按照开源的策略默认打开discard特性在初始化virtio-blk设备时会将IO空间扩展为128字节配置空间会变长
virtio_init(vdev, virtio-blk, VIRTIO_ID_BLOCK, sizeof(struct virtio_blk_config));总结
TRIM/UNMAP特性的引入使高版本QEMU在模拟virtio-blk设备时PCI配置空间变长。会导致迁移是目的端加载设备状态报错。QEMU virtio设备在支持新特性时目的端除了检查两端源端的feature是否是目的端的子集还会检查virtio-pci配置空间内容
如果新特性仅仅引入feature bit前端如果不支持在前后端协商时该feature bit不会被置位。因此热迁移时就算目的端默认开启新特性也不会在热迁移过程中进行协商最终目的端feature也不会包含该特性。只有冷重启后才能生效。这种情况不存在热迁移兼容性问题。如果新特性不仅引入feature bit还对virtio-pci规范中定义的配置空间数据结构进行了扩展从而引起配置空间变化比如这里的TRIM/UNMAP特性则会存在热迁移兼容性问题。
解决方案
QEMU virtio-pci设备在初始化pci空间时应该根据feature是否使能来动态计算PCI空间的长度并初始化只有使能该特性的feature才能将其涉及的数据结构计算到PCI空间的长度中否则不应该在配置空间提供该feature相关数据结构。社区在下面的commit实现了该逻辑
20764be042 virtio-blk: set config size depending on the features enabled控制面如Libvirt在涉及到平滑升级的处理时如果目的端有新增的feature且涉及virtio设备配置空间改变时应该在热迁移时显式关闭该特性从而保证热迁移源端和目的端配置空间相同。