对应路线图: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);
#endifMakefile 声明了实现文件的清单(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)/includeAPI 与 mTCP 的差异是刻意的:mTCP 每个函数都要传 mctx_t(mtcp_api.h:91 mtcp_socket(mctx_t, ...))因为它支持多核多上下文;libmtcp-v3 定位单线程单上下文(uNOS 的一个 RSS worker),所以 API 就是裸 POSIX 风格——少一个参数,应用代码移植成本更低。
模块设计
| 模块(Makefile SRCS-y) | 职责 | mTCP 参照 |
|---|---|---|
mtcp_socket.c | socket/listen/accept/connect/close 封装,socket id 从 mempool 分配 | api.c |
mtcp_conn.c | 4-tuple 连接表(rte_hash),RX 每包查一次 | tcp_stream.c:62 HashFlow + fhash.c |
mtcp_fsm.c | RFC793 简化状态机(11 态) | tcp_in.c:1298 |
mtcp_rx.c | rx_burst 收批 → 解析 → 查表 → fsm → 攒批 ACK | dpdk_module.c:464 + tcp_in.c |
mtcp_tx.c | cwnd 限速 + 发送缓冲 + tx_burst 攒批 | tcp_out.c:753 WriteTCPDataList |
内存模型(与 mTCP 同哲学,用 DPDK 原生设施落地):
- conn 池:
rte_mempool_create("conn_pool", max_concurrency, sizeof(struct conn), ...)——对照 mTCP 的MPCreate("flow_pool_%d", sizeof(tcp_stream), ...)(core.c:947)。每核一个池,运行时零分配。 - 4-tuple 连接表:
rte_hash_create,key = 12 字节(saddr/sport/daddr/dport,字段序对齐 mTCPEqualFlow的 tcp_stream.c:104-107),value = conn 池索引。用 rte_hash 而不是 mTCP 的拉链表——见 P2-05。 - 接收缓冲:定长 ring(每 conn 一个
rte_ring太重,计划用固定 8KB 环形数组,对照 mTCPrcvbuf = 8192,sample_mtcp.conf:57)。 - 事件队列:
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.6 | mTCP | libmtcp-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 问
- libmtcp-v3 的 API 为什么不带 mctx 参数? → mTCP 支持多核多上下文才需要 mctx 定位每核状态(mtcp_api.h:91);v3 定位单线程(一个 RSS worker 一个实例),上下文全局唯一,省参数的同时杜绝"传错上下文"这类 bug。
- conn 池为什么必须用 mempool 而不是 malloc? → 热路径建连/断连频繁,mempool 预分配 + 每核 cache(P1-02 的机制),malloc 有锁且碎片化;mTCP 同款理由(core.c:947)。
- 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。
评论 (0)