TCP/UDP 数据路径:从用户态到对端应用的拷贝与流转

TCP/UDP 数据路径:从用户态到对端应用的拷贝与流转

本文从 Linux 典型协议栈 视角说明:应用 send/recvTCP/UDP 如何协同内核,经 IP、邻居解析、网络设备与网卡 DMA 到达链路,并在对端自底向上 交付到应用;归纳 CPU 拷贝与 DMA 的分工,以及 TCP 接收端不 read 时的流控与零窗口行为。函数名为示意:具体内核版本与路径可能略有差异,以当前内核源码为准。


目录

  1. [TCP/IP 分层与 Linux 协议栈大致对应](#TCP/IP 分层与 Linux 协议栈大致对应)
  2. 以太网上的封装顺序(示意)
  3. 端到端总览
  4. [发送端:用户态 → 网卡](#发送端:用户态 → 网卡)
  5. 发送路径栈(Mermaid)
  6. 链路与对端接收
  7. 接收路径栈(Mermaid)
  8. [epoll 就绪到 recv:谁唤醒、谁拷贝](#epoll 就绪到 recv:谁唤醒、谁拷贝)
  9. 拷贝次数小结
  10. [TCP 与 UDP 差异简表](#TCP 与 UDP 差异简表)
  11. 常见工程优化
  12. [应用不读取时 TCP 接收缓冲区会怎样](#应用不读取时 TCP 接收缓冲区会怎样)
  13. 零窗口与背压(示意)
  14. [ss 中 Recv-Q / Send-Q 的含义](#ss 中 Recv-Q / Send-Q 的含义)
  15. 延伸阅读
  16. 免责声明

TCP/IP 分层与 Linux 协议栈大致对应

TCP/IP 概念层 Linux 中常见对应(概括)
应用层 用户进程、socket API
传输层 tcp_*udp_*inet_*
网络层 ip_*ipv6_*、路由、分片
链路层 邻居(ARP/NDP)、netdeviceqdisc、驱动
物理层 网卡、PHY、介质

本文重点在 socket → 驱动 → DMA 与对端逆路径。


以太网上的封装顺序(示意)

典型 IPv4 + TCP 在以太网链路上的帧结构(仅示意字段顺序,长度因选项与 MTU 而异):

text 复制代码
┌──────────────┬─────────┬─────────┬──────────────────────┐
│ 以太网头     │ IPv4 头 │ TCP 头  │ 载荷(应用数据)      │
│ (含 MAC)     │         │         │                      │
└──────────────┴─────────┴─────────┴──────────────────────┘

UDP 则将中间 TCP 头 换为 UDP 头(更短、无连接状态)。


端到端总览

text 复制代码
本端应用 send/sendto/write
    → 系统调用进入内核
    → socket 层(校验、构造 msghdr)
    → 传输层:TCP(tcp_sendmsg)/ UDP(udp_sendmsg)
    → IP 层:封装、路由、按需分片
    → 邻居子系统:ARP/NDP 解析下一跳 L2 地址
    → netdevice / qdisc:入队
    → 驱动 ndo_start_xmit,DMA 到网卡
    → 物理链路
    → 对端网卡 DMA 入内核 → 软中断/NAPI → 协议栈上行
    → 对端 TCP/UDP → socket → recv/read 拷回用户缓冲区

接收端
网络
发送端
CPU 拷贝至少 1 次
DMA
DMA→内核
CPU 拷贝至少 1 次
用户态缓冲区
内核 socket / sk_buff
网卡 DMA
链路/路由
网卡 DMA
内核接收缓冲
用户态缓冲区


发送端:用户态 → 网卡

1. 系统调用入口

示例:send(sockfd, buf, len, 0) / sendto(...)

CPU 陷落 到内核态,进入 socket 子系统(如 sock_sendmsg → 按协议族进入 inet_sendmsg 等)。

2. Socket 层(概括)

  • 校验文件描述符与 socket 状态。
  • 按类型分派:SOCK_STREAM → TCPSOCK_DGRAM → UDP
  • 组装 struct msghdr,将数据交给传输层。

3. 传输层:TCP 与 UDP

TCP(tcp_sendmsg 等路径)
  • 发送队列 / 发送缓冲 :用户数据通常先拷贝 进内核 sk_buff 链表(发送队列),由 TCP 分段、拥塞控制、重传、按序等逻辑驱动实际发包。
  • 语义 :并非每次 send 都立即对应网线上的一个报文;可能合并、延后发送。
  • 拷贝用户缓冲区 → 内核 sk_buff 至少一次 CPU 拷贝(特殊零拷贝路径除外)。
UDP(udp_sendmsg 等路径)
  • 语义面向报文 ,无 TCP 式的字节流保证;各 sendto 往往对应(在 MTU 允许下)独立数据报的封装与发送路径,无连接状态机与重传。
  • 内核 :仍会构造 sk_buff 并排队到出口路径;「无缓冲」一般指无 TCP 那样复杂的流式可靠发送状态机,并非绝对零队列。

4. IP 层

  • 封装 IPv4/IPv6 首部,查路由表选定出接口。
  • DF + MTU :可能触发 分片(IPv4)或由 PMTUD/路径行为导致发送策略变化(IPv6 通常避免中段分片)。

5. 邻居子系统与设备层

  • ARP / NDP :解析下一跳 MAC
  • dev_queue_xmit :进入队列规则(qdisc) 与驱动发送路径。

6. 网卡与 DMA

  • 驱动 ndo_start_xmit 将待发帧交给网卡;现代网卡常用 DMA内核内存拉取描述符指向的数据。
  • 「网卡侧零拷贝」指:在到达 DMA 前,数据已在内核可达的物理/连续内存 中,由网卡直接读,而不是再起一道 CPU 把整帧逐字节抄到网卡片上小缓冲区(实现细节依硬件)。

发送路径栈(Mermaid)

用户态 send/write
socket 层
TCP / UDP
IP 层
邻居 ARP/NDP
netdevice / qdisc
驱动 ndo_start_xmit
网卡 DMA


链路与对端接收

  1. 链路:比特经交换机/路由器逐跳转发。
  2. 对端网卡DMA 写入预分配的环形缓冲等内核内存区。
  3. 硬中断 → 软中断 / NAPI :收包线程化处理,进入 __netif_receive_skb 一类路径。
  4. 以太网解复用 → IP :校验、去分片、本机投递。
  5. TCP tcp_v4_rcv / UDP udp_rcv
    • TCP :查找 socket、校验、重组ACK 、填入接收缓冲区 ;应用 read/recv 再从内核缓冲拷入用户缓冲
    • UDP :按五元组等匹配 socket,校验和,进入接收队列 ;应用 recvfrom 再拷贝到用户态。
  6. Socket 层 sock_recvmsg :完成「内核缓冲 → 用户缓冲」的至少一次 CPU 拷贝(同样存在高级零拷贝例外)。

接收路径栈(Mermaid)

网卡 DMA 写入 ring
软中断 / NAPI
链路层解复用
IP 层
TCP / UDP
socket 接收队列
用户态 recv/read


epoll 就绪到 recv:谁唤醒、谁拷贝

epoll 只负责多路复用 :告诉你「哪些 fd 上发生了你关心的事件」;真正把数据从内核 socket 接收缓冲拷到用户态 的仍是 read/recv/recvmsg。典型顺序如下。

  1. 进程在 epoll_wait 上阻塞(或超时返回)。
  2. 对端数据经 DMA → NAPI → TCP/UDP → socket 接收队列 入队后,内核将该 socket 标记为可读 ,并把对应 epitem 链入 epoll 的就绪链表(或按边沿/水平触发策略通知)。
  3. epoll_wait 返回events[i].eventsEPOLLIN(等)。
  4. 应用调用 recv :走 sock_recvmsg 等路径,完成 内核接收缓冲 → 用户缓冲区 的 CPU 拷贝(与是否使用 epoll 无关)。

收包路径 NAPI→TCP 内核 socket / TCP epoll 实例 用户态应用 收包路径 NAPI→TCP 内核 socket / TCP epoll 实例 用户态应用 epoll_ctl(ADD, sockfd, EPOLLIN) epoll_wait() 阻塞 数据入接收队列 sk_rcvbuf 标记 fd 可读,就绪链表/通知 返回,events 含 EPOLLIN recv/read 内核缓冲 → 用户缓冲(CPU 拷贝)

边沿触发(ET)提示epoll_wait 返回后应循环读到 EAGAIN,否则内核可能认为「已通知过」而不再重复报告,直到新数据到达。


拷贝次数小结

阶段 典型情况
用户 → 内核(发送) 至少 1 次 CPU 拷贝 (常规 send
内核协议栈内部 尽量复用 sk_buff、减少多余拷贝;具体依路径
内核内存 → 网卡 多为 DMA(对 CPU 而言非「再拷一整帧」意义下的拷贝)
网卡 → 对端内核 DMA 入主机内存
内核 → 用户(接收) 至少 1 次 CPU 拷贝 (常规 recv

TCP 与 UDP 差异简表

维度 TCP UDP
可靠 / 顺序 是(在连接语义下)
流控与拥塞控制 无(由应用自行处理)
发送侧语义 字节流,可合并分段、重传 报文尽力交付
常规路径下用户↔内核拷贝 与 UDP 同属经典两跳模型(发:用户→内核;收:内核→用户) 同左

常见工程优化

技术 作用
sendfile 减少「文件页缓存 → socket」之间的用户态参与与拷贝次数
大页、mmap 降低 TLB 压力或改变内存映射方式(需结合场景评估)
多队列网卡、RSS 并行化收包与中断负载
GRO / LRO 等 收包侧合并/卸载(减轻 CPU;调试时需知可能改变抓包形态)
XDP、eBPF、DPDK 更早/旁路处理报文,降低内核协议栈开销(复杂度高)
SO_ZEROCOPY(视内核与协议) 在限定条件下减少拷贝;需查当前内核与网卡支持

应用不读取时 TCP 接收缓冲区会怎样

对端 应用长期不 read/recv

  1. 内核接收缓冲堆积 :到达的段被确认并放入 socket 接收队列,用户态不取走则占用越来越大。
  2. 接收窗口缩小 :对端在 ACK 中通告的 TCP Window 随剩余缓冲减小;发送端据此减速或停发。
  3. 零窗口(Zero Window) :缓冲满时通告 rwnd=0 ;发送端应停发数据(零窗口探测 除外),形成背压
  4. 发送端表现 :同步阻塞 send 可能阻塞 ;非阻塞返回 EAGAIN/EWOULDBLOCK。若发送缓冲也满则同样阻塞或失败。
  5. 极端与边界
    • rmem_max 达到上限后行为依赖内核策略(丢弃、压力等),对端可能重传
    • 进程退出则内核关闭 socket,可能对端收到 RST
    • Keepalive 主要检测死连接,一般不替代「读走数据」。

运维辨别ss -ntp / ss -nup 观察 Recv-Q、Send-Q ;Recv-Q 大常表示应用读得慢或内核排队多。


零窗口与背压(示意)

发送端应用 TCP 对端栈 内核接收缓冲 接收端应用 发送端应用 TCP 对端栈 内核接收缓冲 接收端应用 直至接收端 read 腾出空间,ACK 携带更大窗口 长期不 read 缓冲渐满 ACK 中 rwnd 缩小至 0 send 阻塞或 EAGAIN


ss 中 Recv-Q / Send-Q 的含义

TCP(LISTEN) TCP(已建立) UDP
Recv-Q 已完成三次握手 、等待 accept 的连接队列长度(受 backlog 等影响) 内核已收到应用尚未 read 的字节数(排队待取) 接收队列中报文或字节统计(实现细节见内核版本)
Send-Q 通常意义较小或依实现 已交给内核发送路径、尚未完全确认离开或应用侧仍占用的待发数据量(与窗口、状态相关) 发送侧排队情况

解读时建议结合 ss -nti 输出中的 cwnd、rwnd 等扩展信息(若支持)与 tcpdump 对照。


延伸阅读

同仓库内可与 TCPIP协议栈详解TCP拥塞控制算法详解 对照阅读;与 ss 相关的笔记见 Linux ss 命令详解与 Netlink 原理


免责声明

不同 OS(BSD、Windows)与内核版本在函数名、零拷贝能力、sysctl 默认值上均有差异;生产排障请结合 ss、抓包、tcpdump 与官方文档。


主题:Linux 网络栈、TCP、UDP、DMA、流控、零窗口。

相关推荐
实在智能RPA2 小时前
Agent 在审计合规场景有哪些应用?——2026年企业智能自动化合规落地全解析
网络·人工智能·ai·自动化
CypressTel2 小时前
AI的“阿喀琉斯之踵”:当技术依赖成为双刃剑——赛柏特安全观察
网络·人工智能·ai
日更嵌入式的打工仔2 小时前
CAN FD扩展帧
网络
炸炸鱼.2 小时前
Python 网络编程入门(简易版)
网络·python
帮我吧智能服务平台3 小时前
工业4.0下,装备制造全生命周期服务数字化落地方案(附实操案例)
网络·人工智能·制造
Vis-Lin3 小时前
BLE 协议栈:L2CAP 信道详解
网络·物联网·网络协议·蓝牙·iot·ble
kiku18183 小时前
Python网络编程
开发语言·网络·python
新新学长搞科研3 小时前
【多所权威高校支持】第五届新能源系统与电力工程国际学术会议(NESP 2026)
运维·网络·人工智能·自动化·能源·信号处理·新能源
Deitymoon4 小时前
linux——网络基础
linux·网络