本文介绍如何处理在部分高版本内核的ECS实例中热拔virtio设备时出现Oops异常的问题。
问题现象
在部分高版本内核的ECS实例中热拔virtio设备(例如磁盘、网卡)时,操作系统会发生如下Oops异常:
在配置了
kernel.panic_on_oops = 1
的实例中,会发生内核Panic异常。在配置了
kernel.panic_on_oops = 0
的实例中,会出现hung错误。
在Linux内核中,kernel.panic_on_oops
是一个内核参数,用于控制当内核遇到Oops(一种内核错误,通常包括访问无效内存地址时发生的情况)时的行为。
如果内核出现Panic异常,系统会立即停止运行当前的所有任务,保存一些调试信息,然后重启或者关机,有助于快速响应问题并减少潜在的损害。
如果内核出现hung错误,内核可能会尝试继续运行,在生产环境中不推荐该配置,因为可能会进一步导致数据损坏或其他严重损害。
问题原因
Linux上游社区在内核社区中添加了对virtio设备Admin Virtqueue的支持:commit详情。
在该commit中:
对virtio_pci_device的定义中添加了一个is_avq的函数指针,用于判断是否存在Admin Virtqueue。
在modern virtio设备的初始化函数virtio_pci_modern_probe中添加了对is_avq函数指针的赋值。
在热拔virtio设备时,代码会检查当前队列是否为Admin Virtqueue。
但是上游社区忽略了一个问题:如果该virtio设备不是一个modern virtio设备,而是一个legacy virtio设备时,并没有对is_avq函数指针进行赋值,导致legacy virtio设备中的virtio_pci_device结构体中的函数指针is_avq是一个空指针(NULL Pointer)。此时如果热拔virtio设备,调用到if (vp_dev->is_avq(vdev, vq->index))
代码时,CPU的RIP寄存器执行了一个空指针地址,从而触发了空指针异常,也就是尝试执行一个无效的内存地址,这是操作系统不允许的,会导致程序崩溃或系统错误。
影响范围
Linux上游社区
上游社区已经针对该问题进行了修复:commit详情。在该commit中,对is_avq函数指针是否为空的情况进行了判断,修复了该问题。
操作系统
Ubuntu 24
其他内核版本在6.8左右的操作系统,且这些操作系统合入了Admin Virtqueue能力(virtio-pci: Introduce admin virtqueue),但是内核没有修复is_avq函数指针判断问题(virtio-pci: Check if is_avq is NULL)。
说明您可以通过
uname -r
命令查询内核版本。
virtio设备
实例使用了legacy virtio设备,并在进行热拔操作。
解决方案
方案一:更换modern virtio设备的实例规格族。具体操作,请参见修改实例规格。
建议使用以下实例规格族,以下规格族是modern virtio设备,不受该问题影响。
ecs.c8i、ecs.g8i、ecs.r8i、ecs.hfg8i、ecs.hfc8i、ecs.hfr8i、ecs.g8ise、ecs.c8ae、ecs.g8ae、ecs.r8ae、ecs.c8a、ecs.g8a、ecs.r8a。更多信息,请参见实例规格族。
方案二
升级最新内核软件包,并确认最新软件包是否已合入is_avq函数指针判断的补丁包:virtio-pci: Check if is_avq is NULL。
(条件必选)如果最新的内核中没有合入is_avq函数指针判断的补丁包,请自行合入。
附录:名词解释
关于文档中涉及的virtio设备、Admin Virtqueue、virtio_pci_device等名词解释说明如下:
名词 | 说明 |
virtio设备 | virtio是一种标准化的虚拟化设备通信框架,允许虚拟机高效地与宿主机上的虚拟硬件设备交互。virtio设备是在虚拟化环境中模拟出来的硬件设备(如磁盘、网卡),分为传统legacy virtio和现代modern virtio两类,主要区别在于使用的配置接口不同。 |
Admin Virtqueue | 是一个特殊的virtio队列,用于执行设备的管理操作,比如获取设备状态、配置设备等。并非所有virtio设备都支持Admin Virtqueue。 |
virtio_pci_device | 是在内核中表示一个virtio PCI设备的数据结构,包含了指向各种功能函数的指针,其中之一就是新添加的is_avq函数指针,用于判断给定的队列是否为Admin Virtqueue。 |
is_avq | 该函数用于判断给定的virtio队列是否为Admin Virtqueue。 |
virtio_pci_modern_probe | 该函数负责virtio PCI设备的探测和初始化过程。在设备被系统发现后,此函数会被调用来完成设备的设置,包括配置空间的读取、设备特性的检测以及必要的资源分配等。 |
RIP寄存器 | RIP(Instruction Pointer Register)寄存器是x86架构CPU中的一个寄存器,存储了当前执行指令的下一条指令的地址。当程序发生异常,如尝试执行一个空指针所指向的地址时,RIP寄存器会指向引发异常的那条指令的地址。 |