# 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 不是内存地址**——它是"描述并映射一片客户机缓冲区"的句柄。 ## 一、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**:它在完成调用时失效,由内核库释放