对应路线图:Phase 2 (Day 50-91) | DPDK 24.11.6 LTS | 内核对照 v6.6.143
代码出处:source/mtcp/mtcp/src/tcp_in.cvssource/linux-stable/net/ipv4/tcp_input.c,引用均标文件:行号
上一篇:P2-02 mTCP 源码架构 | 下一篇:P2-04 libmtcp-v3 设计
一句话定位
同一份 RFC 793 状态机,mTCP 用 65 行 switch 写完,内核用一个 switch + 一条快路径 + 半连接队列 + TW 子机写完。逐状态对照一遍,用户态栈"简化了什么、为什么敢简化"就全清楚了。
两个分发器先摆一起
mTCP 的 switch(tcp_in.c:1298-1363,节选):
switch (cur_stream->state) {
case TCP_ST_LISTEN:
Handle_TCP_ST_LISTEN(mtcp, cur_ts, cur_stream, tcph);
break;
case TCP_ST_SYN_SENT:
Handle_TCP_ST_SYN_SENT(mtcp, cur_ts, cur_stream, iph, tcph,
seq, ack_seq, payloadlen, window);
break;
case TCP_ST_SYN_RCVD:
/* SYN retransmit implies our SYN/ACK was lost. Resend */
if (tcph->syn && seq == cur_stream->rcvvar->irs)
Handle_TCP_ST_LISTEN(mtcp, cur_ts, cur_stream, tcph);
else {
Handle_TCP_ST_SYN_RCVD(mtcp, cur_ts, cur_stream, tcph, ack_seq);
if (payloadlen > 0 && cur_stream->state == TCP_ST_ESTABLISHED) {
Handle_TCP_ST_ESTABLISHED(mtcp, cur_ts, cur_stream, tcph,
seq, ack_seq, payload,
payloadlen, window);
}
}
break;
// ... ESTABLISHED / CLOSE_WAIT / LAST_ACK / FIN_WAIT_1 / FIN_WAIT_2
// ... CLOSING / TIME_WAIT / CLOSED,共 11 个分支
}内核的 switch(tcp_input.c:6637-6684 + 6725 起,节选):
switch (sk->sk_state) {
case TCP_CLOSE:
SKB_DR_SET(reason, TCP_CLOSE);
goto discard;
case TCP_LISTEN:
if (th->ack)
return 1;
if (th->rst) { /* ... */ goto discard; }
if (th->syn) {
// ...
acceptable = icsk->icsk_af_ops->conn_request(sk, skb) >= 0;
// ...
consume_skb(skb);
return 0;
}
// ...
case TCP_SYN_SENT:
tp->rx_opt.saw_tstamp = 0;
tcp_mstamp_refresh(tp);
queued = tcp_rcv_synsent_state_process(sk, skb, th);
if (queued >= 0)
return queued;
// ...
}最关键的架构差:内核 ESTABLISHED 之后根本不进这个 switch——普通数据包走 tcp_rcv_established 快路径(tcp_input.c:6009),只有 SYN/FIN/异常才落到 tcp_rcv_state_process(tcp_input.c:6627)慢路径。mTCP 每包都过同一个 switch,靠 static inline 的 handler 把分支成本压到可接受。
逐状态对比(只看有料的三组)
LISTEN:mTCP 直接转态 vs 内核建 request_sock
mTCP 的 Handle_TCP_ST_LISTEN(tcp_in.c:750-764)收 SYN 直接 state = TCP_ST_SYN_RCVD 并 AddtoControlList 回 SYN/ACK——没有半连接队列,没有 SYN cookie。
内核在 LISTEN 收 SYN 调 icsk_af_ops->conn_request(tcp_input.c:6660)建 request_sock 挂半连接队列,由 tcp_check_req(tcp_minisocks.c:602)在三路握手最后校验。半连接队列 + cookie 是防 SYN flood 的,mTCP 的假设是"信任内网/测试环境流量"——这就是简化权。
SYN_SENT:主动打开路径
mTCP(tcp_in.c:767-812)三步走:① ACK 不可接受(TCP_SEQ_LEQ(ack_seq, iss))直接回 RST(:774-782);② RST 则进 CLOSE_WAIT 报错(:787-798);③ SYN+ACK 则 HandleActiveOpen 后置 ESTABLISHED、清 RTO(RemoveFromRTOList,:810)、RaiseWriteEvent 通知应用可写(:815)。
内核把同时打开等边角情况交给 tcp_rcv_synsent_state_process(tcp_input.c:6675)单独处理,主 switch 只做收尾(tcp_input.c:6672-6683)。
ESTABLISHED:mTCP 一个函数 vs 内核快慢双路径
mTCP 的 Handle_TCP_ST_ESTABLISHED(tcp_in.c:906-953)顺序:SYN 异常回 ACK → payload 进 ProcessTCPPayload 并攒 ACK(ACK_OPT_AGGREGATE 延迟 / ACK_OPT_NOW 立即,:921-928)→ ACK 进 ProcessACK(:932)→ FIN 序号合法则转 CLOSE_WAIT 并 RaiseReadEvent(:937-947)。
内核快路径 tcp_rcv_established(tcp_input.c:6009)只处理"序号连续、无标志异常"的段:校验 header pred、更新 ACK、直接进接收队列,全程不锁用户态锁、不做状态判断;一旦 tcp_validate_incoming(tcp_input.c:5862)判异常就跌回慢路径。
TIME_WAIT:链表批查 vs TW 子机
mTCP 收 FIN 转 TIME_WAIT 时挂进 timewait_list(tcp_in.c:1139),由主循环 CheckTimewaitExpire 每轮批查过期销毁(timer.c:444-487,ts_tw_expire 到期 → CLOSED + DestroyTCPStream)。TIME_WAIT 里再收到 FIN 重传就 re-arm 定时并补 ACK(tcp_in.c:1352-1360)。
内核的 TW 是独立生命周期:tcp_time_wait(tcp_minisocks.c:280)把 sock 转成 inet_timewait_sock,由 tw_timer 定时回收,迟到的包走 tcp_timewait_state_process 专门处理。mTCP 一个链表搞定,内核一个子状态机搞定——复杂度差一个数量级。
🐧 差异总表
| 状态 | mTCP(tcp_in.c) | Linux v6.6(tcp_input.c) | 差异本质 |
|---|---|---|---|
| LISTEN | :750-764,SYN 直接转 SYN_RCVD | :6642-6667,conn_request 建 request_sock | 无半连接队列/cookie |
| SYN_SENT | :767-812,ACK/RST/SYN+ACK 三分支 | :6672-6683,交 synsent_state_process | 内核单列同时打开 |
| SYN_RCVD | :1308-1320,ACK 转 ESTABLISHED 且同包数据顺带处理 | :6726-6768,tcp_init_transfer + tcp_set_state | mTCP 顺带处理是亮点 |
| ESTABLISHED | :906-953,单函数全处理 | :6009 快路径 + 慢路径兜底 | 快慢路径分离是内核最大工程 |
| FIN_WAIT_1 | :1033 起 | :6770-6779,先验 snd_una != write_seq | 内核多一层 FIN 序号防护 |
| TIME_WAIT | timer.c:444 链表批查 | tcp_minisocks.c:280 TW 子机 | 链表 vs 子状态机 |
面试 3 问
- 内核为什么需要 tcp_rcv_established 快路径? → ESTABLISHED 占 99% 包量;快路径省掉状态 switch、锁与校验分支,只做 header pred + ACK 处理(tcp_input.c:6009);异常段经
tcp_validate_incoming跌回慢路径(tcp_input.c:5862)。 - mTCP SYN_RCVD 转 ESTABLISHED 时"顺带处理 payload"体现在哪? → tcp_in.c:1314-1318:ACK 完成后若
state == TCP_ST_ESTABLISHED且 payloadlen>0,直接再调一次 ESTABLISHED handler——同一轮循环把三次握手和数据都吃掉,省一轮事件。 - mTCP 为什么敢不要半连接队列? → 定位是单机多核的 Web/LB 场景,SYN flood 交给前面的硬件/网关防;省掉 request_sock 结构、队列和定时器(对照 tcp_input.c:6660 与 tcp_minisocks.c:602)。
🔧 动手实验
用 tcpdump 抓一次完整的三次握手 + 四次挥手,对照本文行号逐包标出"哪个包触发了哪一行"。再做一个改动实验:把 mTCP tcp_timewait 配成 0(sample_mtcp.conf:71 默认就是 0)观察短连接压测下 TIME_WAIT 立即回收的吞吐差。数据落点 perf-data/P2/03-fsm.md。
评论 (0)