Linux 网络协议栈如何收发数据包
·
一、协议栈分层结构

- 应用程序(Application):比如浏览器、ssh、httpd,产生或消费“应用数据”。
- 系统调用(syscall):
send/recv,write/read等把应用和内核网络栈连接起来。 - socket 接口(socket):应用的抽象(
struct socket/struct sock),负责把应用语义映射成网络层/传输层要处理的数据结构。 - 传输层(TCP / UDP):
- TCP:面向连接、可靠、拥塞控制、重传。
- UDP:无连接、不可靠、低开销(没有重传/拥塞控制逻辑)。
- 网络层(IP):路由、分片/重组、TTL、IP 校验等。
- 链路层(MAC/以太网):同一物理/链路上的帧封装(源/目的 MAC、以太网类型、VLAN 等)。
- 驱动与网卡(NIC):网卡通过 DMA 在内存和物理链路之间搬运数据;驱动负责设置 DMA 描述符、响应中断、交互内核。
作用:每层只关心自己头部的读写与语义转换,数据在内核内部以统一载体(
sk_buff)传递。
二、数据包
-
各层对数据的称呼
- 用户层:
data(应用字节流/报文) - TCP 层:
segment(TCP 段,含端口、序号、ACK 等) - IP 层:
packet(IP 包,含源/目的 IP,TTL 等) - 链路层:
frame(以太网帧,含源/目的 MAC、类型、FCS)
- 用户层:
-
内核统一抽象:
sk_buff(skb)skb是 Linux 网络栈内的“万能包”结构(struct sk_buff),它包含:- 指向数据缓冲区的指针(head、data、tail、end)
- 长度信息(len、data_len)
- 指向各层头部的指针(
mac_header,network_header,transport_header) - 指向接收/发送的
dev(struct net_device) - 协议类型(
protocol),校验状态(ip_summed) - 控制块(
skb->cb)为各协议层临时使用 - 分片信息(
skb_shinfo())用于零拷贝大包、GSO 等
-
作用:从驱动到用户空间,所有层通过修改 skb 的头部指针、长度来封装/剥离首部。
三、接收网络数据包流程(RX)
-
网卡接收数据并DMA写入内存
- 数据到达网卡:当一台主机的网卡收到网络上的以太网帧时,帧首先进入网卡的硬件接收缓存。
- DMA传输:
- 网卡不会通过CPU一条条指令把数据搬到内存,而是通过DMA控制器直接将数据写入内核预先分配好的 环形缓冲区(ring buffer) 中。
- 这样避免了CPU的频繁参与,节省了宝贵的处理时间。
- 结果:数据已经进入内存,等待后续处理。
-
网卡触发硬件中断
- 中断信号:当网卡完成数据写入后,会向CPU发出一个 硬件中断 信号,通知“有新数据了”。
- CPU响应:CPU暂停当前正在执行的任务,跳转到网卡驱动注册的 中断服务例程(ISR)**。
- ISR的任务:只做轻量级的工作(例如标记数据已到来、触发软中断),避免长时间占用CPU。
-
中断抑制与合并
- 问题:如果网卡收到的数据包很多,每个包都触发一次中断,会让CPU忙于处理中断,效率极低。
- 解决方法:
-
中断合并:网卡会把多个数据包凑在一起后,再触发一次中断。这样能显著减少中断次数。
-
NAPI(New API)机制:
- 在低流量时:继续使用中断方式,保证低延迟。
- 在高流量时:切换为 轮询模式,由内核主动批量从网卡环形缓冲区中取数据,避免被中断“淹没”。
-
-
软中断处理
- 触发软中断:硬件中断处理函数不会直接处理所有数据,而是触发一个 软中断(NET_RX_SOFTIRQ)。
- 执行主体:软中断通常由 ksoftirqd 内核线程 来执行。
- 从网卡的 ringbuffer 中批量取出数据。
- 把每个数据包包装成内核的 skb(socket buffer) 结构,以便后续协议栈处理。
-
链路层处理
- 解析以太网帧头:从 skb 中读取 以太网头部,提取源MAC地址、目的MAC地址、协议类型字段。
- 协议分流:
- 如果协议字段是
0x0800-> IPv4 - 如果是
0x86DD-> IPv6 - 其他类型(如ARP)则交给对应模块处理。
- 如果协议字段是
- 剥离头部:去掉以太网帧的头部,只留下 IP 层以上的数据。
- 校验与过滤:可能进行简单的错误检查,丢弃无效或不需要的数据帧。
-
IP层与传输层处理
-
IP层:
- 校验:检查IP头部的校验和是否正确。
- 分片重组:如果一个数据包被拆成多个片段,这里会进行重组。
- 路由判定:决定这个包是交给本机(上交传输层),还是需要转发出去(作为路由器)。
-
传输层(TCP/UDP):
- 根据 五元组(源IP、目的IP、源端口、目的端口、协议号) 定位到正确的 socket。
- TCP 会进行序列号、确认号检查,保证顺序和可靠性。
- UDP 直接把数据交给应用层,不做额外保证。
-
放入 socket 缓冲区:
- 通过内核的 socket 结构体,数据被写入到该 socket 的接收缓冲区(
sk_rcvbuf)。 - 应用程序稍后就能读取这些数据。
- 通过内核的 socket 结构体,数据被写入到该 socket 的接收缓冲区(
-
-
应用层读取数据
- 系统调用接口:应用程序通过
recv()、read()等调用从 socket 中获取数据。 - 阻塞与非阻塞:
- 如果 socket 是 阻塞模式,而缓冲区里暂时没有数据,应用进程会 进入睡眠状态,直到有新数据到达再被唤醒。
- 如果是 非阻塞模式,调用会立即返回(可能返回“无数据可读”错误)。
- 最终结果:应用层获得完整的网络数据,可以继续处理(例如HTTP解析、数据库协议处理等)。
- 系统调用接口:应用程序通过
四、发送网络数据包流程(TX)
-
应用层写入 socket 缓冲区
- 应用程序调用
send()/write()系统调用,数据从用户空间复制到内核空间。 - TCP:数据会进入发送缓冲区(
sk_sndbuf),暂时存放,等待后续分段和发送。 - UDP:一般直接封装成
skb,立即进入发送路径,不需要复杂的缓冲管理。 - 应用层只需把数据交给内核,后续传输由内核和协议栈全权负责。
- 应用程序调用
-
协议栈构造 skb
- 内核将待发送数据组织成
skb(socket buffer,对网络数据包的统一抽象)。 - 如果数据超过 MSS(最大报文段长度),会进行分段(Segmentation)。
- 可启用 GSO/ TSO,让内核或网卡自动完成分段,减少 CPU 负担。
- 保证数据按链路和协议限制分块,同时优化性能。
- 内核将待发送数据组织成
-
逐层封装
- TCP 层:为数据段加上 TCP 头部(序列号、确认号、端口号、校验和等)。
- IP 层:添加 IP 头部(源/目的 IP 地址、TTL、协议号等),确定路由。
- 链路层:加上 MAC 头部(源 MAC、目的 MAC、协议类型)以及帧尾(FCS 校验)。
- 每一层都添加必要的信息,最终生成完整的以太网帧,能在物理网络上传输。
-
队列调度与流量控制
- 封装好的
skb进入 qdisc(排队规则),用于流量整形、限速、优先级调度等。 - 内核调用
dev_queue_xmit(),将数据交给对应网卡驱动。 - 实现 QoS(服务质量)、公平调度,避免某些流量独占带宽。
- 封装好的
-
驱动处理与 DMA 传输
- 网卡驱动将
skb映射到 TX ring buffer(发送队列,DMA 可见的区域)。 - 网卡通过 DMA 从内存中直接读取数据,而不需要 CPU 搬运。
- 网卡把数据帧发送到物理链路(如以太网、Wi-Fi)。
- DMA 提高效率,CPU 只负责控制,不参与数据搬运。
- 网卡驱动将
-
发送完成中断与资源回收
- 网卡完成发送后,触发一个硬件中断通知 CPU。
- 驱动程序回收 TX ring 中的资源:释放已发送的
skb,解除 DMA 映射。 - 避免内存泄漏,保证发送队列能够被循环利用。
-
TCP 确认与缓冲区管理
- 对于 TCP 连接:当接收方返回 ACK 确认报文,发送端内核会:
- 释放已确认的数据,腾出
sk_sndbuf空间。 - 更新拥塞控制算法(如 Reno、CUBIC)和发送窗口大小。
- 释放已确认的数据,腾出
- 如果发生丢包或超时,TCP 会触发重传机制。
- 保证可靠传输,动态调整传输速率,适应网络状况。
- 对于 TCP 连接:当接收方返回 ACK 确认报文,发送端内核会:
更多推荐
所有评论(0)