Skip to content

实验 19:用户态 datapath ​

实验 1 里 ovs-dpctl show 的第一行是:

text
system@ovs-system:

system 表示内核 datapath。这一章换成另一种:netdev,也就是用户态 datapath。

两种 datapath ​

text
   内核 datapath(system)                用户态 datapath(netdev)
   ┌──────────────────────┐              ┌──────────────────────┐
   │ ovs-vswitchd         │              │ ovs-vswitchd         │
   │  完整流表             │              │  完整流表             │
   │                      │              │  + 转发缓存          │
   │                      │              │  + 实际转发          │
   └──────────┬───────────┘              └──────────┬───────────┘
        upcall│装缓存                          收发包 │
   ┌──────────┴───────────┐              ┌──────────┴───────────┐
   │ 内核 openvswitch 模块 │              │ 网卡                  │
   │  转发缓存 + 实际转发   │              │  DPDK / AF_XDP /      │
   └──────────┬───────────┘              │  AF_PACKET           │
              │                          └──────────────────────┘
   ┌──────────┴───────────┐
   │ 网卡                  │
   └──────────────────────┘
system(内核)netdev(用户态)
转发在哪内核模块ovs-vswitchd 进程内
慢路径upcall 跨内核/用户态边界同进程内调用,没有边界
收发包方式内核网络栈DPDK / AF_XDP / AF_PACKET
CPU 占用按需PMD 线程通常 100% 轮询
需要特殊网卡驱动否DPDK 模式需要
极限性能中高(DPDK 可达线速)
适合通用场景NFV、高吞吐转发

用户态 datapath 的核心思路是绕过内核网络栈,配合 DPDK 用轮询代替中断,用大页内存减少 TLB miss,从而把小包转发性能提上去。代价是 CPU 常驻占用。

起环境 ​

bash
cd labs/learn-ovs/16-userspace-dp
./start.sh

先看这台机器支持哪些类型:

bash
docker exec clab-userspace-dp-ovs1 ovs-vsctl get Open_vSwitch . datapath_types
text
[netdev, system]

这个字段实验 2 里见过,现在派上用场了。

建一个 netdev 网桥 ​

只差一个字段:

bash
N="docker exec clab-userspace-dp-ovs1"

$N ovs-vsctl add-br br0 -- set Bridge br0 datapath_type=netdev
$N ovs-vsctl add-port br0 eth1
$N ovs-vsctl add-port br0 eth2
$N ip link set br0 up

datapath_type 必须在建网桥时或之后立刻设

改一个已经在跑的网桥的 datapath_type,OVS 会销毁重建它的 datapath,所有流表和端口状态都会重置。生产环境上别这么干。

验证:

bash
docker exec clab-userspace-dp-h1 ping -c 3 192.168.10.2
text
3 packets transmitted, 3 received, 0% packet loss

通了,而且转发全程没有进过内核 openvswitch 模块。

证据:内核 datapath 是空的 ​

bash
$N ovs-dpctl show
text
(没有任何输出)

ovs-dpctl 直接和内核模块对话(实验 2 那张表),它看不到任何东西 —— 因为这个网桥的 datapath 压根不在内核里。

要看用户态 datapath,必须走 ovs-vswitchd:

bash
$N ovs-appctl dpctl/show
text
netdev@ovs-netdev:
  lookups: hit:5 missed:7 lost:0
  flows: 7
  port 0: ovs-netdev (tap)
  port 1: br0 (tap)
  port 2: eth1
  port 3: eth2

这就是必须用 ovs-appctl dpctl/ 的另一个理由

实验 1 说过日常应该用 ovs-appctl dpctl/... 而不是 ovs-dpctl。用户态 datapath 下这不再是"建议",而是唯一可行的方式 —— ovs-dpctl 完全看不到 netdev datapath。

注意 port 0: ovs-netdev (tap) 和 port 1: br0 (tap):netdev 模式下 internal 端口是通过 tap 设备接回 Linux 协议栈的,而内核模式下它们是 internal 类型。

上层完全不变 ​

这是最值得强调的一点。流表、trace、MAC 表 —— 所有命令和语法一模一样:

