首页
关于
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
P2-02 · mTCP 源码架构:core/tcp_in/eventpoll 三个入口
对应路线图:Phase 2 (Day 50-91) | DPDK 24.11.6 LTS | 内核对照 v6.6代码出处:source/mtcp/mtcp/src/,引用均标 文件:行号上一篇:P2-01 mTCP 论文精读 | 下一篇:P2-03 TCP 状态机对照一句话定位mTCP 整个栈可以压缩成一句话:一个 while 循环扮演"用户态软中断",每轮做九件事。看懂 RunMainLoop,就懂了 mTCP 的全部调度哲学。注:本仓库的 mTCP 是 fork 版本,入口叫 RunMainLoop(core.c:762),由 MTCPRunThread(core.c:1153)拉起;上游旧版叫 mtcp_run_event_loop,职责相同。入口一:core.c 主循环(RunMainLoop 九步)static void RunMainLoop(struct mtcp_thread_context *ctx) // core.c:762 { mtcp_manager_t mtcp = ctx->mtcp_manager; // core.c:764 // ... while ((!ctx->done || mtcp->flow_cnt) && !ctx->exit) { // core.c:776 // ... gettimeofday(&cur_ts, NULL); mtcp->cur_ts = TIMEVAL_TO_TS(&cur_ts); // ① 取时间戳 :781-783 for (rx_inf = 0; rx_inf < CONFIG.eths_num; rx_inf++) { recv_cnt = mtcp->iom->recv_pkts(ctx, rx_inf); // ② 批量收包 :789 for (i = 0; i < recv_cnt; i++) { pktbuf = mtcp->iom->get_rptr(mtcp->ctx, rx_inf, i, &len); if (pktbuf != NULL) ProcessPacket(mtcp, rx_inf, ts, pktbuf, len); // ③ :795 } } if (mtcp->flow_cnt > 0) { CheckRtmTimeout(mtcp, ts, CONFIG.max_concurrency); // ④ 定时器 :824 CheckTimewaitExpire(mtcp, ts, CONFIG.max_concurrency); // :825 // ... } if (mtcp->ep) { FlushEpollEvents(mtcp, ts); // ⑤ 批量事件 :834 } if (mtcp->flow_cnt > 0) { HandleApplicationCalls(mtcp, ts); // ⑥ 应用调用 :839 } WritePacketsToChunks(mtcp, ts); // ⑦ 写发送块 :842 for (tx_inf = 0; tx_inf < CONFIG.eths_num; tx_inf++) mtcp->iom->send_pkts(ctx, tx_inf); // ⑧ 批量发送 :847 mtcp->iom->select(ctx); // ⑨ 空闲才睡 :860 } }两个最关键的细节:① ④⑤⑥⑦的顺序不能乱——先查超时(重传/回收),再刷事件(应用才能看到新数据),再消费应用攒下的调用(connect/send/close),最后才写发送块,保证应用视角一致性;② ⑨ 的 select 是"没活干才睡"——有包收就不睡(这正是 P1-05 说的"把中断换成轮询"),但流空闲时也让出 CPU,避免空转烧核。入口二:tcp_in.c 状态机(每包都过这里)ProcessPacket(eth_in.c:9)按以太类型分发:ETH_P_IP → ProcessIPv4Packet,最终落到 ProcessTCPPacket(tcp_in.c:1205):int ProcessTCPPacket(mtcp_manager_t mtcp, uint32_t cur_ts, const int ifidx, const struct iphdr *iph, int ip_len) { // tcp_in.c:1205 struct tcphdr* tcph = (struct tcphdr *) ((u_char *)iph + (iph->ihl << 2)); uint8_t *payload = (uint8_t *)tcph + (tcph->doff << 2); int payloadlen = ip_len - (payload - (u_char *)iph); // ... s_stream.saddr = iph->daddr; // 注意:4-tuple 是"本端视角" s_stream.sport = tcph->dest; // 与网卡方向相反 s_stream.daddr = iph->saddr; s_stream.dport = tcph->source; // tcp_in.c:1247-1250 if (!(cur_stream = StreamHTSearch(mtcp->tcp_flow_table, &s_stream))) { /* not found in flow table */ cur_stream = CreateNewFlowHTEntry(mtcp, cur_ts, iph, ip_len, tcph, seq, ack_seq, payloadlen, window); // tcp_in.c:1252-1258 } /* Validate sequence. if not valid, ignore the packet */ if (cur_stream->state > TCP_ST_SYN_RCVD) { ret = ValidateSequence(mtcp, cur_stream, cur_ts, tcph, seq, ack_seq, payloadlen); // tcp_in.c:1261-1263 } // ... switch (cur_stream->state) { // tcp_in.c:1298 状态分发 case TCP_ST_LISTEN: Handle_TCP_ST_LISTEN(mtcp, cur_ts, cur_stream, tcph); break; case TCP_ST_ESTABLISHED: Handle_TCP_ST_ESTABLISHED(mtcp, cur_ts, cur_stream, tcph, seq, ack_seq, payload, payloadlen, window); break; // ... 共 11 个状态分支,逐状态对比见 P2-03 } }流表是每核私有的拉链 hash:StreamHTSearch 就是算 hash 下标 + TAILQ_FOREACH 遍历桶(fhash.c:109-126),hash 函数 HashFlow 对 12 字节 4-tuple 做 Jenkins 风格混合后 & (NUM_BINS_FLOWS - 1)(tcp_stream.c:82-94,13 万桶)。入口三:eventpoll.c 事件桥(应用线程的接口)应用线程不碰包路径,只调 mtcp_epoll_wait(eventpoll.c:363)等事件。栈线程侧只做入队 AddEpollEvent(eventpoll.c:569):inline int AddEpollEvent(struct mtcp_epoll *ep, int queue_type, socket_map_t socket, uint32_t event) { // eventpoll.c:569 if (socket->events & event) { return 0; /* 同一事件只入队一次 */ } if (queue_type == MTCP_EVENT_QUEUE) { eq = ep->mtcp_queue; /* 栈线程 → mtcp_queue */ } else if (queue_type == USR_EVENT_QUEUE) { eq = ep->usr_queue; /* 应用线程 → usr_queue */ } index = eq->end++; socket->events |= event; eq->events[index].sockid = socket->id; // eventpoll.c:604-609 }状态机在关键跃迁点调 RaiseReadEvent/RaiseWriteEvent/RaiseErrorEvent(tcp_stream.c:125/146/198),它们内部就是 AddEpollEvent(..., MTCP_EVENT_QUEUE, ...)。然后主循环 ⑤ 统一 flush(core.c:461-472 把 mtcp_queue 搬进 usr_queue)+ 单次 pthread_cond_signal(core.c:483)。这是"批量事件"的完整闭环。🐧 内核对照环节Linux v6.6mTCP收包循环软中断 net_rx_action(budget 限流)→ tcp_v4_rcv(tcp_ipv4.c:1999)RunMainLoop 每轮 burst(core.c:789),无预算连接查找__inet_lookup_established 全局 ehash(tcp_ipv4.c:1803)每核私有 fhash(fhash.c:109)空闲行为NAPI 空闲后重新开中断iom->select 睡眠(core.c:860)面试 3 问RunMainLoop 九步顺序为什么是"超时→事件→应用调用→发送"? → 超时先处理保证重传/回收优先;事件在应用调用之前刷,应用读到最新状态;发送放最后让本轮的 ACK/重传/新数据一起进发送块(core.c:824-847 行序)。ProcessTCPPacket 的 4-tuple 为什么是"反的"? → 流表 key 是本端视角:收到的包 saddr=对端,所以存表时 saddr 取 iph->daddr(tcp_in.c:1247-1250)。mTCP 是纯轮询吗? → 不是:有包收就转(core.c:776 循环),无流无包时 iom->select 让出 CPU(core.c:860),是"轮询 + 可选睡眠"的混合模型。🔧 动手实验在 source/mtcp/ 按 autotools 流程构建(README.md/README.config 有 DPDK 路径配置说明),跑 apps/example/epserver -f epserver.conf + epwget(配置模板 config/sample_mtcp.conf:io = dpdk、port = dpdk0、max_concurrency = 10000)。用 gdb 在 RunMainLoop 每步各打一个断点,统计 1 秒内各断点命中次数——"九个动作的负载画像"。数据落点 perf-data/P2/02-eventloop.md。下一篇:P2-03 · 🐧 对照:mTCP 与内核 TCP 状态机逐状态对比
2026年08月18日
0 阅读
0 评论
0 点赞
2026-08-18
P2-01 · mTCP 论文精读(NSDI'14):用户态协议栈为什么能快 25 倍
对应路线图:Phase 2 (Day 50-91) | DPDK 24.11.6 LTS | 内核对照 v6.6代码出处:source/mtcp/mtcp/src/,引用均标 文件:行号;论文数字为公开知识转述,精确值以原文为准上一篇:P1-07 RSS 与 rte_flow | 下一篇:P2-02 mTCP 源码架构一句话定位mTCP(Jeong et al., NSDI'14)是"用户态 TCP 栈"这个品类的开山之作:每个核跑一个独立的 TCP 线程 + 批量收发 + 批量事件 + 流亲和,论文报告的典型数字约为小连接场景下 25 倍于 Linux TCP 的吞吐(建议复核原文)。读它 = 拿到一份"内核协议栈哪些开销可以砍掉"的完整清单。论文四大论点 → 逐条对代码论点 1:每核一个 TCP 线程,零共享锁。 全部连接状态(flow table、socket 池、定时器)按核私有。落地代码:线程入口 MTCPRunThread 每核一条,进来先绑核:static void * MTCPRunThread(void *arg) // core.c:1153 { mctx_t mctx = (mctx_t)arg; int cpu = mctx->cpu; // ... if (rte_lcore_id() == LCORE_ID_ANY) mtcp_core_affinitize(cpu); // core.c:1164-1167 /* memory alloc after core affinitization would use local memory most time */ ctx = calloc(1, sizeof(*ctx)); // core.c:1172 // ... mtcp = ctx->mtcp_manager = InitializeMTCPManager(ctx); // core.c:1180InitializeMTCPManager 里最值得看的是流池:MPCreate(pool_name, sizeof(tcp_stream), sizeof(tcp_stream) * CONFIG.max_concurrency)(core.c:947-948)——每核私有 mempool 预分配全部连接结构,运行时零 malloc。论点 2:批量包 I/O。 论文批判内核"每包一次中断/一次分配"。mTCP 一次 rte_eth_rx_burst 收一批再逐包喂状态机(dpdk_module.c:464),发送攒进 wmbufs 一批 flush(dpdk_module.c:333-375)。详细拆解留给 P2-02。论点 3:批量事件处理。 内核 epoll 每事件一次唤醒;mTCP 改造版 epoll 把事件先攒在 mtcp_queue,栈线程一轮循环末尾统一 FlushEpollEvents 搬到 usr_queue 再 pthread_cond_signal 一次(core.c:451-485)。一次唤醒处理一批事件,唤醒开销被摊平。论点 4:RSS 流亲和,杜绝连接迁移。 论文还强调:同一连接永远落在同一核的 cache 里。落地代码 mtcp_init_rss(api.c:644)支持多 IP 的 RSS 流初始化;配合 P1-07 学的 Toeplitz 哈希,连接状态不再跨核漂移。为什么内核做不到?看一段真代码内核 TCP 收包入口(tcp_ipv4.c:1999 tcp_v4_rcv)每一包都要:查 ehash 连接表(__inet_lookup_established,tcp_ipv4.c:1803)→ 加锁(bh_lock_sock)→ 校验 → 进状态机。连接表全局共享(tcp_hashinfo,tcp_ipv4.c:93),hash 桶上挂的是自旋锁。维度Linux TCP(v6.6)mTCP(NSDI'14 设计)收包触发硬中断 + 软中断(budget 限流)应用线程主动轮询,无预算包容器每包构造 skb(分配+引用计数)预分配 mbuf 池连接表全局 ehash + 桶锁每核私有 hash,无锁定时器每 sock 独立 timer时间轮批查(P2-06 展开)事件每事件一次唤醒批量事件 + 单次唤醒系统调用read/write 每次陷进内核用户态函数调用一句话总结:内核 TCP 不是算法差,而是每包的固定税太重(中断、锁、skb、syscall);mTCP 把这四项税全部改成"批量均摊",这就是 25 倍的来源。论文的评测方法(为什么它的数字可信)论文的可信度来自三个设计(公开知识转述,精确数字建议复核原文):对照公平:mTCP 和 Linux TCP 跑同一台机器、同一批应用(如 lighttpd/epwget 类工作负载),差别只在协议栈;工作负载分层:短连接(CPS 型)、长连接(吞吐型)、混合型分开测——短连接最能暴露"每包固定税",这正是 25 倍量级数字出现的场景;可扩展性单独测:从 1 核加到 8/16/32 核画吞吐曲线,证明每核私有设计不随核数退化。读论文时建议对照做一件事:把论文每个图对应的代码路径找出来——比如"batch I/O"对应 core.c:789 的 recv_pkts 循环,这正是本文第一节干的事。论文的图讲"现象",代码行讲"机制",两层对上才算读懂。对 libmtcp-v3 的启示这份"固定税清单"直接变成 P2-04 的验收标准:libmtcp-v3 每一行实现都要能回答"这一行在砍哪项税"——中断税(无,纯轮询)、锁税(每核私有)、skb 税(mempool 预分配)、syscall 税(用户态 API)。答不上来的代码就是过度设计,删。面试 3 问mTCP 为什么能无锁跑满多核? → 每个核一份完整 TCP 状态(flow_pool 每核独立,core.c:947),流经 RSS 钉死在核上(api.c:644),没有共享状态自然没有锁。mTCP 的 epoll 和内核 epoll 差在哪? → 内核 epoll 每事件触发一次唤醒;mTCP 事件先进 mtcp_queue,主循环末尾批量 flush 到 usr_queue 再单次 pthread_cond_signal(core.c:461-483)——唤醒次数从 O(事件数) 降到 O(批次)。为什么说 skb 构造是内核的固定税? → 每收一包都要分配 skb、填字段、挂引用;mTCP 的 mbuf 从 mempool 来,收包只是换指针(对应 P1-02/P1-05 的 mempool/refill 机制),分配完全移出热路径。🔧 动手实验把论文四大论点逐一 grep 到代码行,形成"论点→行号"笔记(上面每个论点的行号就是答案),落点 perf-data/P2/01-paper-notes.md。检查项:① 找出一处 mTCP 仍用锁的地方(提示:ep->epoll_lock,core.c:457——它只保护栈线程与应用线程的队列边界,不在包路径上);② 读 core.c:776 的循环条件,回答"全部连接关闭后主循环为什么退出"。下一篇:P2-02 · mTCP 源码架构:core/tcp_in/eventpoll 三个入口
2026年08月18日
0 阅读
0 评论
0 点赞
第 1/1 页 · 本页 2 条 · 共 2 条