对应路线图: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 | 签名位置 | 作用 |
|---|---|---|---|
| 1 | sai_create_switch_fn | saiswitch.h:4113 | 创建/连接交换机 |
| 2 | sai_create_port_fn | saiport.h:4296 | 创建端口 |
| 3 | sai_set_port_attribute_fn | saiport.h:4320 | 端口 admin up/down |
| 4 | sai_create_vlan_fn | saivlan.h:563 | 创建 VLAN |
| 5 | sai_create_fdb_entry_fn | saifdb.h:357 | 静态 MAC 表项 |
| 6 | sai_create_route_entry_fn | sairoute.h:195 | L3 路由 |
| 7 | sai_create_neighbor_entry_fn | saineighbor.h:219 | ARP 邻接 |
| 8 | sai_create_next_hop_fn | sainexthop.h:297 | 下一跳对象 |
| 9 | sai_create_acl_table_fn | saiacl.h:3713 | ACL 表 |
| 10 | sai_create_hostif_fn | saihostif.h:1003 | 上送 CPU 的 host 接口 |
| 11 | sai_send_hostif_packet_fn | saihostif.h:1385 | 从 CPU 发包 |
| 12 | sai_packet_event_notification_fn | saihostif.h:1442 | 收包回调(RX trap) |
| 13 | set_switch_attribute(switch 级属性) | saiswitch.h:4265 | switch 级属性 |
| 14 | sai_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_device | SAI |
|---|---|---|
| 对象表示 | 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。
评论 (0)