摘要
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 |
灵活计数器配置 | 轮询组和对象开关 | 哪些计数器应被采集 |

注意两个常见误区:
- APPL_DB 出现表项,只能证明业务意图已经到达该层,不能单独证明
orchagent、syncd和硬件都成功。 - 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 能看到以下顺序:
- 创建
APPL_DB、CONFIG_DB、STATE_DB连接。 - 调用
initSaiApi()。 - 通过
sai_switch_api->create_switch()创建或连接 switch 对象。 - 创建
OrchDaemon或FabricOrchDaemon。 - 调用
orchDaemon->init()创建各业务 Orch。 - 调用
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 为例,当前本地代码可以确认这条主线:
CONFIG_DB的 PORT 配置被PortMgr::doTask()读取。PortMgr::setPortMtu()先处理 Linux netdev MTU,并把mtu写入应用侧 PORT 表。PortsOrch消费端口任务并调用PortsOrch::setPortMtu()。PortsOrch构造SAI_PORT_ATTR_MTU,调用sai_port_api->set_port_attribute()。- 请求经 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 三类依赖。
本地代码中的主要路径是:
VlanMgr::doVlanMemberTask()消费 CONFIG_DB 的 VLAN_MEMBER 配置。vlanmgrd把应用意图写到 APPL_DB 的 VLAN member 表。PortsOrch::doVlanMemberTask()检查 VLAN、端口和 bridge port 是否就绪。- 满足依赖后构造
SAI_VLAN_MEMBER_ATTR_VLAN_ID、BRIDGE_PORT_ID和VLAN_TAGGING_MODE。 - 调用
sai_vlan_api->create_vlan_member()。 - 对 untagged 成员,还可能需要设置端口 PVID。

"VLAN 已创建"和"端口已成为 VLAN member"是两个不同观察点。排障时不要只看 show vlan brief 的一列结果。
11. 典型流程三:BGP 路由怎样进入 ASIC
这里选一条从 FRR 学到的路由,而不是笼统说"静态路由一定来自 CONFIG_DB"。典型链路是:
bgpd学到前缀并交给 zebra。- zebra 完成选路,把 FIB 更新通过 FPM 发给
fpmsyncd。 RouteSync::onMsg()解析 netlink route 消息。m_routeTable.set()把路由写入 APPL_DBROUTE_TABLE。RouteOrch::doTask()读取前缀、下一跳、接口和权重等字段。- 下一跳未就绪时等待
NeighOrch/下一跳对象;就绪后调用RouteOrch::addRoute()。 - 最终形成
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. 推荐的源码阅读顺序
第一阶段:只看主框架
src/sonic-swss/orchagent/main.cppsrc/sonic-swss/orchagent/saihelper.cppsrc/sonic-swss/orchagent/orch.cppsrc/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 到平台
src/sonic-sairedis/lib/Sai.cppsrc/sonic-sairedis/lib/Context.cppsrc/sonic-sairedis/lib/RedisRemoteSaiInterface.cppsrc/sonic-sairedis/metasrc/sonic-sairedis/syncd/Syncd.cppsrc/sonic-sairedis/syncd/VendorSai.cppplatform/<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 等专题都会落回同一套清晰的方法。