首页
关于
Search
1
PacketForge 博客部署文档
16 阅读
2
深入 SONiC 与 SAI:从命令行到芯片的完整数据之旅
14 阅读
3
DPU 与 CPO:AI 时代的网络基础设施革命
11 阅读
4
Broadcom芯片
10 阅读
5
DPDK高性能网络
10 阅读
默认分类
交换芯片
高性能网络
AI算力网络
WebSocket高性能项目
DPDK 成长路线
①性能工程基础
②DPDK 源码深读
③用户态协议栈
④软件交换与 SONiC
⑤PCIe、虚拟化与 DPU
⑥面试与复盘
登录
Search
标签搜索
DPDK→DPU 学习路线
P0
P1
P2
P3
P5
PCIe
P4
P6
libmtcp
NUMA
mTCP
OVS
SONiC
SAI
DPU
perf
gdb
EAL
mempool
PacketForge
累计撰写
53
篇文章
累计收到
3
条评论
首页
栏目
页面
关于
最新文章
热门文章
评论最多
点赞最多
2026-08-18
P3-05 · sai-dpdk-mini:用 DPDK 实现 SAI 最小适配器
对应路线图:Phase 3 (Day 92-119) | DPDK 24.11.6 LTS | 内核对照 v6.6代码出处:code/sai-dpdk-mini/(当前实际内容)与 source/dpdk-stable-24.11.6/lib/,引用均标 文件:行号上一篇:P3-04 SAI 头文件全景 | 下一篇:P3-06 host_if 打通一句话定位sai-dpdk-mini 回答一个问题:不买 ASIC,纯 DPDK 软件能不能当 SAI 的一个"厂商实现"? 能——把 SAI 的 L2/L3/ACL 语义逐条映射到 rte_hash/rte_lpm/rte_flow 上,SONiC 全家桶就能跑在 x86 软件交换机上。如实交代现状:截至写这篇博客,code/sai-dpdk-mini/ 里只有一份 CMake 骨架和空目录——所以这篇是"接口映射设计 + 骨架精读",每个映射目标都给出 DPDK 侧真实行号,实现代码随学习进度补。现状:骨架的真实内容code/sai-dpdk-mini/CMakeLists.txt(全文,当前唯一文件):cmake_minimum_required(VERSION 3.16) project(sai-dpdk-mini C) set(CMAKE_C_STANDARD 11) add_library(sai-dpdk SHARED src/sai_switch.c src/sai_port.c src/sai_fdb.c src/sai_route.c src/sai_neighbor.c) target_include_directories(sai-dpdk PRIVATE include ${CMAKE_SOURCE_DIR}/../../references/sai-headers/inc)这份骨架已经锁定了三个关键决策:产物是共享库 libsai-dpdk.so——它扮演厂商 SDK 的角色,syncd 加载它就像加载 libsai-brcm;模块划分 = SAI 模块划分:switch/port/fdb/route/neighbor 五个 .c,每个导出一张 SAI API 表(对应 P3-04 的 sai_switch_api_t 等);直接 include 真实 SAI 头文件(references/sai-headers/inc)——API 签名零翻译、零自造,sai_api_query 换表流程原样可用(sai.h:252)。src/、include/、test/ 目录已建但为空——按学习节奏逐模块填充,这是刻意的:先定接口契约,再写实现。映射设计:SAI 语义 → DPDK 原语① L2 FDB → rte_hash(哈希表)SAI 的 sai_fdb_entry_t(MAC+VLAN+port,saifdb.h:357 的 key 参数)天然是哈希 key。DPDK 侧的真实接口(lib/hash/rte_hash.h):// rte_hash.h:146 struct rte_hash * rte_hash_create(const struct rte_hash_parameters *params); // rte_hash.h:246 int rte_hash_add_key_data(const struct rte_hash *h, const void *key, void *data); // rte_hash.h:454 int rte_hash_lookup_data(const struct rte_hash *h, const void *key, void **data);设计要点:key 用 12 字节结构 {mac[6], vlan_id[2], port_id[4]} 对齐填零;data 存出端口号;rte_hash_add_key_data 把"查表→拿值"合成一步。学习型 FDB 的洪泛逻辑(查不到就广播)在 SAI 层用 create_fdb_entry 的删除回调 + 超时实现,与内核 br_fdb_update 的职责对照(内核版见 net/bridge/br_fdb.c:863)。② L3 路由 → rte_lpm(DIR-24-8 最长前缀匹配)SAI 的 sai_route_entry_t(sairoute.h:180 的 destination 前缀)对应 DPDK 的真实 LPM 接口(lib/lpm/rte_lpm.h):// rte_lpm.h:162-164 struct rte_lpm * rte_lpm_create(const char *name, int socket_id, const struct rte_lpm_config *config); // rte_lpm.h:221 int rte_lpm_add(struct rte_lpm *lpm, uint32_t ip, uint8_t depth, uint32_t next_hop); // rte_lpm.h:279 static inline int rte_lpm_lookup(const struct rte_lpm *lpm, uint32_t ip, uint32_t *next_hop)next_hop 参数直接挂 sai_next_hop_t 的 OID 低 32 位(P3-04:OID 是 64 位句柄,高位编码类型)。DIR-24-8 算法本身就是"软件交换机的最佳路由表实现"——这也是 P3-01 里 OVS dpcls 和内核 fib_trie 之外的第三条路线(内核对照:net/ipv4/fib_trie.c:1425 的 fib_table_lookup 用 level-compressed trie)。③ ARP 邻接 → rte_hashsai_neighbor_entry_t(IP+rif,saineighbor.h:206)同样进哈希,key 是 {ip[4], rif_oid[8]},data 是 {mac[6], 出口}。它和 FDB 共用同一份 rte_hash 初始化代码——两个 SAI 模块映射到同一个 DPDK 原语,这就是适配层的乐趣。④ ACL → rte_flowSAI 的 create_acl_table(saiacl.h:3713)+ entry 匹配动作,翻译成 DPDK 的 flow 规则(lib/ethdev/rte_flow.h):// rte_flow.h:89 struct rte_flow_attr { uint32_t group; uint32_t priority; /**< Rule priority level within group. */ uint32_t ingress:1; uint32_t egress:1; // ... }; // rte_flow.h:4695 struct rte_flow * rte_flow_create(uint16_t port_id, const struct rte_flow_attr *attr, const struct rte_flow_item pattern[], const struct rte_flow_action actions[], struct rte_flow_error *error);映射规则:SAI ACL table → rte_flow_attr.group;SAI 规则优先级 → priority;匹配字段翻译成 pattern(ETH/IPV4/5-tuple);动作翻译成 actions(DROP/COUNT/redirect)。如果 NIC 不支持 offload,DPDK 会退软件 fallback——sai-dpdk-mini 的 ACL 一开始就走软件路径,等价于一张每核免锁的精确匹配表(P3-01 的 EMC 思路复用)。⑤ port/switch → ethdevcreate_port(saiport.h:4296)→ rte_eth_dev_info_get/rte_eth_dev_configure;set_port_attribute(saiport.h:4320)的 admin state → rte_eth_dev_start/stop。switch 属性(saiswitch.h:4263 表)落在全局配置结构里。🐧 内核对照:这三张表在内核长什么样SAI 语义DPDK 原语内核对应FDB entryrte_hash_add_key_data(rte_hash.h:246)br_fdb_update(net/bridge/br_fdb.c:863)route entryrte_lpm_add(rte_lpm.h:221)fib_table_insert(net/ipv4/fib_trie.c:1199)neighborrte_hash_add_key_dataneigh_update(ARP 表)ACLrte_flow_create(rte_flow.h:4695)tc cls flower + net/sched/hostif 发包sai_send_hostif_packet_fn(saihostif.h:1385)tun_net_xmit(drivers/net/tun.c:1256)注意 DPDK 和内核的查表数据结构选择完全一致:哈希表做精确匹配(FDB/邻居),trie/LPM 做前缀匹配(路由)。区别在于 DPDK 的表是用户态预分配、每核可私有、批量查询(rte_lpm_lookup_bulk);内核表要扛 RCU 和路由事件通知。sai-dpdk-mini 就是把这个对照写成代码。面试 3 问sai-dpdk-mini 的产物为什么是共享库? → 它扮演厂商 SDK:syncd 通过 sai_api_query 换表(sai.h:252),CMake 骨架 add_library(sai-dpdk SHARED ...) 已定;模块按 SAI 模块切 .c 文件(CMakeLists 的 5 个源文件)。FDB 和 neighbor 为什么都用 rte_hash 而不是各自的表? → 两者都是精确匹配语义,key 结构不同({MAC,VLAN,port} vs {IP,rif})但原语相同;SAI 层两个模块、DPDK 层一个原语。SAI ACL 怎么映射 rte_flow? → table → rte_flow_attr.group,优先级 → priority(rte_flow.h:89-101),匹配字段 → pattern,动作 → actions,rte_flow_create(rte_flow.h:4695)下发;软件 fallback 时等价 per-core 精确匹配表。🔧 动手实验cmake -B build 验证骨架可配置(src 为空时先补桩函数);实现 sai_fdb.c 的第一版:create_fdb_entry 用 rte_hash_add_key_data,写 gdb 断点验证 key 布局(P0-06 的 gdb 功夫);实现 sai_route.c 用 rte_lpm_add,和 fib_trie.c:1199 的插入路径对照读;跑通 sai_api_query 换表 → create_switch(INIT_SWITCH=false) 的最小 demo。产出 perf-data/P3/05-sai-dpdk-mini.md(三张表插入/查询的单核 OPS 曲线)。下一篇:P3-06 · host_if 打通:让 FRR/BGP 跑在纯软件转发上
2026年08月18日
1 阅读
0 评论
0 点赞
2026-08-18
P3-04 · SAI 头文件全景:15 个 MVP API 矩阵
对应路线图:Phase 3 (Day 92-119) | DPDK 24.11.6 LTS | 内核对照 v6.6代码出处:references/sai-headers/inc/(真实 SAI 头文件),引用均标 文件:行号上一篇:P3-03 SONiC 控制面链路 | 下一篇:P3-05 sai-dpdk-mini一句话定位SAI(Switch Abstraction Interface)是一套纯 C 函数指针 API 集合:所有对象共用一个 sai_object_id_t 句柄,所有属性共用一个 sai_attribute_t 结构,所有能力通过 sai_api_query 按模块换函数表。看完头文件你会发现:它比内核的 net_device_ops 还要"接口化"——连对象都不是结构体,而是 ID。三个地基类型① OID:64 位不透明句柄(saitypes.h:131)typedef uint64_t sai_object_id_t;端口、VLAN、路由表项、交换机本身……万物皆 64 位。对象类型枚举(saitypes.h:188-227 节选):typedef enum _sai_object_type_t { SAI_OBJECT_TYPE_NULL = 0, /**< invalid object type */ // ... SAI_OBJECT_TYPE_ROUTE_ENTRY = 37, // ...OID 的高位编码对象类型、低位编码索引,所以 sai_object_type_query() 能从一个 OID 反查类型(sai.h:286)。对比内核:内核每种对象是一个 struct(net_device/fib_info),SAI 统一成一个整数——因为厂商 SDK 在进程边界之外,句柄比指针跨得了界。② attribute:id + union(saitypes.h:1815-1822)typedef struct _sai_attribute_t { /** @passparam meta */ sai_attr_id_t id; /** @passparam meta */ sai_attribute_value_t value; } sai_attribute_t;value 是个大 union(bool/u8/u16/u32/u64/oid/指针全塞里面)。所有 API 的参数都是 attr_count + attr_list——新增属性不破坏二进制接口,这是 SAI 能兼容十多家 ASIC 厂商的协议基础。③ API 表:每个模块一张函数指针表sai_switch_api_t(saiswitch.h:4261-4279)节选:typedef struct _sai_switch_api_t { sai_create_switch_fn create_switch; sai_remove_switch_fn remove_switch; sai_set_switch_attribute_fn set_switch_attribute; sai_get_switch_attribute_fn get_switch_attribute; sai_get_switch_stats_fn get_switch_stats; // ... } sai_switch_api_t;应用启动时 sai_api_query(SAI_API_SWITCH, ...) 换出这张表,剩下全是函数指针间接调用。没有虚函数、没有对象模型、没有 ABI 版本地狱——C ABI 是跨厂商 SDK 的唯一公约数。15 个 MVP API 矩阵#API签名位置作用1sai_create_switch_fnsaiswitch.h:4113创建/连接交换机2sai_create_port_fnsaiport.h:4296创建端口3sai_set_port_attribute_fnsaiport.h:4320端口 admin up/down4sai_create_vlan_fnsaivlan.h:563创建 VLAN5sai_create_fdb_entry_fnsaifdb.h:357静态 MAC 表项6sai_create_route_entry_fnsairoute.h:195L3 路由7sai_create_neighbor_entry_fnsaineighbor.h:219ARP 邻接8sai_create_next_hop_fnsainexthop.h:297下一跳对象9sai_create_acl_table_fnsaiacl.h:3713ACL 表10sai_create_hostif_fnsaihostif.h:1003上送 CPU 的 host 接口11sai_send_hostif_packet_fnsaihostif.h:1385从 CPU 发包12sai_packet_event_notification_fnsaihostif.h:1442收包回调(RX trap)13set_switch_attribute(switch 级属性)saiswitch.h:4265switch 级属性14sai_api_query 换表 + sai_remove_* 删除族sai.h:252 / 同名文件相邻行全生命周期读两个有代表性的完整定义:saifdb.h:357-360:typedef sai_status_t (*sai_create_fdb_entry_fn)( _In_ const sai_fdb_entry_t *fdb_entry, _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list);saihostif.h:1385-1390:typedef sai_status_t (*sai_send_hostif_packet_fn)( _In_ sai_object_id_t hostif_id, _In_ sai_size_t buffer_size, _In_ const void *buffer, _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list);套路完全一致:entry 结构描述 key(sai_fdb_entry_t 是 MAC+VLAN+port,sai_route_entry_t 是 prefix,saineighbor.h:206 的 neighbor 是 IP+rif),属性列表描述 value。把 key 和 value 分开是 SAI 和内核 API 最大的风格差异。🐧 内核对照:net_device_ops 的"每对象一张表"内核把操作挂在对象自身上(include/linux/netdevice.h:1410 起),tun 驱动实例(drivers/net/tun.c:1251-1262):static const struct net_device_ops tun_netdev_ops = { .ndo_init = tun_net_init, .ndo_uninit = tun_net_uninit, .ndo_open = tun_net_open, .ndo_stop = tun_net_close, .ndo_start_xmit = tun_net_xmit, // ... };维度内核 net_deviceSAI对象表示struct net_device(结构体+指针)sai_object_id_t(64 位句柄)操作接口net_device_ops 挂在对象里每模块一张全局 API 表属性访问内核态直接读写字段set/get_attribute(id+union)表项对象fib_info/neighbour 结构体entry 结构体(key 值分离)跨进程不可(内核态)可(序列化成 Redis/grpc)热插拔实现指针直接替换换 API 表指针SAI 是为"控制面和数据面分属不同进程/不同厂商"设计的——它的每个设计决策(句柄、属性 union、函数指针表)都在为进程边界和二进制兼容让路。这正好解释 P3-03 里 orchagent 和 syncd 为什么能隔着 Redis 工作。面试 3 问SAI 为什么不把属性做成结构体字段? → sai_attribute_t = id + union(saitypes.h:1815-1822),增删属性不改 ABI;对比内核直接访问 net_device 字段,SAI 必须保证跨厂商 SDK 的二进制兼容。sai_fdb_entry_t 和 attribute 怎么分工? → entry 描述 key(MAC/VLAN/port,saifdb.h:357 参数),attr_list 描述 value(如端口绑定)——和内核把 key、value 都塞进一个结构体的习惯不同。为什么 OID 用 64 位整数而不是指针? → 对象在厂商 SDK 进程里,跨进程只能传句柄(saitypes.h:131);类型编码进 OID 高位,sai_object_type_query 反查(sai.h:286)。🔧 动手实验把 references/sai-headers/inc/ 下 10 个头文件的 typedef sai_status_t (*sai_create_* 全部 grep 出来,做一张"对象 × 模块"对照表。写一个只含 sai_api_query 换表 + create_switch(INIT_SWITCH=false)的最小程序链接 sairedis。产出 perf-data/P3/04-sai-api-matrix.md。下一篇:P3-05 · sai-dpdk-mini:用 DPDK 实现 SAI 最小适配器
2026年08月18日
3 阅读
0 评论
0 点赞
第 1/1 页 · 本页 2 条 · 共 2 条