SONiC 整体框架:一条配置如何走到 ASIC

摘要

SONiC 不是一个单独进程,而是一套运行在 Linux 上、由容器化服务和数据库协作组成的网络操作系统。它把用户配置、协议计算、硬件编排和芯片适配拆成不同层:上层表达"想要什么",orchagent 解决依赖并生成 SAI 对象,SAI 提供厂商无关接口,syncd 调用 vendor SAI,最后由厂商 SDK 编程 ASIC。

初学者可以先记住这条主线:

text 复制代码
用户配置或协议结果
  -> CONFIG_DB / APPL_DB
  -> Consumer / 具体 Orch
  -> 标准 SAI API
  -> libsairedis
  -> syncd
  -> vendor SAI
  -> 厂商 SDK
  -> ASIC

这里写 APPL_DB,因为这是本地代码使用的数据库名;很多资料会把它口语化写成 APP_DB,两者通常指同一个应用数据库概念。

关键词

SONiC、Redis、CONFIG_DB、APPL_DB、ASIC_DB、orchagent、SAI、sairedis、syncd、VendorSai、SDK、ASIC、VID、RID、warm restart


1. 先建立正确的整体直觉

把 SONiC 想成一家分工明确的工厂:

  • CLI、管理接口和协议进程提交订单。
  • Redis DB 保存配置、意图、状态和硬件对象视图。
  • cfgmgr、*syncd 等进程负责把不同来源的数据整理成统一格式。
  • orchagent 判断依赖是否满足,并把业务对象翻译成 SAI 对象。
  • libsairedis 把进程内的 SAI 调用变成可以跨进程传输的请求。
  • syncd 接收请求,维护对象映射,调用 vendor SAI。
  • vendor SAI 把标准 SAI 对象转换成厂商 SDK 能理解的操作。
  • SDK 最终修改芯片表项和资源。

最重要的边界是:标准 SONiC 上层一般不直接调用厂商 SDK。上层面向 SAI 编程,芯片差异被压在 vendor SAI 和 SDK 一侧。


2. 每一层分别负责什么

层级 代表组件 主要职责 不应混淆的点
管理层 CLI、REST、gNMI、配置文件 接收配置和运维请求 写入配置不等于硬件已经生效
协议层 FRR、teamd、lldpd 运行协议并产生控制面结果 协议 RIB 不等于 ASIC FIB
数据库层 CONFIG_DB、APPL_DB、STATE_DB 等 解耦进程并保存不同语义的数据 各 DB 不是简单的多份副本
管理转换层 portmgrd、vlanmgrd、fpmsyncd 把配置、内核或协议结果写成应用意图 不同业务不一定走完全相同路径
编排层 orchagent、各种 *Orch 校验依赖、构造 SAI 对象、重试 它负责业务编排,不是厂商驱动
SAI 客户端层 libsairedis、metadata、远端接口 校验并序列化 SAI 请求 SAI 函数调用不代表当前线程直接进 SDK
执行层 syncd 消费请求、转换 VID/RID、调用 vendor SAI syncd 不是 vendor SAI 本身
平台层 vendor SAI、SDK 实现 SAI 并编程 ASIC 大量实现可能以二进制包交付

SONiC 官方架构把 orchestration agent 描述为 APP 表与 ASIC 表之间的转换者,把 syncd 描述为 ASIC_DB 与 SAI SDK 之间的同步进程。


3. Redis DB:同样是数据,语义完全不同

数据库 主要语义 典型内容 排障问题
CONFIG_DB 持久化配置意图 PORT、VLAN、VLAN_MEMBER、ACL、BGP 邻居 用户想配置什么
APPL_DB 面向编排层的业务意图 PORT_TABLE、VLAN_TABLE、ROUTE_TABLE、NEIGH_TABLE 上层是否已把意图交给 orchagent
ASIC_DB 序列化后的 SAI 对象视图和请求通道 ASIC_STATE:SAI_OBJECT_TYPE_* orchagent 是否已形成 SAI 操作
STATE_DB 软件运行状态和依赖状态 端口、接口、warm restart 等状态 依赖是否就绪、软件认为状态如何
COUNTERS_DB 对象计数和映射 端口、队列、ACL 等计数 硬件统计是否可读
FLEX_COUNTER_DB 灵活计数器配置 轮询组和对象开关 哪些计数器应被采集

