侧边栏壁纸
  • 累计撰写 53 篇文章
  • 累计收到 3 条评论

P2-04 · libmtcp-v3 设计:RFC、模块图、内存模型

PacketForge
2026-08-18 / 0 评论 / 0 阅读 /
对应路线图:Phase 2 (Day 50-91) | DPDK 24.11.6 LTS | 内核对照 v6.6
代码出处:code/libmtcp-v3/(当前为骨架,本文如实标注"计划中"的部分),对照 source/mtcp/
上一篇:P2-03 TCP 状态机对照 | 下一篇:P2-05 实现(上)RX/TX

一句话定位

libmtcp-v3 的目标:把 mTCP 的精华(每核私有、批量 I/O、批量事件)用 DPDK 24.11.6 的现代接口重写一遍,代码量控制在 mTCP 的十分之一——它是 uNOS 数据面 TCP 栈的预研,不是第二个 mTCP。

先说实话:这个项目现在只有两个真实文件。Makefile 和 13 行的头文件。这正是"先定接口,再填实现"的 TDD 式开局。

现状:两个真实文件的全部内容

include/libmtcp_v3.h(全文,共 13 行):

#ifndef LIBMTCP_V3_H
#define LIBMTCP_V3_H
#include <stdint.h>

int mtcp_socket(int domain, int type, int protocol);
int mtcp_listen(int sockfd, int backlog);
int mtcp_accept(int sockfd, void *addr, void *addrlen);
int mtcp_connect(int sockfd, const void *addr, void *addrlen);
int mtcp_send(int sockfd, const void *buf, int len, int flags);
int mtcp_recv(int sockfd, void *buf, int len, int flags);
int mtcp_close(int sockfd);

#endif

Makefile 声明了实现文件的清单(SRCS-y,第 6 行):

RTE_SDK ?= $(HOME)/myshare/dpdk-roadmap-v3/source/dpdk-stable-24.11.6
include $(RTE_SDK)/mk/rte.vars.mk

APP = libmtcp_v3
SRCS-y = src/mtcp_socket.c src/mtcp_rx.c src/mtcp_tx.c src/mtcp_fsm.c src/mtcp_conn.c

CFLAGS += -O2 -Wall -Wextra -std=c11
CFLAGS += -I$(PWD)/include

API 与 mTCP 的差异是刻意的:mTCP 每个函数都要传 mctx_t(mtcp_api.h:91 mtcp_socket(mctx_t, ...))因为它支持多核多上下文;libmtcp-v3 定位单线程单上下文(uNOS 的一个 RSS worker),所以 API 就是裸 POSIX 风格——少一个参数,应用代码移植成本更低。

模块设计

libmtcp-v3 模块图

模块(Makefile SRCS-y)职责mTCP 参照
mtcp_socket.csocket/listen/accept/connect/close 封装,socket id 从 mempool 分配api.c
mtcp_conn.c4-tuple 连接表(rte_hash),RX 每包查一次tcp_stream.c:62 HashFlow + fhash.c
mtcp_fsm.cRFC793 简化状态机(11 态)tcp_in.c:1298
mtcp_rx.crx_burst 收批 → 解析 → 查表 → fsm → 攒批 ACKdpdk_module.c:464 + tcp_in.c
mtcp_tx.ccwnd 限速 + 发送缓冲 + tx_burst 攒批tcp_out.c:753 WriteTCPDataList

内存模型(与 mTCP 同哲学,用 DPDK 原生设施落地):

  1. conn 池:rte_mempool_create("conn_pool", max_concurrency, sizeof(struct conn), ...) ——对照 mTCP 的 MPCreate("flow_pool_%d", sizeof(tcp_stream), ...)(core.c:947)。每核一个池,运行时零分配。
  2. 4-tuple 连接表:rte_hash_create,key = 12 字节(saddr/sport/daddr/dport,字段序对齐 mTCP EqualFlow 的 tcp_stream.c:104-107),value = conn 池索引。用 rte_hash 而不是 mTCP 的拉链表——见 P2-05。
  3. 接收缓冲:定长 ring(每 conn 一个 rte_ring 太重,计划用固定 8KB 环形数组,对照 mTCP rcvbuf = 8192,sample_mtcp.conf:57)。
  4. 事件队列:rte_ring 存待通知的 socket id(mTCP 用 TAILQ + 锁,我们用免锁 ring,P1-03 学的正好派上用场)。

要实现的 RFC(按优先级裁剪)

只列设计目标,编号是公开知识:RFC 793(状态机主体)→ RFC 1122(实现建议:延迟 ACK、Nagle 可关)→ RFC 2018(SACK 解析,先只解析不主动用)→ RFC 6298(RTO 计算,取 mTCP 简化版)→ RFC 5681(慢启动/拥塞避免/快重传,对照 mTCP tcp_in.c:447-451)。第一版明确不做:窗口缩放可配、TCP 选项里只认 MSS 和时间戳、不实现 cookie/半连接队列(理由见 P2-03)。

🐧 内核对照

决策点Linux v6.6mTCPlibmtcp-v3(计划)
连接查找全局 ehash 带锁(tcp_hashinfo,tcp_ipv4.c:93)每核拉链 hash(fhash.c:109)每核 rte_hash(布谷鸟 + sig)
状态存储struct tcp_sock 内嵌 sock,随 skb 走锁tcp_stream 从 mempool 取(core.c:947)struct conn 从 rte_mempool 取
定时器每 sock 独立 timer(tcp_timer.c:666 起)时间轮批查(timer.c:384)时间轮批查(抄 mTCP)
发送每包进 qdisc攒批 tx_burst(dpdk_module.c:375)攒批 tx_burst

内核那一栏的作用不是"抄",而是"不抄":每个决策点先问"内核为什么这样"(P2-03 已答过半),再问"单核单租户下能否简化"。

面试 3 问

  1. libmtcp-v3 的 API 为什么不带 mctx 参数? → mTCP 支持多核多上下文才需要 mctx 定位每核状态(mtcp_api.h:91);v3 定位单线程(一个 RSS worker 一个实例),上下文全局唯一,省参数的同时杜绝"传错上下文"这类 bug。
  2. conn 池为什么必须用 mempool 而不是 malloc? → 热路径建连/断连频繁,mempool 预分配 + 每核 cache(P1-02 的机制),malloc 有锁且碎片化;mTCP 同款理由(core.c:947)。
  3. Makefile 里 SRCS-y 为什么在 .c 还不存在时就写好? → 接口先行:先定 7 个 API 和 5 个模块边界,编译失败本身驱动实现顺序(先 socket.c 让链接过,再 conn.c……)。

🔧 动手实验

make -C code/libmtcp-v3 一次,把报错记录下来(预期:No rule to make target 'src/mtcp_socket.c')。然后按依赖序实现:mtcp_socket.c(只用 mempool 分配 id)→ 编译过 → mtcp_conn.c(rte_hash 建表)→ 编译过……每一步都保持可构建。里程碑与报错日志落点 perf-data/P2/04-design-notes.md。


下一篇:P2-05 · libmtcp-v3 实现(上):RX/TX 路径与 4-tuple 查表

0

评论 (0)

取消