对应路线图:Phase 3 (Day 92-119) | DPDK 24.11.6 LTS | 内核对照 v6.6
上一篇:P3-02 OVS-DPDK 调优 | 下一篇:P3-04 SAI 头文件全景
一句话定位
SONiC 把"交换机配置"拆成一条Redis 接力链:业务进程写 APPL_DB → orchagent 消费、翻译成 SAI 对象写 ASIC_DB → syncd 消费、调厂商 SDK 驱动 ASIC。控制面与数据面之间唯一的耦合点是 Redis 的 key-value 协议——谁写谁读、写成什么格式,全部由这套协议定义。
全景:Redis 六库与三进程
数据库名是 sairedis 的公开常量(lib/sairediscommon.h:104-106):ASIC_DB/STATE_DB/COUNTERS_DB/FLEX_COUNTER_DB,加上 CONFIG_DB、APPL_DB 就是六库。职责一句话版:CONFIG_DB 存静态配置;APPL_DB 是业务进程(BGP/LLDP/Port)的目标意图;ASIC_DB 是翻译后的 SAI 对象;STATE_DB 是 ASIC 实际状态;COUNTERS_DB/FLEX_COUNTER_DB 是统计。本篇只跟 APPL_DB → ASIC_DB 这条主线。
第一棒:orchagent 从 APPL_DB 消费任务
orchagent 是 swss 的核心,每个业务一个 Orch 子类(PortOrch、RouteOrch……),任务处理入口 Orch::doTask(orchagent/orch.cpp:919-955,节选):
void Orch::doTask()
{
// limit the number of tasks moved from RetryMap to SyncMap in one iteration
// to avoid starvation of new tasks in SyncMap
auto threshold = gBatchSize == 0 ? 30000 : gBatchSize;
size_t count = 0;
for (auto &it : m_consumerMap)
{
try
{
count += retryToSync(it.first, threshold - count);
it.second->drain();
}
catch (const std::invalid_argument& e)
{
SWSS_LOG_ERROR("Exception caught: type=invalid_argument, table=%s, orch=%s, error=%s",
it.first.c_str(), typeid(*this).name(), e.what());
}
// ...(其余 catch 分支省略)
}
}m_consumerMap 把表名映射到 Consumer,每个 Orch 通过它声明"我订阅 APPL_DB 的哪些表"。消费动作(orch.cpp:455-456):subTable->pops(entries) → addToSync(entries)。Redis 表被当成队列:pops 取出 KeyOpFieldsValuesTuple(key + SET/DEL + field:value 列表),addToSync 交给子类 doTask 处理。RouteOrch 收到一条 ROUTE_TABLE:192.168.1.0/24 的 SET,就调 SAI 接口创建路由对象。
第二棒:ClientSai 把 SAI 调用序列化成 ASIC_DB 写操作
swss 链接 sairedis 的客户端库——把标准 SAI C API 变成 Redis 写。ClientSai::create(lib/ClientSai.cpp:112-209,节选):
sai_status_t ClientSai::create(
_In_ sai_object_type_t objectType,
_Out_ sai_object_id_t* objectId,
_In_ sai_object_id_t switchId,
_In_ uint32_t attr_count,
_In_ const sai_attribute_t *attr_list)
{
MUTEX();
// NOTE: in client mode, each create oid must be retrieved from server since
// server is actually creating new oid, and it's value must be transferred
// over communication channel
*objectId = SAI_NULL_OBJECT_ID;
// ...(INIT_SWITCH=false 的握手分支,省略)...
auto status = create(
objectType,
sai_serialize_object_id(switchId),
attr_count,
attr_list);
if (status == SAI_STATUS_SUCCESS)
{
// since user requested create, OID value was created remotely and it
// was returned in m_lastCreateOids
*objectId = m_lastCreateOids.at(0);
}
// ...
}三个关键点:① OID 由对端分配——orchagent 传 SAI_NULL_OBJECT_ID,真正的 OID 是 syncd 回传的(m_lastCreateOids);② switch ID 也要序列化成字符串做 key;③ 全程互斥(MUTEX())——Redis 通信通道是复用的。写出的 key 形如 ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY:{...},value 是 SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID:oid:0x... 这类 field:value——这套文本协议同时是 ASIC_DB 的 schema。
第三棒:syncd 消费 ASIC_DB,调厂商 SDK
syncd 侧走 RedisRemoteSaiInterface(服务端模式)。请求-响应类消息走独立频道,notifySyncd(lib/RedisRemoteSaiInterface.cpp:2236-2266)是典型样例:
auto key = sai_serialize(redisNotifySyncd);
// we need to use "GET" channel to be sure that
// all previous operations were applied, ...
m_communicationChannel->set(key, entry, REDIS_ASIC_STATE_COMMAND_NOTIFY);
auto status = waitForNotifySyncdResponse();普通 SAI 调用走 ASIC_DB 的 key-value 写,notify/GET 请求走 REDIS_ASIC_STATE_COMMAND_NOTIFY 频道,响应回来在 REDIS_TABLE_GETRESPONSE 表(sairediscommon.h:100)。syncd 处理完 create 把 OID 写回响应表,orchagent 的 m_lastCreateOids 才有值——闭环。最终 syncd 持有一张真实的 sai_switch_api_t 表(references/sai-headers/inc/saiswitch.h:4261-4279),一路调进厂商 SDK(BCM/MLNX 或 vslib)。
🐧 内核对照:rtnetlink 是内核的"ASIC_DB"
ip route add 最终进 rtnl_newlink(net/core/rtnetlink.c:3744-3757):
static int rtnl_newlink(struct sk_buff *skb, struct nlmsghdr *nlh,
struct netlink_ext_ack *extack)
{
struct rtnl_newlink_tbs *tbs;
int ret;
tbs = kmalloc(sizeof(*tbs), GFP_KERNEL);
if (!tbs)
return -ENOMEM;
ret = __rtnl_newlink(skb, nlh, tbs, extack);
kfree(tbs);
return ret;
}| 环节 | 内核 | SONiC |
|---|---|---|
| 用户接口 | netlink socket(RTM_NEWLINK) | Redis 写(APPL_DB) |
| 消息格式 | nlmsghdr + nlattr 二进制 TLV | 文本 key + field:value |
| 翻译层 | rtnetlink 直接改 net_device | orchagent 翻译成 SAI 对象 |
| 执行层 | 内核驱动(ndo_*) | syncd → 厂商 SAI SDK |
同一张架构图,两种实现:用户态请求 → 统一入口 → 翻译 → 驱动执行。SONiC 用 Redis 因为控制面要多进程、可热重启、可容器化——Redis 天然是进程间的共享内存 + 队列;内核用 netlink 因为要服务全系统且严格校验(extack 报错路径)。
面试 4 问
- 为什么 create 的 OID 不由 orchagent 分配? → 服务端才真正创建对象,OID 必须从 syncd 经通信通道回传(
ClientSai.cpp:123-125注释、190行m_lastCreateOids)。 - APPL_DB 和 ASIC_DB 的区别? → APPL_DB 是意图("我想要一条路由"),ASIC_DB 是翻译后的 SAI 对象("SAI_ROUTE_ENTRY + NEXT_HOP_ID"),中间隔着 orchagent 的翻译。
- syncd 和 orchagent 之间除了 key-value 还有什么通道? → 请求-响应类走独立频道,如
REDIS_ASIC_STATE_COMMAND_NOTIFY(RedisRemoteSaiInterface.cpp:2260),避免与 op 队列竞争(2248-2255注释)。 - doTask 的 threshold 为什么存在? → 限制单轮从 RetryMap 搬进 SyncMap 的任务数(默认 30000),防止重试任务饿死新任务(
orch.cpp:921-923)。
🔧 动手实验
# 观察一条路由的完整旅程
redis-cli -n 0 HSET "ROUTE_TABLE:10.0.0.0/24" "nexthop" "10.0.0.1" # APPL_DB
redis-cli -n 1 KEYS "ASIC_STATE*" # ASIC_DB 出现序列化后的 SAI 路由
redis-cli -n 6 KEYS "*" # STATE_DB 出现 ASIC 实际状态
docker exec -it syncd redis-cli -n 1 MONITOR # 看谁在消费产出 perf-data/P3/03-sonic-path.md:APPL_DB 写入 → ASIC_DB 出现 → STATE_DB 落盘的耗时三段计时表。
评论 (0)