ef_vi 学习笔记
ef_vi 是 OSI 二层的一套接口,用户态应用可以绕开 POSIX socket 的接口,直接通过 ef_vi 访问 Solarflare 网卡的数据路径,能够降低延迟并减少每个消息发送的开销。
ef_vi 是位于 OSI 二层的接口,应用可以通过这些接口处理原始二层以太帧的收发,此时上层协议就必须由应用实现。
另外 ef_vi 是打包在 OpenOnload 发行版里的,仓库在这里:https://github.com/Xilinx-CNS/onload
参考文档:
https://docs.amd.com/r/en-US/ug1586-onload-user/ef_vi
https://docs.amd.com/v/u/en-US/SF-114063-CD-ef_vi_User_Guide
总览
ef_vi 能够暴露一些网卡特性的接口,或者与这些特性协同工作,比如:
- 校验和 offload
- 硬件时戳
- 一些二层交换功能
- 哈希 RX 负载均衡
- 分流
- 数据路径的监控
- 极低延迟的 CTPIO 模式
- 低延迟 PIO 模式
- Scatter/gather 的传输与接收
- 虚拟环境下的 PCI 直通与 SO-IOV
与其他类似技术相比,ef_vi 能提供更好的灵活性。 每个 ef_vi 实例都可以认为是交换机上的一个虚拟端口,这个实例能够处理单个物理端口的报文,也可以仅处理特定的报文子集。 每个物理端口支持至多 2048 个虚拟网卡接口。 这就意味着可以通过多个 ef_vi 实例同时支持不同的上层网络实现,比方说 Linux 标准网络栈可以跟 AMD OpenOnload 的 socket 加速技术同时使用。 通过这些虚拟接口,我们可以把单个物理端口的流量负载分配的不同的 CPU 核心上处理,或者同时加速大量的虚拟机。
一些常见的应用场景:
-
套接字加速
可以使用 ef_vi 代替 POSIX socket API 或其他 API。 应用可以为各个的线程分配不同的 ef_vi 实例,并分别指定它们关心的特定流量,也就是 RX 过滤器。 比方说可以指定接收 UDP 协议的特定目的 IP 与端口号。 这些流量会被传递给 ef_vi 处理,而其他流量则会经由通用驱动照旧传递给内核网络栈。
-
报文捕获
-
报文回放
-
应用模拟终端
-
软件定义的桥接、交换与路由
概念
虚拟接口
每个 ef_vi 实例对应网卡的一个虚拟接口。 每个虚拟接口包括以下组件:
- 一个事件队列
- 一个 TX 描述符环
- 一个 RX 描述符环
这些组件都是可选的,但是如果不指定事件队列,就必须指定另一个具有事件队列的虚拟接口作为替代。
每个虚拟接口还具有一些硬件资源:
- 一个门铃寄存器,用于通知硬件已有新的 RX 缓冲区可用于写入
- 一个门铃寄存器,用于通知硬件已有新的 TX 缓冲区可用于发送
- 一些定时器
- 一个共享的中断
虚拟接口集
包含多个虚拟接口,通过 RSS 策略,将过滤器过滤出的流量负载自动分配到各个虚拟接口上。
注意一下,必须为虚拟接口集配置多个过滤器,因为单个流量过滤器的 RSS 哈希值是固定的。 这意味着单个过滤器的流量只会被分配到某个特定的虚拟接口上。
事件队列
用于向应用传递网卡上的信息,例如报文的接收、发送完成等。
TX 描述符环
用于从应用向网卡传递需要发送的报文。 TX 描述符环中的每一个描述符条目会对应一个缓冲区,需要发送的数据就被存储在这个缓冲区中。 一个报文可以通过一个描述符发送,长报文也可以通过多个描述符发送。
应用需要向缓冲区中写入报文,并提交到 TX 描述符环中。 报文传输就会在后台进行,传输完成时网卡会通过事件队列通知应用。
RX 描述符环
用于从网卡向应用传递接收到的报文。 每个描述符对应一个空闲的缓冲区,网卡接收到报文时会把它写入下一个空闲缓冲区中,并通过事件队列通知应用。 应用消费完报文后,需要将恢复空闲状态的缓冲区重新提交到 RX 描述符环中。
视网卡型号不同,缓冲区的分配方式是不一样的。 比方说部分网卡的缓冲区需要由 ef_vi 库管理,而部分网卡则需要应用分配并管理。 具体需要参考官方文档。
保护域
一个保护域标识了一个用于 DMA 地址的独立地址空间,这些 DMA 地址会被传递给网卡。 保护域可以用于隔离不同的 ef_vi 应用所使用的 DMA 内存,或用于共享资源。
- 每个虚拟接口(ef_vi 实例)只能绑定到一个保护域上。
- 多个虚拟接口可以绑定到同一个保护域上。
- 每个内存区域可以绑定到多个保护域上。
- 每个内存区域只能由处于相同保护域内的虚拟接口使用。
传统的设备驱动能够直接把内存缓冲区的物理地址传递给 I/O 设备,因为驱动本身运行在内核态。 但使用 ef_vi 的上层应用通常不能使用未经保护的物理地址,所以需要通过保护域间接地获取缓冲区的物理地址,并将其传递给网卡。
内存区域
内存区域是通过 ef_memreg 接口注册的,可用于 TX 或 RX 缓冲区。
这能够确保内存区域满足 ef_vi 的要求:
- 内存会被固定(pinned),保证驻留在物理内存中,不会被交换到交换空间中。
- 内存被映射为 DMA 地址,如此一来网卡就可以访问这块内存。网卡会把用户提供的 DMA 地址翻译为总线上的 I/O 地址。
- 内存区域需要是页对齐的。
- 内存区域的大小需要是报文缓冲区大小的倍数,从而避免浪费。
报文缓冲区
报文缓冲区是分配在主机上的内存,可供网卡读取需要发送的报文,或将接收到的报文写入进去。 其大小通常为 2KB。
报文缓冲区会通过这样的方式映射给网卡:只有处在相同保护域中的虚拟接口才可访问,除非显式使用物理寻址模式(需要 root 用户配置驱动选项并授予一组用户)。