bash
$N ovs-ofctl dump-flows br0
text
 cookie=0x0, table=0, n_packets=12, priority=0 actions=NORMAL
bash
$N ovs-appctl ofproto/trace br0 "in_port=1,dl_src=<h1MAC>,dl_dst=<h2MAC>"
text
bridge("br0")
-------------
 0. priority 0
    NORMAL
     -> forwarding to learned port

Datapath actions: 3

换 datapath 只影响最底层那一跳。 前面十八章学的东西全部适用。

用户态的缓存结构更复杂 ​

bash
$N ovs-appctl dpctl/dump-flows
text
flow-dump from the main thread:
recirc_id(0),in_port(2),packet_type(ns=0,id=0),
  eth(src=aa:c1:ab:ab:fb:f2,dst=ff:ff:ff:ff:ff:ff),
  eth_type(0x0806),arp(...), actions:1,3

格式和内核的 megaflow 基本一样,多了个 packet_type 字段。

但内部结构不同 —— 看 PMD 统计:

bash
$N ovs-appctl dpif-netdev/pmd-stats-show
text
main thread:
  packets received: 27
  packet recirculations: 0
  avg. datapath passes per packet: 1.00
  phwol hits: 0
  mfex opt hits: 0
  simple match hits: 0
  emc hits: 0
  smc hits: 0
  megaflow hits: 16

用户态 datapath 有四级缓存,从快到慢:

层级全称说明
phwolPartial HW Offload网卡硬件已经算好了流标识
emcExact Match Cache精确匹配缓存,最快,容量小(默认 8192)
smcSignature Match Cache签名匹配,比 EMC 省内存
megaflow——带掩码的匹配,和内核 megaflow 同一个概念

一个包依次尝试这几级,全都不中才上到完整流表。

emc hits 占比是用户态转发的核心性能指标。 EMC 命中率低意味着流数量超过了缓存容量,性能会明显下降。

这里没有 PMD 线程

输出里是 main thread:,不是 pmd thread numa_id 0 core_id 2:。

因为我们没配 DPDK,也没设 pmd-cpu-mask。这种情况下转发由 ovs-vswitchd 主线程用 AF_PACKET 完成 —— 能跑通,但完全没有性能优势,反而比内核 datapath 慢(多了内核到用户态的拷贝)。

真正的性能来自下面这些配置。

走向真正的高性能 ​

本实验只验证了机制。生产环境的 OVS-DPDK 需要:

一、初始化 DPDK ​

bash
ovs-vsctl set Open_vSwitch . other_config:dpdk-init=true
ovs-vsctl set Open_vSwitch . other_config:dpdk-socket-mem="1024,1024"

需要提前配好大页内存(hugepages)并重启 ovs-vswitchd。

二、绑定 PMD 线程到专用 CPU ​

bash
ovs-vsctl set Open_vSwitch . other_config:pmd-cpu-mask=0x6

PMD(Poll Mode Driver)线程忙轮询收包,会把绑定的核占到 100%。这不是异常,是设计如此 —— 轮询消除了中断延迟。

绑核要避开内核和其它业务用的核,并且注意 NUMA 亲和性:网卡和 PMD 线程应该在同一个 NUMA 节点。

三、用 DPDK 类型的端口 ​

bash
ovs-vsctl add-port br0 dpdk0 \
  -- set Interface dpdk0 type=dpdk options:dpdk-devargs=0000:01:00.0

网卡要先用 dpdk-devbind 从内核驱动解绑,交给 vfio-pci。解绑之后内核就看不到这块网卡了,ip link 里会消失。

四、虚拟机接入用 vhost-user ​

bash
ovs-vsctl add-port br0 vhu0 \
  -- set Interface vhu0 type=dpdkvhostuserclient \
       options:vhost-server-path=/var/run/openvswitch/vhu0

虚拟机通过共享内存直接和 OVS 交换报文,绕过宿主机内核。

AF_XDP:折中方案 ​

不想上 DPDK 那套重型配置,可以用 AF_XDP:

bash
ovs-vsctl add-port br0 eth1 -- set Interface eth1 type=afxdp

