2. 内核数据路径基础:设计原理、流表缓存与关键数据结构

OVS 内核数据路径(Kernel Datapath)是整个 OVS 转发性能的核心。它通过将流表缓存到内核空间,避免了每次报文转发都陷入用户态,从而实现了线速转发。本章深入剖析其设计原理、缓存机制以及核心数据结构。

核心概念: OVS 内核数据路径遵循“先慢速路径学习,后快速路径匹配”的原则。第一个报文触发用户态流表查询并下发流表项,后续同流报文直接在内核空间完成匹配与转发。

2.1 内核数据路径(Fast Path)设计原理

OVS 的数据路径分为两个层次:快速路径(Fast Path) 和 慢速路径(Slow Path)。快速路径运行在内核模块 openvswitch.ko 中,处理绝大多数数据包;慢速路径则涉及用户态守护进程 ovs-vswitchd。

  • 快速路径(Fast Path): 报文到达网卡后,直接由内核模块处理。模块提取报文头部生成 flow_key,在内核流表(struct flow_table)中执行精确匹配或通配匹配。若命中,则直接执行 actions(如 output、modify、drop),全程无上下文切换。
  • 慢速路径(Slow Path): 当内核流表未命中时,报文通过 Netlink 通道(upcall)上送至用户态 ovs-vswitchd。用户态进程查询完整的 OpenFlow 流表,决策后将新的流表项下刷到内核,同时将报文重新注入内核或直接处理。
性能关键: 快速路径的目标是处理 99% 以上的流量。流表命中率直接决定了 OVS 的转发性能。高命中率意味着低延迟和低 CPU 开销。
OVS 内核数据路径:Fast Path 与 Slow Path 流程 报文到达 (Packet In) 内核流表查找 (Flow Lookup) 执行 Actions (Fast Path) Upcall (上送至用户态) ovs-vswitchd 处理 流表项下刷 (Flow Install) 说明:绿色箭头表示 Fast Path 命中路径;红色箭头表示未命中后进入 Slow Path 的 Upcall 流程。

2.2 流表缓存机制

内核流表缓存是 Fast Path 的核心。它通过哈希表组织,支持精确匹配和通配匹配(Wildcard Matching)。缓存机制的设计直接影响查找效率和内存占用。

缓存类型 匹配方式 适用场景 性能特征
精确匹配缓存 基于 flow_key 的哈希值精确查找 TCP/UDP 单流,五元组固定 O(1) 查找,内存占用低
通配匹配缓存 基于 TSS(Tuple Space Search)或 元组空间 L2/L3 转发,允许部分字段通配 O(log N) ~ O(N),内存占用较高
Megaflow 缓存 通配匹配,支持掩码 OpenFlow 通配规则下刷到内核 查找复杂度与元组数量相关
注意: 通配匹配缓存(Megaflow)虽然提高了灵活性,但可能导致缓存膨胀和查找性能下降。当流表项数量超过 10 万条时,建议评估查找延迟。
OVS 内核流表缓存结构 哈希桶 (Hash Buckets) Bucket 0 Bucket 1 Bucket 2 Bucket N struct sw_flow (流表项) struct sw_flow (流表项) struct sw_flow_mask 说明:哈希桶通过链表解决冲突,每个 sw_flow 可关联一个 sw_flow_mask 实现通配匹配。

2.3 关键数据结构

理解内核数据路径,必须掌握三个核心数据结构:struct sw_flow、struct flow_key 和 struct sw_flow_actions。它们分别代表流表项、报文特征和转发动作。

2.3.1 struct flow_key

struct flow_key 是报文头部特征的抽象,用于流表查找。它包含了 L2、L3、L4 层的关键字段。

/* 内核源码位置: net/openvswitch/flow.h */
struct flow_key {
    __be16  eth_type;           /* 以太网类型 (EtherType) */
    __be16  vlan_tci;           /* VLAN TCI (含 PCP/CFI/VID) */
    __be32  ip_src;             /* IPv4 源地址 */
    __be32  ip_dst;             /* IPv4 目的地址 */
    u8      ip_proto;           /* IP 协议号 (TCP=6, UDP=17) */
    u8      ip_tos;             /* IP ToS/DSCP */
    u8      ip_ttl;             /* IP TTL */
    __be16  tp_src;             /* 传输层源端口 */
    __be16  tp_dst;             /* 传输层目的端口 */
    u8      eth_src[ETH_ALEN];  /* 源 MAC 地址 */
    u8      eth_dst[ETH_ALEN];  /* 目的 MAC 地址 */
    /* ... 其他字段如 MPLS、IPv6、ARP 等 */
};

2.3.2 struct sw_flow

struct sw_flow 是内核流表中的核心条目,包含匹配规则、统计信息和关联的动作集。

/* 内核源码位置: net/openvswitch/flow.h */
struct sw_flow {
    struct rcu_head rcu;                /* RCU 回调,用于无锁读 */
    struct hlist_node hash_node[2];     /* 哈希链表节点 (精确+通配) */
    u32             hash;               /* 流表项的哈希值 */
    struct sw_flow_key key;             /* 匹配的 flow_key */
    struct sw_flow_mask *mask;          /* 通配掩码 (NULL 表示精确匹配) */
    struct sw_flow_actions *sf_acts;    /* 关联的动作集 */
    struct sw_flow_stats stats;         /* 统计信息 (包数/字节数/时间) */
    /* ... 其他字段 */
};

2.3.3 struct sw_flow_actions

动作集定义了匹配后需要执行的操作,如输出到端口、修改字段、丢弃等。

/* 内核源码位置: net/openvswitch/flow.h */
struct sw_flow_actions {
    struct rcu_head rcu;        /* RCU 回调 */
    u32             actions_len; /* 动作列表长度 (字节) */
    struct nlattr   actions[];  /* 灵活数组,存储 Netlink 格式的动作 */
};
设计要点: 使用 struct rcu_head 实现 RCU(Read-Copy-Update)机制,允许读者在动作集更新时无锁访问,极大提升了并发性能。
OVS 内核数据路径关键数据结构关系 struct flow_key 报文特征 (L2-L4 头部) struct sw_flow 流表项 (匹配+动作+统计) struct sw_flow_actions 动作集 (output/modify/drop) struct sw_flow_mask 关系:sw_flow 包含 flow_key 作为匹配条件,关联 sw_flow_actions 作为执行动作,并通过 sw_flow_mask 支持通配。

2.4 流表查找流程总结

结合上述数据结构,内核数据路径的流表查找流程可以归纳为以下步骤:

  1. 报文解析: 从 skb 中提取 L2/L3/L4 头部,填充 struct flow_key。
  2. 哈希计算: 对 flow_key 进行哈希,定位哈希桶。
  3. 精确匹配: 遍历哈希桶链表,比较 sw_flow->key 与当前 flow_key 是否完全一致。
  4. 通配匹配: 若精确匹配失败,遍历所有 sw_flow_mask,对 flow_key 进行掩码后比较。
  5. 执行动作: 匹配成功后,执行 sw_flow->sf_acts 中的动作列表。
  6. 未命中处理: 若均未匹配,触发 Upcall,将报文发送至用户态。
性能优化建议: 在数据中心场景下,建议优先使用精确匹配缓存。对于通配匹配,应控制元组数量,避免因掩码组合过多导致查找性能退化。