linux协议栈网卡接收数据到tcp缓冲区
1. 核心数据结构与角色
数据结构 | 所属层级 | 作用 |
---|---|---|
rx_ring | 网卡驱动层 | 网卡的DMA环形缓冲区,存储原始数据包描述(物理地址由rx_buffer->dma 指向)。 |
rx_buffer | 网卡驱动层 | 描述DMA缓冲区的元数据(dma 地址、page 虚拟地址等)。 |
struct sock | TCP/UDP层 | 管理连接状态、数据队列(sk_receive_queue 、backlog 、prequeue )。 |
struct socket | BSD Socket层 | 面向用户空间的抽象,关联struct sock ,提供文件操作接口(如recv )。 |
sk_receive_queue | TCP层 | 存储已按序排列的数据包(sk_buff 链表),供用户进程读取。 |
backlog | TCP层 | 临时缓存锁竞争时的数据包(当用户进程持有socket锁时)。 |
prequeue | TCP层 | 优化路径:用户进程正在读取时,数据直接暂存于此,避免锁竞争。 |
2. 数据流动的完整流程
阶段 1:网卡接收数据(硬中断)
-
网卡DMA写入内存
-
网卡将数据包通过DMA写入驱动预分配的
rx_ring
缓冲区(物理地址由rx_buffer->dma
指向)。 -
数据位置:此时数据在内核的DMA映射区域(
rx_buffer->page
对应的内存页)。
-
-
触发硬中断
-
网卡触发硬中断(IRQ),中断处理函数(如
igb_msix_ring
)仅记录需处理的包数量,并触发软中断(NET_RX_SOFTIRQ)。 -
关键点:硬中断不处理数据,仅调度软中断。
-
阶段 2:软中断处理(协议栈入口)
-
软中断执行
net_rx_action
-
调用网卡驱动的poll函数(如
igb_poll
),从rx_ring
中取出数据包。 -
构建
sk_buff
:-
skb->data
指向DMA缓冲区的数据(rx_buffer->page
的虚拟地址)。 -
填充协议头等元数据。
-
-
-
协议栈处理(IP/TCP层)
-
数据包进入
ip_rcv()
→tcp_v4_rcv()
,根据socket状态选择路径:-
路径A(无锁竞争):直接调用
tcp_rcv_established()
处理。 -
路径B(用户进程持有锁):数据包暂存到
backlog
。 -
路径C(用户正在
recv()
):数据包放入prequeue
(性能优化)。
-
-
阶段 3:协议栈队列管理
(1)prequeue
路径(优化场景)
-
条件:用户进程正在执行
tcp_recvmsg()
且未设置tcp_low_latency
。 -
数据流:
复制
软中断 → prequeue → tcp_recvmsg() → 用户空间
-
优势:避免操作
sk_receive_queue
的锁竞争,数据直达用户进程。
(2)常规路径(sk_receive_queue
)
-
条件:无锁竞争或
prequeue
不可用。 -
数据流:
复制
软中断 → tcp_data_queue() → sk_receive_queue → tcp_recvmsg() → 用户空间
-
乱序处理:乱序数据暂存到
out_of_order_queue
,重组后移入sk_receive_queue
。
(3)backlog
路径(锁竞争场景)
-
条件:用户进程持有socket锁(如正在
send()
或recv()
)。 -
数据流:
复制
软中断 → backlog → 用户进程释放锁 → __release_sock() → sk_receive_queue → tcp_recvmsg()
-
作用:防止数据包因锁竞争丢失。
阶段 4:用户进程读取数据
-
tcp_recvmsg()
逻辑-
从
sk_receive_queue
或prequeue
中取出sk_buff
。 -
调用
skb_copy_datagram_iovec()
将数据从内核(skb->data
)拷贝到用户空间缓冲区。
-
-
内存拷贝次数
-
最佳情况(零拷贝):用户空间直接映射DMA内存(如
mmap
或splice
)。 -
常规情况:1次拷贝(内核→用户空间)。
-
3. 关键交互关系图
plaintext
复制
网卡硬件 │ ↓ DMA rx_ring (rx_buffer->dma) → 硬中断 → 软中断 │ │ │ ├─ 无锁竞争 → tcp_rcv_established() → sk_receive_queue → tcp_recvmsg() │ │ │ ├─ 用户正在recv() → prequeue → tcp_recvmsg() │ │ │ └─ 锁竞争 → backlog → 用户释放锁 → sk_receive_queue │ └───────────────────────────────────────────────────────────┘
4. 性能优化要点
-
减少锁竞争
-
prequeue
避免sk_receive_queue
争用。 -
backlog
防止数据丢失。
-
-
零拷贝技术
-
sendfile()
/splice()
直接传递DMA数据,避免内核-用户空间拷贝。
-
-
队列调优
-
调整
net.core.netdev_max_backlog
(全局backlog长度)。 -
启用
tcp_low_latency=1
(禁用prequeue
,优先低延迟)。
-
5. 总结
-
rx_ring
和rx_buffer
:网卡DMA的物理/虚拟内存管理。 -
软中断:将DMA数据转为
sk_buff
,推动协议栈处理。 -
prequeue
/backlog
:解决用户进程与软中断的并发问题。 -
sk_receive_queue
:最终存储有序数据,供用户进程读取。 -
struct socket
:用户态接口,通过struct sock
操作内核协议栈。
通过这种分层设计,Linux内核在保证可靠性的同时,实现了高性能的网络数据传输。