它利用内核的 XDP 钩子在协议栈之前处理包,性能不如 DPDK 但不需要解绑网卡驱动,部署轻量得多。Open_vSwitch.iface_types 里能看到 afxdp 和 afxdp-nonpmd。

什么时候该用 ​

绝大多数场景用内核 datapath。 它够快、零配置、和 Linux 生态完全兼容。

考虑用户态 datapath 的信号:

  • 小包(64 字节)转发能力成为瓶颈,需要百万 pps 以上
  • NFV 场景,虚拟机之间要跑高吞吐流量
  • 有专门的核可以拿来做转发(PMD 会占满)
  • 团队有能力维护大页、绑核、NUMA 这些配置

用户态 datapath 不是免费的性能提升

配错了会比内核 datapath 更慢(就像本实验的 AF_PACKET 模式)。而且它引入了一整套新的运维复杂度:大页内存、CPU 隔离、NUMA 亲和、网卡驱动解绑。

先确认内核 datapath 真的成了瓶颈,再考虑迁移。第 6 部分会讲怎么测。

自动检查 ​

bash
./verify.sh
text
  [通过] br0 的 datapath_type 是 netdev
  [通过] 存在 netdev@ovs-netdev 这个 datapath
  [通过] 内核 datapath 里没有 br0 的端口
  [通过] 用户态转发正常:h1 能 ping 通 h2

自测清单 ​

  1. system 和 netdev 两种 datapath,转发分别发生在哪里?
  2. 建 netdev 网桥需要改什么?
  3. 为什么 ovs-dpctl show 看不到 netdev datapath?该用什么命令?
  4. 用户态 datapath 有哪四级缓存?哪个指标最关键?
  5. 本实验里为什么没有 PMD 线程?这意味着什么?
  6. PMD 线程 CPU 占用 100% 是异常吗?
  7. 什么信号说明该考虑用户态 datapath?

常见问题 ​

建了 netdev 网桥但端口报错 ​

bash
ovs-vsctl show | grep -A3 error

netdev datapath 对端口类型更挑。普通 Linux 设备(veth、物理网卡)走 AF_PACKET 是支持的,但某些虚拟设备类型不支持。

ovs-dpctl 什么都不显示 ​

预期行为。 netdev datapath 不在内核里,用:

bash
ovs-appctl dpctl/show
ovs-appctl dpctl/dump-flows

性能比内核 datapath 还差 ​

没配 DPDK 或 AF_XDP 时就是这样 —— AF_PACKET 模式多了一次内核到用户态的拷贝。

要性能必须配 DPDK(大页 + 绑核 + 网卡解绑)或至少 AF_XDP。

dpdk-init 设了但 ovs-vswitchd 起不来 ​

看日志:

bash
tail -50 /var/log/openvswitch/ovs-vswitchd.log

最常见的是大页内存没配够。检查:

bash
cat /proc/meminfo | grep -i huge

PMD 线程把 CPU 占满了 ​

这是设计如此。 PMD 忙轮询以消除中断延迟。用 pmd-cpu-mask 把它限制在专用核上,别让它和业务或内核抢核。

小结 ​

  • OVS 有两种 datapath:system(内核模块) 和 netdev(用户态)
  • 切换只需 set Bridge br0 datapath_type=netdev,上层流表、trace、命令全部不变
  • netdev datapath 下 ovs-dpctl 完全看不到,必须用 ovs-appctl dpctl/...
  • 用户态有四级缓存:phwol → EMC → SMC → megaflow,emc hits 占比是核心性能指标
  • 不配 DPDK/AF_XDP 时走 AF_PACKET,能跑通但比内核 datapath 更慢
  • 真正的性能需要:DPDK 初始化 + 大页内存 + PMD 绑核 + 网卡驱动解绑
  • PMD 线程占满 CPU 是设计如此(忙轮询消除中断延迟)
  • AF_XDP 是轻量折中:不用解绑网卡,性能介于两者之间
  • 绝大多数场景用内核 datapath;先确认它真成了瓶颈再迁移

下一章:OVN 是什么 —— 把前面所有零件组装成一个完整的虚拟网络系统。