Files

6.9 KiB
Raw Permalink Blame History

Hyper-V / VMBus:VSC·VSP / GPADL / SR-IOV 数据面切换

Hyper-V 的合成设备(synthetic device)不走"硬件 MMIO + 中断",而走 VMBus:
子分区:  VSC(guest 侧驱动,如 netvsc / storvsc)
            ↕  VMBus(双向通道 = 两个 ring buffer)
根分区:  VSP(宿主侧实现合成设备)+ 硬件

所以:

设备 I/O 看不到明显的硬件 MMIO
  → 可能只是合成设备流量走 VMBus

第二条:GPADL 不是内存地址——它是"描述并映射一片客户机缓冲区"的句柄。 </CORE RULE>

一、VMBus 的机制

  • VMBus 通过映射到多个分区的内存页实现——数据经总线传递不需要 hypervisor 拷贝,减少上下文切换
  • 一条通道是双向的,由两个 ring buffer 组成(进/出);高速设备(合成 SCSI、网卡)可用多条通道提高并行度
  • 在 Linux 侧它是总线模型(/sys/bus/vmbus),主要用户是合成设备:存储、网络、视频、工具服务

二、GPADL:客户机物理地址描述符列表

  • ring buffer 空间有限,性能关键路径用 GPADL:让根分区把额外的客户机内存页直接映射进自己的地址空间
  • ring buffer 本身是 guest 分配、由 guest 把 GPA 列表经 VMBus 控制路径告知宿主;更大量数据传输另用独立的 GPA 列表描述数据缓冲区
  • Hyper-V 对经 GPADL 共享的客户机内存总量有限制(Windows Server 2019+ 约 1280 MB,更早约 384 MB),防止恶意 guest 耗尽宿主资源

RE 含义:

GPADL = 37
  → 不是 GPA 0x25000
  → 是一个句柄,背后是"一片被描述并映射的客户机缓冲区"

三、SR-IOV:数据面会"中途换路"

带 SR-IOV 的网卡上:

  • 数据面(VF):从 VM 内的 VF 直接走硬件到网卡——不经过 VMBus,也不经过 hypervisor
  • 控制面:VF 的控制仍然经 VMBus 回到根分区的 PF 驱动

VF 拆除时的迁移顺序:

虚拟化栈通知 VPCI VSP(根分区管理 OS)
  → 经 VMBus 通知 guest 内的 VPCI VSC
  → NetVSC 从 VF 网卡驱动解绑
  → 子分区流量从 VF 数据面迁移到软件合成数据面
  → 后续:过滤规则搬到 PF 上的默认 VPort、删除 VF 的 VPort、请求函数级复位、释放 VF 资源

所以:

一开始 VMBus 很忙 → 后来包捕获里流量突然消失 → 网络却仍然正常

不一定是数据丢失——很可能已经切到 SR-IOV VF 直通路径(或反向迁回)。

四、GPADL 的删除会阻塞

  • 若该 GPADL 当前仍被 server 映射,删除调用会被阻塞,直到它被解除映射
  • 从 buffer 创建 GPADL 时,该 buffer 会被 probe 并锁定,直到 GPADL 被销毁
  • 所以 teardown 里"调用删除之后线程卡住"先查远端映射的生命周期,而不是马上判死锁

五、packet 的生命周期:回调返回 ≠ packet 结束

  • 完成调用可以在处理回调中立即调用,也可以稍后调用
  • 直到完成调用被发起,系统才会:清理未完成的映射、释放占用的缓冲区、在对端要求完成通知时发送完成 packet
  • 因此:
callback returned  ≠  packet lifetime ended

逆向资源生命周期时不能把"回调返回"当释放边界。

六、大块 payload 不在 ring packet 里

  • 一个 packet 可以只有 inline 控制缓冲,大块 payload 通过外部内存描述提供
  • 所以只 dump ring 时看到:
header / command / 几个字段
但找不到真正的数据

很正常——要继续解析外部的内存描述。

七、取外部数据可能返回"待决",而且会再次回调

  • 若为描述这段内存需要把对应区域分页进来,取外部数据的调用会返回 STATUS_PENDING
  • 此时必须从 packet 处理回调返回;稍后回调可能以不同的 IRQL(例如 DPC)再次被调用,届时再调一次就会成功并返回数据
  • 于是 packet handler 的控制流实际是:
callback #1  →  取数据 → STATUS_PENDING → return
        …稍后,可能在不同 IRQL…
callback #2  →  再次取数据 → 成功 → 继续处理

普通的同步 CFG 很容易漏掉这条边。

返回的 MDL 有两个易错点:

  • 它描述的内存已被锁定——不需要再 probe/lock
  • 它既没有用户态也没有内核态虚拟地址——需要虚拟地址时必须显式映射(并在之后解除映射),或者直接把 MDL 交给做 DMA 的驱动
  • MDL 在"完成"被调用时失效,之后由内核库释放;驱动不需要自己释放它

需要把处理放到被动级别时,可用"延迟到被动级"的接口把 packet 交过去。

决策树

拿到 Hyper-V 环境目标
├─ 设备 I/O 找不到硬件 MMIO
│    └─ 先按合成设备(VSC/VSP 经 VMBus)解释
├─ trace 里出现小整数句柄
│    └─ 可能是 GPADL(客户机缓冲区描述),不是地址
├─ 网络流量中途"消失"但功能正常
│    ├─ 查是否启用 SR-IOV:VF 数据面不过 VMBus
│    └─ 查 VF 拆除/迁移路径(VPCI VSP → VMBus → VPCI VSC → NetVSC 解绑)
├─ 通道拥塞 / 性能异常
│    └─ 看是否使用了多通道(高速设备常有)
└─ 共享内存用量相关
     └─ GPADL 总量存在上限(宿主侧限制),不是无限共享

工具与验证

  • 通道与设备:VMBus 总线视图、VSC/VSP 配对关系、合成设备类型
  • 缓冲区:GPADL 建立与使用点;对照"这是句柄不是地址"
  • 网络路径:VF 是否存在、控制面是否仍在 VMBus、数据面当前走 VF 还是合成路径
  • 验证:能同时说清「这条 I/O 走合成路径还是 VF 直通 + 共享内存经 GPADL 如何描述 + 控制面在谁那里」

该平台的坑(汇总)

  • 把 GPADL 当内存地址:它是缓冲区描述符句柄
  • 在合成设备上找硬件 MMIO:VMBus 走共享 ring,不走寄存器窗口
  • 把 SR-IOV 切换当数据丢失或抓包失败:数据面可以从 VMBus 切到 VF 直通(并可能切回)
  • 以为 VF 的一切都绕开 VMBus:数据面绕开,控制面仍走 VMBus
  • 忽略多通道:高速设备可能并行使用多条通道
  • 以为 GPADL 共享无上限:宿主对共享总量有限制
  • 把删除 GPADL 时的阻塞当死锁:它会等 server 解除映射;从 buffer 创建的 GPADL 还会锁定该 buffer 直到销毁
  • 把处理回调返回当 packet 结束:完成调用才是释放边界,且可能被延后
  • 在 ring packet 里找大块 payload:大块数据走外部内存描述
  • 把取外部数据返回"待决"当失败:需要分页时就是它会返回的,之后会在可能不同的 IRQL 再次回调
  • 从外部数据 MDL 直接取虚拟地址:它不带任何虚拟地址,需要显式映射并解除
  • 自己去释放外部数据 MDL:它在完成调用时失效,由内核库释放