问题描述
qemu 5.1.0 版本 kvm 虚拟化环境中使用 virtio 网卡时,dpdk 程序初始化打印了如下信息后卡住:
..............................................
EAL: PCI device 0000:00:04.0 on NUMA socket -1
EAL: probe driver: 1af4:1000 rte_virtio_pmd
EAL: PCI memory mapped at 0x400148000000
EAL: PCI memory mapped at 0x400148001000
gdb 查看到如下堆栈信息:
(gdb) bt
#0 0x00007ffff75faec4 in modern_set_status () from /lib64/libdpdk.so
#1 0x00007ffff75fb4cd in vtpci_reset () from /lib64/libdpdk.so
#2 0x00007ffff75ff534 in virtio_init_device () from /lib64/libdpdk.so
#3 0x00007ffff75ffe86 in eth_virtio_dev_init () from /lib64/libdpdk.so
..........................................................................,....
堆栈信息表明程序是在 modern_set_status 函数中卡住了,具体原因并不清楚。
信息收集
内核版本信息
3.16.35
qemu 版本
[root@localhost ~]# /usr/bin/qemu-system-x86_64 --version
QEMU emulator version 5.1.0
接口驱动绑定信息
Network devices using DPDK-compatible driver
============================================
0000:00:04.0 'Virtio network device' drv=igb_uio unused=uio_pci_generic
0000:00:05.0 'Virtio network device' drv=igb_uio unused=uio_pci_generic
网卡正常绑定到 igb_uio 驱动上。
接口 lspci 信息
00:04.0 0200: 1af4:1000
Subsystem: 1af4:0001
Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR+ FastB2B- DisINTx+
Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
Latency: 0
Interrupt: pin A routed to IRQ 11
Region 0: I/O ports at c180 [size=32]
Region 1: Memory at febd2000 (32-bit, non-prefetchable) [size=4K]
Region 4: Memory at fe000000 (64-bit, prefetchable) [size=16K]
Expansion ROM at feb40000 [disabled] [size=256K]
Capabilities: [98] MSI-X: Enable+ Count=3 Masked-
Vector table: BAR=1 offset=00000000
PBA: BAR=1 offset=00000800
Capabilities: [84] Vendor Specific Information: VirtIO: <unknown>
BAR=0 offset=00000000 size=00000000
Capabilities: [70] Vendor Specific Information: VirtIO: Notify
BAR=4 offset=00003000 size=00001000 multiplier=00000004
Capabilities: [60] Vendor Specific Information: VirtIO: DeviceCfg
BAR=4 offset=00002000 size=00001000
Capabilities: [50] Vendor Specific Information: VirtIO: ISR
BAR=4 offset=00001000 size=00001000
Capabilities: [40] Vendor Specific Information: VirtIO: CommonCfg
BAR=4 offset=00000000 size=00001000
Kernel driver in use: igb_uio
lspci 信息未见明显异常。
程序 pmap 信息
程序映射的 resource 信息如下:
0000600009e00000 4K rw-s- /sys/devices/pci0000:00/0000:00:04.0/resource1
0000600009e01000 16K rw-s- /sys/devices/pci0000:00/0000:00:04.0/resource4
对照测试
dpdk 示例程序 l2fwd 测试
测试结论:
同样卡住,堆栈与产品程序 一致,日志记录如下:
(gdb) bt
#0 0x00000000004f1184 in modern_set_status ()
#1 0x00000000004f178d in vtpci_reset ()
#2 0x00000000004f57f4 in virtio_init_device ()
#3 0x00000000004f6146 in eth_virtio_dev_init ()
...................................................
高版本 l2fwd 程序测试
编译 dpdk-19.11 版本 l2fwd 测试,现象相同。
官方驱动测试
能够正常绑定到 virtio-pci 驱动中,表明问题出在 dpdk 驱动上。
排查内容
1. dmesg 信息
虚拟机 dmesg 信息未见异常,宿主机 dmesg 信息未见异常。
2. 关闭 iommu
关闭后问题仍旧存在
源码分析
modern_set_status 函数源码如下:
static void
modern_set_status(struct virtio_hw *hw, uint8_t status)
{
io_write8(status, &hw->common_cfg->device_status);
}
它的功能是写入 virtio 网卡寄存器,设定虚拟网卡状态。执行 io_write8 函数写入的时候卡住表明问题出在虚拟网卡 pci resource 空间访问上,确认代码逻辑中此处为第一次访问网卡寄存器,表明映射的虚拟网卡寄存器空间不可访问,怀疑问题可能与 qemu 版本有关。
卡住的现象到底是怎样的?
gdb 查看卡住是的寄存器信息与函数返汇编代码如下:
(gdb) info registers
rax 0x600009e01000 105553281945600
rbx 0x60000501f100 105553200279808
rcx 0x0 0
rdx 0x3 3
rsi 0x0 0
rdi 0x60000501f100 105553200279808
rbp 0x29cb1f0 0x29cb1f0
rsp 0x7fffffffdd48 0x7fffffffdd48
r8 0x1 1
r9 0xfeff7efdff336462 -72199439641254814
r10 0x7fffffffdb20 140737488345888
r11 0x7ffff75fb4c0 140737343632576
r12 0x29cb1f0 43823600
r13 0x60000501f1c0 105553200280000
r14 0x60000501f100 105553200279808
r15 0x74fcd0 7666896
rip 0x7ffff75faec4 0x7ffff75faec4 <modern_set_status+4>
eflags 0x3246 [ PF ZF IF #12 #13 ]
cs 0x33 51
ss 0x2b 43
....................................................................
(gdb) disass
Dump of assembler code for function modern_set_status:
0x00007ffff75faec0 <+0>: mov 0x40(%rdi),%rax
=> 0x00007ffff75faec4 <+4>: mov %sil,0x14(%rax)
0x00007ffff75faec8 <+8>: retq
End of assembler dump.
可以确定是在访问 resource4 virtio bar 空间的时候卡住,仅仅影响当前进程,其它进程仍旧能够正常运行。有这个现象,我比较怀疑是 qemu 本身的问题,但是解释不了为什么官方驱动能够正常工作,还需要继续定位。
思考
内核驱动与 dpdk 驱动访问网卡的 pci resource bar 空间都需要经过地址映射,dpdk 中通过 mmap resource 文件来映射地址,这个操作我判断大概率不会存在差别,毕竟 resource 文件也是内核探测 pci 设备的时候配置的,出问题的概率非常小。
从现象上来看比较像是在用户态无法访问,但是一般来说在虚拟机中访问 virtio resource bar 空间在 qemu 中的行为应当是一致的,不应该出现内核能访问,用户态程序无法访问的问题,毕竟 qemu 并不区分虚拟机中的内核与用户态程序,而且其实内核与用户态程序只是分时执行代码,qemu 侧不应该有这个区别。
阅读内核代码
由于我对内核 virtio-pci、virtio-net 这部分代码的实现并不清楚,既然内核代码能够工作,那需要先研究下内核代码的逻辑,一定是这个过程与 dpdk 驱动的执行过程有区别才造成了不同的结果。
阅读 3.16.35 内核的代码,发现它的 virtio-pci 驱动并不支持 virtio modern,只支持 virtio legacy 这种访问方式。
在这种访问方式下,它设置 virtio 网卡 status 寄存器使用如下代码:
static void vp_reset(struct virtio_device *vdev)
{
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
/* 0 status means a reset. */
iowrite8(0, vp_dev->ioaddr + VIRTIO_PCI_STATUS);
/* Flush out the status write, and flush in device writes,
* including MSi-X interrupts, if any. */
ioread8(vp_dev->ioaddr + VIRTIO_PCI_STATUS);
/* Flush pending VQ/configuration callbacks. */
vp_synchronize_vectors(vdev);
}
而我们使用的 dpdk virtio 驱动支持 virtio modern 设备,reset virtio 网卡通过读写 virtio resource bar4 中 CommonCfg 处的空间来进行 virtio 网卡的 reset。
修改 dpdk virtio 驱动,使用 legacy 模式访问 virtio 网卡寄存器
将 vtpci_init 函数修改为如下逻辑:
#if 0
if (virtio_read_caps(dev, hw) == 0) {
PMD_INIT_LOG(INFO, "modern virtio pci detected.");
hw->vtpci_ops = &modern_ops;
hw->modern = 1;
*dev_flags |= RTE_ETH_DEV_INTR_LSC;
return 0;
}
#endif
PMD_INIT_LOG(INFO, "trying with legacy virtio pci.");
if (legacy_virtio_resource_init(dev, hw, dev_flags) < 0) {
if (dev->kdrv == RTE_KDRV_UNKNOWN) {
PMD_INIT_LOG(INFO,
"skip kernel managed virtio device.");
return 1;
}
return -1;
}
临时注掉了使用 virtio modern pci ops 的逻辑,改为使用 legacy pci ops,重新编译 l2fwd 能够正常初始化。
l2fwd 运行打印了如下与 virtio 驱动相关的信息:
EAL: PCI device 0000:00:04.0 on NUMA socket -1
EAL: probe driver: 1af4:1000 rte_virtio_pmd
EAL: PCI Port IO found start=0xc180
EAL: PCI device 0000:00:05.0 on NUMA socket -1
EAL: probe driver: 1af4:1000 rte_virtio_pmd
EAL: PCI Port IO found start=0xc1a0
dpdk 驱动对 virtio modern 设备会先尝试使用 modern pci ops 的方法读写网卡寄存器,当尝试失败后再继续尝试 legacy pci ops,这个顺序并不能调整,如果将 legacy pci ops 尝试放到前面,那一些 virtio modern 网卡就不能使用 modern pci ops 来读写网卡寄存器,性能会下降。
从现象上表明问题出在 qemu 上,可能是某种兼容性问题,可以尝试通过升级 qemu 版本解决。
能否修改 qemu 启动参数指定使用 virtio-legacy 设备?
异常 kvm VM qemu 启动参数中 virtio 网卡相关内容如下:
-device virtio-net-pci,netdev=hostnet1,id=net1,mac=52:54:00:5e:90:1a,bus=pci.0,addr=0x4 -netdev tap,fd=48,id=hostnet2,vhost=on,vhostfd=49 -device virtio-net-pci,netdev=hostnet2,id=net2,mac=52:54:00:af:a8:c9,bus=pci.0,addr=0x5
配置上没有发现有啥异常,网上搜索到可以设置 disable-modern=‘off’ 参数,但是我们的虚拟机配置是通过 virt-manager 生成的,一通搜索与尝试确定 libvirtio 不支持强制设置 virtio 设备为 legacy 模式。