注意两个常见误区:

  1. APPL_DB 出现表项,只能证明业务意图已经到达该层,不能单独证明 orchagent、syncd 和硬件都成功。
  2. ASIC_DB 出现对象,也不能单独证明 ASIC 已完成编程;还要结合 syncd 日志、SAI 返回值、平台命令或数据面测试。

4. 容器、进程和目录怎样对应

关注对象 常见所在位置 本地源码入口
数据库服务 database 容器 files、dockers 下的数据库构建和启动模板
orchagent、管理进程 swss 容器 src/sonic-swss/orchagent、src/sonic-swss/cfgmgr
FRR、zebra、fpmsyncd bgp 容器 src/sonic-frr、src/sonic-swss/fpmsyncd
syncd 平台相关 syncd 容器 src/sonic-sairedis/syncd、platform/*/docker-syncd-*.mk
SAI Redis 客户端 被调用方进程加载的库 src/sonic-sairedis/lib
vendor SAI、SDK syncd 镜像中的平台包 platform/broadcom、platform/mellanox 等

容器是部署边界,DB 表和通知通道是通信边界,C++ 类和函数是源码边界。读代码时把三者分开,链路会清晰很多。


5. 源码地图与证据表

结论 本地路径 关键符号 证据等级
orchagent 连接 APPL_DB、CONFIG_DB、STATE_DB src/sonic-swss/orchagent/main.cpp DBConnector appl_db/config_db/state_db 本地代码直接证明
orchagent 初始化 SAI API 表 src/sonic-swss/orchagent/saihelper.cpp initSaiApi()、sai_api_initialize()、sai_api_query() 本地代码直接证明
Consumer 把 DB 事件交给具体 Orch src/sonic-swss/orchagent/orch.cpp Consumer::execute()、Orch::doTask() 本地代码直接证明
OrchDaemon 使用 Select 驱动事件循环 src/sonic-swss/orchagent/orchdaemon.cpp OrchDaemon::init()、OrchDaemon::start() 本地代码直接证明
libsairedis Context 串起 metadata 和远端接口 src/sonic-sairedis/lib/Context.cpp m_meta、m_redisSai 本地代码直接证明
远端接口支持 Redis 与 ZeroMQ 通道 src/sonic-sairedis/lib/RedisRemoteSaiInterface.cpp RedisChannel、ZeroMQChannel 本地代码直接证明
syncd 分发 create/remove/set/get src/sonic-sairedis/syncd/Syncd.cpp processSingleEvent()、processQuadEvent() 本地代码直接证明
VendorSai 查询并调用真实 SAI API src/sonic-sairedis/syncd/VendorSai.cpp initialize()、create()、set() 本地代码直接证明
SAI 提供厂商无关的转发元素控制接口 OCP SAI 官方规范 SAI Adapter、API、对象模型 上游通用机制
vendor SAI 内部如何调用具体 SDK 厂商二进制包或闭源代码 厂商私有实现 平台或设备待验证

6. orchagent:从 DB 事件进入业务逻辑

6.1 启动主线

本地 src/sonic-swss/orchagent/main.cpp 能看到以下顺序:

  1. 创建 APPL_DB、CONFIG_DB、STATE_DB 连接。
  2. 调用 initSaiApi()。
  3. 通过 sai_switch_api->create_switch() 创建或连接 switch 对象。
  4. 创建 OrchDaemon 或 FabricOrchDaemon。
  5. 调用 orchDaemon->init() 创建各业务 Orch。
  6. 调用 orchDaemon->start() 进入事件循环。

当前 checkout 中对应的重要位置包括 main.cpp:460、main.cpp:677、main.cpp:795 和 main.cpp:810。行号会随代码变化,长期引用时应同时保留函数名。

6.2 Consumer 和 Orch

Orch 是编排类的基础框架,具体业务类包括:

  • PortsOrch:端口、VLAN、LAG、bridge port。
  • RouteOrch:路由对象与下一跳选择。
  • NeighOrch:邻居和下一跳。
  • FdbOrch:FDB。
  • AclOrch:ACL。
  • QosOrch、BufferOrch:QoS 和 buffer。

Consumer 订阅表项变化,取出 KeyOpFieldsValuesTuple 后调用对应 Orch 的 doTask(Consumer&)。如果依赖没有满足,任务可以留在待处理队列中,等待后续事件重试。

这说明 orchagent 不是"收到一条 Redis 消息就盲目下发"。它还要处理对象顺序、引用关系、资源和失败重试。


7. SAI、libsairedis 与 syncd:不要把边界混成一层

7.1 SAI 是标准接口,不是某家 SDK

SAI 定义统一的对象和操作。例如:

text 复制代码
sai_port_api->set_port_attribute()
sai_vlan_api->create_vlan_member()
sai_neighbor_api->create_neighbor_entry()
sai_next_hop_api->create_next_hop()
sai_route_api->create_route_entry()

src/sonic-swss/orchagent/saihelper.cpp 的 initSaiApi() 先调用 sai_api_initialize(),再通过多个 sai_api_query() 获取 switch、port、VLAN、neighbor、next hop、route、ACL 等 API 函数表。

7.2 libsairedis 做了什么

本地代码比一句"SAI 调用写 Redis"更完整:

  • Sai.cpp 暴露 SAI 操作并把调用交给当前 Context。
  • Context.cpp 构造 saimeta::Meta 与 RedisRemoteSaiInterface,metadata 包裹远端接口。
  • metadata 进行对象类型、属性和生命周期等检查。
  • RedisRemoteSaiInterface 序列化 key 与属性,通过通信通道发给远端执行者。

src/sonic-sairedis/lib/ClientSai.cpp 也是当前源码中的客户端实现,但不能把所有运行模式都简化成同一个固定类调用链。文章采用更稳定的边界表述:orchagent 调 SAI;所加载的 sairedis 客户端实现负责 metadata、序列化和跨进程通信。

7.3 Redis 与 ZeroMQ 都可能出现

RedisRemoteSaiInterface.cpp 同时构造 RedisChannel 和 ZeroMQChannel。Redis async 是典型默认模式;当 context 配置启用 ZMQ 时,会创建 ZeroMQ 通道并强制同步模式。orchagent 的命令行也提供 ZMQ 相关参数。

因此,适合初学者的主线可以写"经 ASIC_DB/Redis 到 syncd",但必须补充:当前实现存在 ZeroMQ 通信模式,实际链路取决于镜像、context 与启动配置。

7.4 syncd 与 VendorSai

Syncd::processSingleEvent() 根据命令类型分派 create、remove、set、get;Syncd::processQuadEvent() 进一步解析对象类型、VID、属性,并处理普通执行或 init view 等模式。

VendorSai::initialize() 调用真实 SAI 的 sai_api_initialize 和 sai_api_query,保存各对象 API 表。后续 VendorSai::create/set/remove/get 再调用对应 vendor API。

所以三者边界可以这样记:

text 复制代码
libsairedis:把调用变成可传输请求
syncd:解释请求、维护状态并组织执行
VendorSai/vendor SAI:进入厂商实现

8. SAI 对象与 metadata:为什么不是简单透传

SAI 既有 OID 对象,也有 entry 对象:

  • OID 对象:switch、port、VLAN、next hop、ACL table 等。
  • entry 对象:route entry、neighbor entry、FDB entry 等,通常由结构体 key 标识。

四类常见操作是 create、remove、set、get。批量接口还包括 bulk create/remove/set 等。

metadata 的价值包括:

  • 根据对象类型找到合法属性描述。
  • 校验必填属性、枚举值和对象引用。
  • 统一序列化和反序列化需要的元信息。
  • 跟踪对象生命周期与通知数据。
  • 让 VS、sairedis 和测试共享同一套对象语义。

metadata 能证明软件层的类型和规则检查,不能证明某个型号 ASIC 一定有足够资源或支持所有属性。平台能力仍要看 SAI capability、vendor 文档和设备实测。


9. 典型流程一:端口 MTU 如何下发

以 Ethernet0 的 MTU 为例,当前本地代码可以确认这条主线:

  1. CONFIG_DB 的 PORT 配置被 PortMgr::doTask() 读取。
  2. PortMgr::setPortMtu() 先处理 Linux netdev MTU,并把 mtu 写入应用侧 PORT 表。
  3. PortsOrch 消费端口任务并调用 PortsOrch::setPortMtu()。
  4. PortsOrch 构造 SAI_PORT_ATTR_MTU,调用 sai_port_api->set_port_attribute()。
  5. 请求经 sairedis 和 syncd 进入 vendor SAI。

关键路径:

  • src/sonic-swss/cfgmgr/portmgr.cpp:PortMgr::doTask()、PortMgr::setPortMtu()。
  • src/sonic-swss/orchagent/portsorch.cpp:PortsOrch::setPortMtu()、SAI_PORT_ATTR_MTU。

如果 CONFIG_DB 正确但端口 MTU 没变化,要分别检查 Linux netdev、APPL_DB、orchagent 日志、ASIC_DB/通信模式和 syncd/SAI 返回值。


10. 典型流程二:VLAN member 如何下发

把 Ethernet0 加入 Vlan100 时,业务至少涉及 VLAN 对象、bridge port 和 VLAN member 三类依赖。

本地代码中的主要路径是:

  1. VlanMgr::doVlanMemberTask() 消费 CONFIG_DB 的 VLAN_MEMBER 配置。
  2. vlanmgrd 把应用意图写到 APPL_DB 的 VLAN member 表。
  3. PortsOrch::doVlanMemberTask() 检查 VLAN、端口和 bridge port 是否就绪。
  4. 满足依赖后构造 SAI_VLAN_MEMBER_ATTR_VLAN_ID、BRIDGE_PORT_ID 和 VLAN_TAGGING_MODE。
  5. 调用 sai_vlan_api->create_vlan_member()。
  6. 对 untagged 成员,还可能需要设置端口 PVID。

"VLAN 已创建"和"端口已成为 VLAN member"是两个不同观察点。排障时不要只看 show vlan brief 的一列结果。


11. 典型流程三:BGP 路由怎样进入 ASIC

这里选一条从 FRR 学到的路由,而不是笼统说"静态路由一定来自 CONFIG_DB"。典型链路是:

  1. bgpd 学到前缀并交给 zebra。
  2. zebra 完成选路,把 FIB 更新通过 FPM 发给 fpmsyncd。
  3. RouteSync::onMsg() 解析 netlink route 消息。
  4. m_routeTable.set() 把路由写入 APPL_DB ROUTE_TABLE。
  5. RouteOrch::doTask() 读取前缀、下一跳、接口和权重等字段。
  6. 下一跳未就绪时等待 NeighOrch/下一跳对象;就绪后调用 RouteOrch::addRoute()。
  7. 最终形成 sai_route_api->create_route_entry() 或 set 操作。

关键路径:

  • src/sonic-swss/fpmsyncd/routesync.cpp:RouteSync::onMsg()、m_routeTable.set()。
  • src/sonic-swss/orchagent/routeorch.cpp:RouteOrch::doTask()、RouteOrch::addRoute()。
  • src/sonic-swss/orchagent/neighorch.cpp:邻居和下一跳管理。

路由排障至少要区分四个状态:FRR RIB、zebra FIB、APPL_DB ROUTE_TABLE、ASIC_DB/硬件 FIB。某一层存在不代表后续层一定成功。


12. 反向链路:硬件事件如何回到上层

硬件不仅接受配置,也会主动产生事件,例如端口状态变化、FDB 学习或老化、BFD 状态、PFC deadlock 和 ASIC SDK health event。

本地 src/sonic-sairedis/syncd/NotificationProcessor.cpp 包含这些通知的处理与发送函数。大体方向是:

text 复制代码
ASIC / SDK 事件
  -> vendor SAI callback
  -> syncd NotificationProcessor
  -> sairedis 通知通道
  -> orchagent 对应通知处理器
  -> DB 状态、业务动作或告警

12.1 warm restart 与 init/apply view

warm restart 的目标不是"重启后重新刷一遍全部对象",而是在尽量保留转发面的同时,让恢复后的控制面重新建立并校验期望状态。

当前 Syncd.cpp 能看到:

  • 默认 warm boot 文件 /var/warmboot/sai-warmboot.bin。
  • SAI_KEY_WARM_BOOT_READ_FILE、SAI_KEY_WARM_BOOT_WRITE_FILE。
  • SAI_REDIS_NOTIFY_SYNCD_INIT_VIEW 与 APPLY_VIEW。
  • processQuadEventInInitViewMode() 和 apply view 比较/执行相关逻辑。

是否真正无损仍取决于镜像配置、各 daemon 的 warm restart 支持、vendor SAI 和设备行为,不能只根据存在这些函数就宣称业务无中断。


13. VID 和 RID:为什么对象 ID 有两套

名称 含义 谁主要使用
VID Virtual Object ID,虚拟对象标识 orchagent、ASIC_DB 和 sairedis 一侧
RID Real Object ID,vendor SAI 返回的真实对象标识 syncd 调用 vendor SAI 时

syncd 侧的 VirtualOidTranslator、VidManager 等组件维护转换关系。这样做可以让上层对象标识与具体 vendor SAI 进程中的真实句柄解耦,也为 warm restart、多个 switch context 等场景提供管理基础。

并非所有 SAI 对象都用 OID。route、neighbor、FDB 这类 entry 对象的 key 本身是结构体,内部仍可能引用需要翻译的对象 ID。


14. 平台 SAI/SDK 是怎样接进构建系统的

本地 platform 目录可以直接证明"构建系统如何选择和装入平台包",但通常不能展示完整 vendor SAI/SDK 内部实现。

14.1 Broadcom

  • platform/broadcom/sai.mk 定义 Broadcom SAI 包。
  • platform/broadcom/docker-syncd-brcm.mk 组织平台 syncd 镜像。

14.2 Mellanox/NVIDIA

  • platform/mellanox/mlnx-sai.mk 定义 Mellanox SAI 包。
  • platform/mellanox/sdk.mk 定义相关 SDK 包。
  • 对应 docker-syncd-mlnx 规则把组件装入平台镜像。

14.3 VS

  • platform/vs/syncd-vs.mk 构建 VS 平台的 syncd。
  • src/sonic-sairedis/vslib 提供虚拟交换机 SAI 行为,适合开发与测试,但不等同于真实 ASIC 性能和限制。

这里最稳妥的结论是"这些规则负责包和镜像集成"。某个 SAI 属性是否受支持、资源上限是多少、SDK 最终调用了哪个芯片 API,都需要对应平台资料或设备验证。


15. 一套实用的分层排障方法

15.1 第一步:配置是否存在

bash 复制代码
sonic-db-cli CONFIG_DB keys '*'
sonic-db-cli CONFIG_DB hgetall 'PORT|Ethernet0'

15.2 第二步:应用意图是否形成

bash 复制代码
sonic-db-cli APPL_DB keys 'PORT_TABLE*'
sonic-db-cli APPL_DB keys 'VLAN*'
sonic-db-cli APPL_DB keys 'ROUTE_TABLE*'

15.3 第三步:orchagent 是否形成 SAI 对象

bash 复制代码
sonic-db-cli ASIC_DB keys 'ASIC_STATE:*'
grep orchagent /var/log/syslog
docker exec swss supervisorctl status

如果目标镜像使用 ZeroMQ 模式,不能只靠 ASIC_DB 队列现象推断通信是否正常,还要核对 context 和启动参数。

15.4 第四步:syncd 和 vendor SAI 是否成功

bash 复制代码
grep syncd /var/log/syslog
docker exec syncd supervisorctl status
show logging

重点看 SAI status、资源不足、属性不支持、对象依赖和 SDK 错误。

15.5 第五步:业务状态和数据面是否一致

bash 复制代码
show interfaces status
show vlan brief
vtysh -c 'show ip route'
vtysh -c 'show bgp summary'

最终仍应配合流量、计数器、邻居/FDB/FIB 查询或平台诊断工具验证。软件 DB 正确不是数据面成功的充分条件。

现象 优先检查
CONFIG_DB 有配置,APPL_DB 没意图 对应 cfgmgr、配置格式、依赖状态
APPL_DB 有意图,ASIC_DB/执行通道无动作 orchagent 日志、依赖对象、Consumer 队列
SAI 请求已形成,syncd 报错 对象顺序、属性支持、资源和 vendor SAI
DB 都正常,业务流量仍失败 ASIC 状态、平台限制、邻居/FDB/FIB 和数据面测试
warm restart 后不一致 daemon 恢复状态、init/apply view、VID/RID、vendor warm boot

16. 推荐的源码阅读顺序

第一阶段:只看主框架

  1. src/sonic-swss/orchagent/main.cpp
  2. src/sonic-swss/orchagent/saihelper.cpp
  3. src/sonic-swss/orchagent/orch.cpp
  4. src/sonic-swss/orchagent/orchdaemon.cpp

目标:理解进程如何启动、Consumer 如何执行、Orch 如何进入事件循环。

第二阶段:选择一个业务闭环

  • 端口:cfgmgr/portmgr.cpp 与 orchagent/portsorch.cpp。
  • VLAN:cfgmgr/vlanmgr.cpp 与 orchagent/portsorch.cpp。
  • 路由:fpmsyncd/routesync.cpp、routeorch.cpp、neighorch.cpp。

目标:从生产者、DB 表、消费者一直追到具体 SAI 属性。

第三阶段:看 SAI 到平台

  1. src/sonic-sairedis/lib/Sai.cpp
  2. src/sonic-sairedis/lib/Context.cpp
  3. src/sonic-sairedis/lib/RedisRemoteSaiInterface.cpp
  4. src/sonic-sairedis/meta
  5. src/sonic-sairedis/syncd/Syncd.cpp
  6. src/sonic-sairedis/syncd/VendorSai.cpp
  7. platform/<vendor> 对应构建规则

目标:理解 metadata、通信模式、事件分发、VID/RID 和 vendor 边界。

第四阶段:再看扩展架构

当前 main.cpp 已包含 VOQ、fabric、DPU 和 ZMQ 等分支,SAI 也包含 DASH、SRv6 等 API。先掌握经典单 ASIC 主线,再按具体需求进入 multi-ASIC、VOQ、DASH 或 P4,学习成本更低。


17. 快速记忆

SONiC 以数据库和消息通道解耦各模块。用户配置通常进入 CONFIG_DB,管理进程或协议同步进程把业务意图写入 APPL_DB。orchagent 通过 Consumer 接收变化,由 PortsOrch、RouteOrch、NeighOrch 等模块检查依赖并构造标准 SAI 对象。orchagent 调用的是 SAI,不直接面向厂商 SDK。当前 sairedis 代码通过 metadata 和远端接口完成校验、序列化与跨进程通信,通信可以使用 Redis,也存在 ZeroMQ 模式。syncd 接收请求、维护 VID/RID 等状态,再通过 VendorSai 调真实 vendor SAI。vendor SAI 内部如何调用 SDK 由厂商实现,最终目标是编程 ASIC。

一句口诀:

text 复制代码
配置进 CONFIG,意图到 APPL;
Orch 管依赖,SAI 管抽象;
sairedis 传请求,syncd 管执行;
vendor SAI 接 SDK,最后写 ASIC。

结语

理解 SONiC 的关键不是背下所有 daemon,而是始终问四个问题:谁生产这条数据、它进入哪个 DB 或通道、谁消费并处理依赖、最后如何观察硬件结果。沿着这四个问题追踪,端口、VLAN、路由、FDB、ACL、VXLAN 等专题都会落回同一套清晰的方法。

相关推荐
ZOnePieceC2 小时前
消息队列之Kafka
后端
yunwei372 小时前
eBPF 入门实践教程十七:编写 eBPF 程序统计随机/顺序磁盘 I/O
linux·后端·性能优化
花间相见2 小时前
【计算基础|网络07】HTTPS(下):ECDHE 握手与优化
后端
QuantiCore_IO2 小时前
从请求风暴到可维护的数据管道:量化系统为什么需要批量接口?
后端·github·api
136096757232 小时前
.env 的三个必查项
后端
长安米粒贵2 小时前
接口超时了,为什么重试反而把系统打垮?聊聊超时预算的 4 个误区
后端
用户813267933252 小时前
量化系统为什么不应该为每只股票单独写一套数据获取逻辑?
后端·github·api
程序员Sunday2 小时前
Spring @Transactional 没回滚?按代理调用、异常和传播行为排查
java·后端·spring
付威20232 小时前
我用 100 行核心代码,做了一个能接入飞书的 Hermes 式智能体
人工智能·后端