高版本内核热拔virtio设备Oops异常

本文介绍如何处理在部分高版本内核的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函数指针的赋值。image.png

  • 在热拔virtio设备时,代码会检查当前队列是否为Admin Virtqueue。

    image.png

但是上游社区忽略了一个问题:如果该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函数指针是否为空的情况进行了判断,修复了该问题。

  • 操作系统

  • 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。更多信息,请参见实例规格族

  • 方案二

    1. 升级最新内核软件包,并确认最新软件包是否已合入is_avq函数指针判断的补丁包:virtio-pci: Check if is_avq is NULL

    2. (条件必选)如果最新的内核中没有合入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寄存器会指向引发异常的那条指令的地址。