目录
1. 背景
1.1 协议栈的构成
1. 应用层:
2. Socket 层:
3. 传输层 (TCP/UDP):
4. 网络层 (IP):
5. 数据链路层 (MAC):
6. 物理层 (网卡驱动):
1.2 数据包的组成
2. 接收网络数据包的流程
2.1 数据包接收流程概述
2.2 详细步骤说明
2.2.1 网卡接收数据包
2.2.2 触发硬件中断
2.2.3 处理硬件中断
2.2.4 软中断处理
2.2.5 协议解析与路由
2.2.6 数据分发到 Socket
2.2.7 应用程序处理
3. 发送网络数据包的流程(TCP)
3.1 数据包发送流程概述
3.2 详细步骤说明
3.2.1 应用程序发送数据
3.2.2 协议栈准备数据
3.2.3 协议封装
3.2.4 软中断通知网卡
3.2.5 网卡驱动发送数据
3.2.6 触发网卡发送
3.2.7 处理 ACK 应答
4. sk_buff 结构
5. 总结
1. 背景
1.1 协议栈的构成
想象一下,网络通信就像是一次邮寄包裹的过程,而 TCP/IP 协议栈就是这整个过程中的各个环节,每一层都有自己独特的职责,确保包裹能够安全、准确地送达目的地。具体来说,TCP/IP 协议栈主要分为以下几层:
1. 应用层:
这是你和网络互动的最上层,就像是你写信或包裹标签的地方。常见的应用包括浏览器、邮件客户端、文件传输工具等。
- 应用程序(例如浏览器、文件传输程序)生成需要通过网络发送的数据。
- 应用程序通过系统调用(如
send()
、recv()
、connect()
等)与操作系统交互,向下传递数据。这个系统调用通常会与套接字(socket)进行交互。
2. Socket 层:
想象这是邮局中的分拣部门,负责将你的包裹(数据)分发到正确的目的地。主要协议有 TCP(传输控制协议)和 UDP(用户数据报协议)。
- 套接字作为操作系统提供的一种抽象接口,使应用程序能够与底层的网络协议栈交互。
- 在套接字层,应用程序可以选择使用 TCP 或 UDP 协议。TCP 是面向连接的,提供可靠的数据传输,保证数据按序到达;而 UDP 是无连接的,不保证数据的可靠传递。
3. 传输层 (TCP/UDP):
这层就像是国际物流公司,决定包裹的最佳运输路线。主要协议是 IP(互联网协议)。
- TCP:当使用 TCP 协议时,传输层会对数据进行分段(segment),为每个段添加首部信息,包括序列号、确认号等,确保数据可靠传输。
- UDP:当使用 UDP 协议时,数据会被打包成数据报(datagram),每个数据报包含源端口、目标端口、长度和校验和信息。UDP 的特点是简单、快速,但不保证数据的完整性或顺序。
4. 网络层 (IP):
类似于本地配送,负责在同一个网络内传递包裹,处理物理地址(MAC 地址)和局域网协议。
- 传输层的数据会传递给网络层,IP 协议会将这些段或数据报封装进 IP 包中。IP 包含源地址和目的地址,用于确定数据包的路由。
- IP 协议 不保证数据的传递,但它负责把数据包从源地址传送到目标地址。它通常与 ICMP(Internet Control Message Protocol)等协议一起使用,用于处理错误和诊断。
5. 数据链路层 (MAC):
这是实际运输包裹的物理媒介,比如道路、飞机、船只等。在网络中,这对应于网卡、光纤、电缆等。
- 在网络层之后,数据被传递到数据链路层,也称为 MAC 层。此时,数据会被封装进帧(Frame),每一帧包含目标 MAC 地址、源 MAC 地址等信息。MAC 地址是用来标识网络接口的物理地址。
- 数据链路层主要负责在局域网内传输数据,并将数据传递给正确的物理网络接口。
6. 物理层 (网卡驱动):
- 数据帧最终被传递到物理层,也就是网卡驱动层。在这里,数据会通过网络接口卡(NIC)实际发出去,可能通过以太网、Wi-Fi 等介质传输到目的地。
- 同样,接收到的数据包也会经过网卡驱动,上传至数据链路层、网络层,最终到达应用层。
1.2 数据包的组成
每当你发送或接收数据时,这些数据会在不同的层级被“打包”,每一层都会添加一些必要的信息,确保数据能够顺利传输。具体来说:
- 用户层数据(User Data):就像你写在信封里的内容,是最原始的数据。
- TCP 段(Segment):传输层会把用户数据分成小块(段),并添加一些控制信息,比如序列号,确保数据能按顺序到达。
- IP 包(Packet):网络层会把这些段封装成 IP 包,添加源地址和目标地址,确保数据知道要去哪里。
- 链路层帧(Frame):数据链路层会进一步封装成帧,添加源和目标的物理地址(MAC 地址),确保数据在局域网内能找到正确的设备。
sk_buff
结构:在 Linux 内核中,所有这些封装好的数据包都通过一个叫做sk_buff
的结构来管理和传递。它就像是数据包的“行李标签”,包含了所有必要的信息和指针。
2. 接收网络数据包的流程
接收数据包的过程有点像快递员送货到你家,然后你拆开包裹检查内容。具体步骤如下:
2.1 数据包接收流程概述
- 网卡接收数据包:当数据包通过网络到达你的电脑时,网卡(Network Interface Card)就像是快递员,把包裹(数据)通过 DMA(直接内存访问)直接放到内存中的环形缓冲区(ring buffer)。
- 触发硬件中断:网卡发现有新包裹到达后,会向 CPU 发出一个“快递来了”的信号,这就是硬件中断。
- 处理硬件中断:
- 中断处理函数:CPU 收到中断信号后,会查找对应的处理程序,就像接到快递通知后去取包裹。
- 屏蔽中断:为了防止连续不断的快递打扰,处理程序会暂时屏蔽同一来源的进一步中断。
- 发起软中断:复杂的处理任务会交给软中断,就像让助手帮忙整理包裹,避免你被繁琐的任务耽误其他事情。
- 软中断处理:
- 内核
ksoftirqd
线程:这是内核中的一个专门线程,负责处理这些软中断。它会从环形缓冲区中取出一个个数据包,转换成sk_buff
结构。
- 内核
- 协议解析与路由:
- 解析帧头:从数据帧中提取出 IP 协议版本(IPv4 或 IPv6),并去掉链路层的头尾信息,就像拆开包裹查看里面是什么。
- 解析 IP 头:根据 IP 头的信息,判断上层协议是 TCP 还是 UDP,为后续处理做准备。
- 数据分发到 Socket:
- 五元组匹配:根据源 IP、目标 IP、源端口、目标端口和协议类型(TCP/UDP),找到对应的 Socket,就像根据地址找到对应的房间。
- 数据复制:将数据部分提取出来,放入对应 Socket 的接收缓存区,等待应用程序来取,就像将包裹放到你的门前。
- 结束软中断:完成处理后,重新开启硬件中断,准备接收下一个包裹。
- 应用程序处理:
- 系统调用:你的应用程序(比如浏览器)通过系统调用(如
recv()
)从 Socket 的接收缓存区中取出数据,完成接收过程,就像你打开包裹,查看里面的内容。
- 系统调用:你的应用程序(比如浏览器)通过系统调用(如
2.2 详细步骤说明
2.2.1 网卡接收数据包
当网络设备(如网卡)通过物理媒介(如以太网、Wi-Fi)接收到数据包时,会利用 DMA 将数据包直接写入内存中的环形缓冲区(ring buffer)。环形缓冲区是一种高效的数据结构,允许网卡在固定大小的缓冲区内循环写入和读取数据包,减少内存管理开销。
2.2.2 触发硬件中断
数据包写入环形缓冲区后,网卡会向 CPU 发起硬件中断请求(IRQ)。硬件中断用于通知 CPU 有新的数据包到达,迫使 CPU 中断当前的任务,转而处理网络数据。
2.2.3 处理硬件中断
- 中断处理函数调用:
- CPU 接收到中断请求后,会根据中断向量表查找对应的中断处理函数,并调用该函数。
- 屏蔽进一步中断:
- 为了防止中断风暴(Interrupt Storm),中断处理函数会临时屏蔽同一中断源的进一步中断请求。
- 发起软中断:
- 将数据处理任务交由软中断处理,以释放 CPU 及时响应其他硬件中断。
- 软中断机制能够批量处理数据包,减少上下文切换次数,提高系统整体性能。
2.2.4 软中断处理
内核中的 ksoftirqd
线程负责处理软中断。在软中断处理过程中,ksoftirqd
会从环形缓冲区中逐个取出数据帧,并将其转换为 sk_buff
结构。sk_buff
是 Linux 内核中用于管理网络数据包的核心数据结构,包含了数据包的各种元数据和指针。
2.2.5 协议解析与路由
- 解析帧头:
- 从数据帧中提取 IP 协议版本(IPv4 或 IPv6),并移除链路层的帧头和帧尾,获取 IP 层的数据。
- 解析 IP 头:
- 根据 IP 头部的协议字段,判断上层协议是 TCP 还是 UDP,为后续的传输层处理做准备。
2.2.6 数据分发到 Socket
- 五元组匹配:
- 根据源 IP、目标 IP、源端口、目标端口和协议类型(TCP/UDP),在内核的 Socket 表中查找对应的 Socket。
- 数据复制:
- 将数据区部分提取出来,并放入找到的 Socket 的接收缓存区(Receive Buffer),等待应用程序读取。
- 结束软中断:
- 完成数据包处理后,重新开启硬件中断,以便处理新的数据包到达。
2.2.7 应用程序处理
应用程序通过系统调用(如 recv()
、read()
等)从 Socket 的接收缓存区中读取数据,将其拷贝到应用层的缓冲区,完成数据接收过程。
3. 发送网络数据包的流程(TCP)
发送数据包的过程有点像你通过邮局寄送包裹,从打包到快递员取件,再到最终送达。以 TCP 为例,具体步骤如下:
3.1 数据包发送流程概述
- 应用程序发送数据:你在应用程序(如浏览器)中点击“发送”,用户数据会被拷贝到
sk_buff
,并放入 Socket 的发送缓存区(UDP 通常没有发送缓存区,因为它不需要确认)。 - 协议栈准备数据:网络协议栈从 Socket 的发送缓存区取出
sk_buff
,并克隆出一个新的sk_buff
,这是为了支持 TCP 的重传机制,确保数据不会因为丢失而无法恢复。 - 协议封装:数据在协议栈中逐层封装,添加 TCP 头部、IP 头部、MAC 头部和帧尾。TCP 会对数据进行分段,IP 可能会进行分片。
- 软中断通知网卡:一旦数据准备好,协议栈会通过软中断通知网卡驱动,有新的包裹(数据)需要发送。
- 网卡驱动发送数据:网卡驱动程序会从发送队列中依次取出
sk_buff
,并写入环形缓冲区(ring buffer),这个区域是网卡通过 DMA 直接读取数据的地方。 - 触发网卡发送:网卡开始发送数据,发送成功后会触发硬件中断,通知系统发送完成,并释放
sk_buff
和环形缓冲区的内存(对于 TCP,释放的是克隆的sk_buff
,而 UDP 则释放原始的)。 - 处理 ACK 应答:对于 TCP,发送的数据需要等待对方的 ACK 确认。一旦收到 ACK,内核就会释放原始的
sk_buff
,完成整个发送过程。
3.2 详细步骤说明
3.2.1 应用程序发送数据
当你在浏览器中点击“发送”按钮时,应用程序会通过系统调用(如 send()
或 write()
)将用户的数据拷贝到一个 sk_buff
结构中,并放入对应 Socket 的发送缓存区。对于 UDP 来说,由于它不需要确认机制,所以通常不会有发送缓存区。
3.2.2 协议栈准备数据
网络协议栈会从 Socket 的发送缓存区中取出 sk_buff
,并克隆出一个新的 sk_buff
。这个克隆的 sk_buff
就像是为可能的重传做准备,以防数据在传输过程中丢失。
3.2.3 协议封装
数据在传输过程中会逐层添加协议头部,就像在寄送包裹时,每一层包装都加上必要的信息:
- 传输层(TCP):
- 添加 TCP 头部信息,包括源端口、目标端口、序列号、确认号等。
- 如果数据量大,TCP 会进行分段(Segmenting),确保每个数据段都能被正确传输。
- 网络层(IP):
- 添加 IP 头部信息,包括源 IP 地址和目标 IP 地址。
- 如果 IP 包过大,可能会进行分片(Fragmenting),将大包拆分成小片段传输。
- 数据链路层(MAC):
- 添加链路层头部信息,包括源 MAC 地址和目标 MAC 地址。
- 添加帧尾部(如 CRC 校验),确保数据在传输过程中没有被篡改。
3.2.4 软中断通知网卡
数据包封装完成后,协议栈会通过软中断机制通知网卡驱动程序,有新的数据包准备好要发送了。这就像是你在手机上点击“发送”按钮后,邮局系统自动安排快递员来取件。
3.2.5 网卡驱动发送数据
网卡驱动程序会从发送队列中依次取出 sk_buff
,并将其写入环形缓冲区(ring buffer)。这个环形缓冲区位于内存的 DMA 区域,允许网卡通过 DMA 直接读取待发送的数据包,减少了 CPU 的负担,提高了传输效率。
3.2.6 触发网卡发送
一旦 sk_buff
被写入环形缓冲区,网卡就会开始实际的发送过程。发送成功后,网卡会触发一个硬件中断,通知系统数据已经成功发送:
- 释放
sk_buff
和 ring buffer 内存:- 对于 TCP,发送完成后释放克隆出的
sk_buff
,保留原始的sk_buff
等待 ACK 确认。 - 对于 UDP,由于不需要确认机制,直接释放原始的
sk_buff
。
- 对于 TCP,发送完成后释放克隆出的
3.2.7 处理 ACK 应答
对于 TCP,发送的数据包需要等待对方的 ACK 确认。一旦收到 ACK,内核就会释放原始的 sk_buff
,确保资源得到有效利用。如果没有收到 ACK,TCP 会根据超时机制重新发送数据包,确保数据的可靠传输。
4. sk_buff
结构
在 Linux 内核中,sk_buff
(socket buffer)就像是网络数据包的“行李标签”,负责管理和传递网络数据。每个 sk_buff
包含以下信息:
- 数据指针和长度:指向数据包的实际内容及其长度,就像包裹上的地址和内容描述。
- 协议信息:标识数据包所属的协议层(如 IP、TCP),确保每一层都能正确处理数据。
- 元数据:包含数据包的状态信息,比如接收和发送队列的位置、引用计数等,就像包裹的追踪信息。
- 指针链:
sk_buff
可以通过链表形式连接,方便数据包的快速传输和处理,就像多个包裹通过运输带连接在一起。
sk_buff
使得内核能够高效地管理网络数据包的生命周期,从数据的接收、传输到释放,提供了统一的接口和管理机制。
5. 总结
整个 Linux 协议栈中的数据传输过程就像是一次精心安排的包裹运输,从应用层的“打包”开始,经过传输层、网络层、数据链路层,最终通过物理层的网卡发送或接收数据。每一层在数据封装或解封装的过程中都会添加或解析各自协议层的头部信息,确保数据能够顺利传输到目的地或从网络中正确接收。而 sk_buff
作为核心数据结构,就像是网络数据包的“行李标签”,在整个传输过程中扮演了关键角色,提供了高效的数据包管理机制。
参考:
0voice · GitHub