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 开销。
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 万条时,建议评估查找延迟。
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)机制,允许读者在动作集更新时无锁访问,极大提升了并发性能。
2.4 流表查找流程总结
结合上述数据结构,内核数据路径的流表查找流程可以归纳为以下步骤:
- 报文解析: 从 skb 中提取 L2/L3/L4 头部,填充
struct flow_key。 - 哈希计算: 对
flow_key进行哈希,定位哈希桶。 - 精确匹配: 遍历哈希桶链表,比较
sw_flow->key与当前flow_key是否完全一致。 - 通配匹配: 若精确匹配失败,遍历所有
sw_flow_mask,对flow_key进行掩码后比较。 - 执行动作: 匹配成功后,执行
sw_flow->sf_acts中的动作列表。 - 未命中处理: 若均未匹配,触发 Upcall,将报文发送至用户态。
性能优化建议: 在数据中心场景下,建议优先使用精确匹配缓存。对于通配匹配,应控制元组数量,避免因掩码组合过多导致查找性能退化。