首页
关于
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-06 · host_if 打通:让 FRR/BGP 跑在纯软件转发上
对应路线图:Phase 3 (Day 92-119) | DPDK 24.11.6 LTS | 内核对照 v6.6上一篇:P3-05 sai-dpdk-mini | 下一篇:P4-01 PCIe一句话定位交换机光会转发没用,BGP/OSPF 这些协议要能收发包。SAI 为此定义了一整套 hostif(host interface)抽象:控制面报文(BGP 的 TCP 179、ARP、ICMP)从数据面 trap 上 CPU,控制面要发的包经 hostif 送回数据面——这就是把 FRR 接进纯软件转发的最后一公里。hostif 三件套:类型、创建、收发① 三种通道类型(saihostif.h:848-859)typedef enum _sai_hostif_type_t { /** Netdevice */ SAI_HOSTIF_TYPE_NETDEV, /** File descriptor */ SAI_HOSTIF_TYPE_FD, /** Generic netlink */ SAI_HOSTIF_TYPE_GENETLINK } sai_hostif_type_t;三种类型的语义差异:NETDEV 让 SAI 实现去创建一个 Linux 网卡(hostif 就是"内核能看到的那张卡");FD 用文件描述符收发包(最高效,syncd 直接读 socket);GENETLINK 走 generic netlink family。创建时的 SAI_HOSTIF_ATTR_TYPE 是 MANDATORY_ON_CREATE(saihostif.h:906-908): /** * @brief Host interface type * * @type sai_hostif_type_t * @flags MANDATORY_ON_CREATE | CREATE_ONLY */ SAI_HOSTIF_ATTR_TYPE = SAI_HOSTIF_ATTR_START,② 创建与收发(saihostif.h:1003-1007、1442-1447、1385-1390)typedef sai_status_t (*sai_create_hostif_fn)( _Out_ sai_object_id_t *hostif_id, _In_ sai_object_id_t switch_id, _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list); typedef void (*sai_packet_event_notification_fn)( _In_ sai_object_id_t switch_id, _In_ sai_size_t buffer_size, _In_ const void *buffer, _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list);(sai_send_hostif_packet_fn 与回调同构,只是返回值是 sai_status_t。)收包靠回调(packet_event 挂在 switch 属性上,saiswitch.h:1970-1972 注释),发包靠 send_hostif_packet_fn,全套在 sai_hostif_api_t(saihostif.h:1452 起)。端到端:一个 BGP 报文的旅程进(BGP 邻居 → FRR):数据面收到 TCP 179 报文 → 查表 miss(不是转发流量)→ 命中 trap 规则 → 沿 packet_event 回调进 syncd → syncd 经 NETDEV 写进 Linux 内核协议栈 → FRR 的 zebra/bgpd socket 收到。出(FRR → BGP 邻居):bgpd 发 TCP 179 报文 → 内核路由表把流量指向 hostif 网卡 → SAI 实现的 ndo_start_xmit(NETDEV 型)或 syncd 读 FD → 调 send_hostif_packet_fn → 数据面封装后从物理口发出。对比:内核 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, // ... };NETDEV 型 hostif 本质就是一个"SAI 实现的 tun":内核侧是标准网卡,驱动侧不是 DMA 而是 send_hostif_packet_fn。这是理解 hostif 最快的类比。设计取舍:NETDEV vs FD vs GENETLINK NETDEVFDGENETLINK内核集成原生(就是网卡)无,syncd 自己读需要 family 注册性能中(内核协议栈必经)高(绕过内核)低(netlink 序列化)实现复杂度低(tun 参考现成)中高适用快速打通 FRR生产数据面工具类通道学习路线选 NETDEV:sai-dpdk-mini 阶段第一目标是把 FRR 跑起来,NETDEV 让 BGP 报文"看起来像普通网卡流量",内核协议栈、FRR 全都不动。等 perf 数据显示 hostif 成为瓶颈,再切 FD 型绕内核。🐧 内核对照:tun 的收包路径tun_get_user(drivers/net/tun.c:1750)是字符设备读写的核心——应用写 /dev/net/tun 就进这里(节选):static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, void *msg_control, struct iov_iter *from, int noblock, bool more) { struct tun_pi pi = { 0, cpu_to_be16(ETH_P_IP) }; struct sk_buff *skb; // ... if (!(tun->flags & IFF_NO_PI)) { if (len < sizeof(pi)) return -EINVAL; len -= sizeof(pi); if (!copy_from_iter_full(&pi, sizeof(pi), from)) return -EFAULT; } // ...(IFF_TAP 时校验 ETH_HLEN 等,省略)...环节tun 驱动SAI hostif (NETDEV)内核侧对象/dev/net/tun 字符设备标准 net_device发包进"网卡"写 char dev → tun_get_userndo_start_xmit → send_hostif_packet_fn收包出"网卡"tun_net_xmit → skb 进协议栈packet_event 回调 → 内核协议栈数据面另一端用户态进程读 fdSAI 数据面(硬件或 DPDK)方向正好相反:tun 的"网线那头"是用户态进程,hostif 的"网线那头"是交换机数据面。同一份 net_device_ops 机制,两端换了个位置。面试 3 问三种 hostif 类型的区别? → NETDEV 创建 Linux 网卡、FD 用文件描述符、GENETLINK 走 generic netlink(saihostif.h:848-859);学习期选 NETDEV 最快打通 FRR。BGP 报文怎么从数据面到 FRR? → trap 命中 → packet_event 回调(saihostif.h:1442)→ syncd 经 NETDEV 入内核协议栈 → bgpd socket。hostif 和 tun 是什么关系? → NETDEV 型 hostif 就是"SAI 实现的 tun":内核侧同一套 net_device_ops(tun.c:1251-1262 对照),驱动侧从"读写 fd"换成 SAI 收发函数。🔧 动手实验ip tuntap add dev bgp0 mode tap 建一个真 tun,跑 FRR 邻居建连,抓包看 TCP 179 的路径(先在内核侧建立直觉);在 sai-dpdk-mini 里实现 create_hostif + send_hostif_packet 的 NETDEV 桩,ip link 能看到这张"SAI 网卡";FRR zebra 把 10.0.0.0/24 路由注入,观察路由经 APPL_DB → ASIC_DB 最终变成 rte_lpm_add(衔接 P3-03/05);端到端验证:BGP 邻居建连 + 数据面转发 + ping 通。产出 perf-data/P3/06-hostif-frr.md(邻居建连日志 + route 注入链路截图 + 转发吞吐)。下一篇:P4-01 · PCIe Configuration Space 与 BAR 解码(Phase 4 学到时发布)
2026年08月18日
2 阅读
0 评论
0 点赞
2026-08-18
P3-03 · SONiC 控制面链路:APPL_DB → ASIC_DB → syncd
对应路线图: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,调厂商 SDKsyncd 侧走 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_deviceorchagent 翻译成 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 落盘的耗时三段计时表。下一篇:P3-04 · SAI 头文件全景与 15 个 MVP API
2026年08月18日
1 阅读
0 评论
0 点赞
第 1/1 页 · 本页 2 条 · 共 2 条