-
应用层:
- 功能:提供应用程序间通信。
- 例子:电子邮件客户端如Outlook或Thunderbird,它们提供用户界面来发送和接收电子邮件。这些客户端使用SMTP(用于发送邮件)和IMAP或POP3(用于接收邮件)这样的应用层协议。
-
表示层:
- 功能:确保信息的语义在不同系统间传输时保持不变,通常涉及数据格式转换。
- 例子:当一个Web服务将数据从一种格式(如数据库中的二进制数据)转换为另一种格式(如JSON或XML)以供Web客户端使用时,就是表示层在起作用。
-
会话层:
- 功能:建立、管理和终止应用程序之间的会话。
- 例子:在网银交易中,当用户登录到他们的账户进行操作时,会话层负责维护和管理这个会话的完整性和安全性,例如RPC。
-
传输层:
- 功能:实现端到端的数据传输。
- 例子:TCP(传输控制协议)确保数据包的正确顺序传输,比如在一个网页请求中从服务器传送HTML文件到你的浏览器。
-
网络层:
- 功能:负责数据的路由选择和传输。
- 例子:当你在浏览器中输入一个网址时,IP协议(网络层的一部分)帮助确定数据包应该如何从你的设备路由到目标服务器。
-
数据链路层:
- 功能:确保在同一网络中或两个直接连接的网络设备间的可靠数据传输。
- 例子:以太网(Ethernet)是数据链路层的一个实例,它通过MAC地址对网络中的设备进行识别和通信。
-
物理层:
- 功能:在物理媒介上传输原始比特流。
- 例子:双绞线、光纤或无线电波等传输媒介,在这些媒介上,物理层以电信号、光脉冲或无线信号的形式传输数据。
Linux 接收网络包的流程
网卡是计算机里的一个硬件,专门负责接收和发送网络包,当网卡接收到一个网络包后,会通过 DMA 技术,将网络包写入到指定的内存地址,也就是写入到 Ring Buffer ,这个是一个环形缓冲区,接着就会告诉操作系统这个网络包已经到达。
DMA(Direct Memory Access)是一种允许某些硬件子系统直接访问主内存的技术,用于数据传输。当网卡接收到一个网络包后,使用DMA技术可以直接将网络包写入到计算机内存的指定地址,而不需要CPU的介入,这样可以显著提高数据传输的效率。
DMA的工作原理如下:
- 硬件配置: 网卡(或其他DMA支持的硬件设备)被配置为对特定内存地址有直接访问的权限。
- 接收数据: 当网卡接收到数据(如网络包)时,它会启动DMA传输。
- 内存写入: DMA控制器接管数据传输任务,直接将数据从网卡传输到预定的内存地址,无需CPU参与。这意味着CPU可以同时处理其他任务,而不必被数据传输过程所占用。
- 中断信号: 数据传输完成后,DMA控制器会发送一个中断信号给CPU,告知它数据已经被成功传输并存储在内存中。
通过这种方式,DMA提高了数据处理的效率,尤其是在处理大量数据或高速数据流时。它是现代计算机和网络设备中不可或缺的一部分,用于优化数据传输过程,减轻CPU的负担。
如何知道网络包到了
每当网卡收到了一个网络包,就会触发一个硬中断,告诉操作系统要去拿数据咯。
但是这就会有一个致命的问题,因为网卡触发的是硬中断,优先级很高,如果这时候有很多很多的网络包发送过来,那岂不是得一直硬中断了,那还怎么处理其他的进程呢?
因此,为了解决频繁中断带来的性能开销,Linux 内核在 2.6 版本中引入了 NAPI 机制
NAPI(New API)机制是为了改进Linux内核处理网络数据包的方式,特别是在高速网络环境下。传统的网络数据包处理机制是完全中断驱动的:每收到一个数据包就产生一个硬中断,这在数据包到达速率非常高的时候会导致大量的CPU中断,从而影响系统性能。
NAPI的目标是通过引入轮询机制来减少在高负载下处理网络数据包时的中断数量。NAPI机制工作的基本流程如下:
- 正常模式: 当网络负载较低时,系统使用传统的中断驱动机制。网卡接收到数据包时会产生中断,由中断处理函数(Interrupt Service Routine, ISR)来处理。
- 高负载检测: 当网卡开始接收到大量的数据包时,ISR会判断是否需要转为NAPI模式。这通常基于接收到数据包的速率。
- NAPI模式: 在NAPI模式下,硬中断会被禁用,ISR会将网卡设置到一个轮询模式。在轮询模式中,不是对每个接收到的数据包产生硬中断,而是在网络驱动的轮询函数中处理多个数据包。
- 轮询函数处理: 内核会定期调用网卡驱动的轮询函数(也称为poll函数),该函数负责批量处理接收到的数据包。处理过程中,如果数据包到达速率降低,或者已经处理了足够多的数据包,轮询函数会重新启用硬中断并退出NAPI模式。
例子:
假设你有一个网卡支持10Gb/s的速率,当网络流量低时,每个数据包的到达会触发一个硬中断,由CPU处理。这在低流量下是可行的。
然而,在一个高流量场景下,比如服务器正在处理成千上万的并发连接,或者正在进行大规模数据传输,这时每个数据包都触发中断就会导致CPU花费大量时间去处理中断本身,而不是数据包。在这种情况下,启用NAPI模式可以显著减少中断的数量,网卡驱动会在轮询函数中一次处理多个数据包,减轻了CPU的负担,提高了系统的整体性能。
也就是说,正常情况下还是通过硬中断的方式去处理网络包,当请求较大的时候会开启NAPI模式,提升整体性能。
在Linux内核当中有一个ksoftirqd线程是专门来处理负载很重的软中断的。
- 网络包接收: 当网卡接收到网络数据包,它们首先被放入到网卡的接收环形缓冲区(Ring Buffer)中。
- 触发软中断: 一旦环形缓冲区中有数据包,网卡会触发一个软中断(或者在高流量情况下由硬中断切换到轮询模式后,通过轮询来处理)。这个软中断是通过内核的软中断系统(softirq)来处理的,而不是传统的硬中断处理方式。
- ksoftirqd处理: 如果软中断的处理负载很重,可能会唤醒专门的内核线程
ksoftirqd
来处理。这个内核线程会从接收环形缓冲区中取出数据包,并封装成sk_buff
结构体。- 交付协议栈: 一旦数据包被封装到
sk_buff
,它就会被交付到网络协议栈进行进一步处理。这包括从数据链路层到网络层,然后到传输层,最终可能到应用层。- 逐层处理: 在协议栈的每一层,
sk_buff
会被相应的层处理。例如,在网络层,IP头部会被解析,并根据需要进行路由。在传输层,TCP或UDP头部会被处理,并且数据会被传递到相应的socket缓冲区。
总结
- 网络接口层(数据链路层):
- 数据包首先到达网络接口,并被放置在环形缓冲区(Ring Buffer)中。
- 当环形缓冲区接收到数据后,会触发一个软中断(或者在某些情况下是NAPI轮询机制)。
ksoftirqd
内核线程(或者在高负载时直接由中断上下文)处理这些软中断,从环形缓冲区中取出数据帧。- 使用
sk_buff
结构来表示网络包,并进行基本的合法性检查,如CRC检验等,不合法的帧会被丢弃。 - 识别上层协议(如IPv4, IPv6),并移除数据链路层的头(如以太网帧头)。
- 网络层:
- 在网络层,包括IP头的网络数据包会被进一步处理。
- 内核检查IP包的目的地址,决定是将数据包传递给上层协议还是路由转发。
- 对于本机处理的包,内核检查IP头部确定上层协议(如TCP或UDP),并移除IP头。
- 传输层:
- 在传输层,TCP或UDP头被处理。
- 根据源IP地址、源端口、目的IP地址和目的端口四元组标识找到对应的套接字(Socket)。
- 数据被放入相应Socket的接收缓冲区。
- 应用层:
- 应用程序通过系统调用(如
read
或recvfrom
),请求从套接字缓冲区读取数据。 - 数据从内核空间的Socket接收缓冲区复制到用户空间的应用层缓冲区。
- 应用程序继续处理接收到的数据。
- 应用程序通过系统调用(如
在这个过程中,内核对网络数据包的处理是分层的,并且每层只处理对应层次的协议头和数据。利用sk_buff
来在各层之间传递数据包可以实现有效的数据处理,因为它避免了不必要的数据复制,从而提高了性能。
Linux 发送网络包的流程
- 系统调用: 应用程序调用Socket API来发送数据。这会触发从用户态到内核态的切换。
- 内存分配: 内核为要发送的数据分配
sk_buff
结构体。用户空间的数据被复制到这个新分配的sk_buff
中。 - 发送缓冲区:
sk_buff
被加入到Socket关联的发送缓冲区中。 - TCP协议处理: 如果使用TCP,因为
sk_buff
后续在调用网络层,最后到达网卡发送完成的时候,这个sk_buff
会被释放掉。而 TCP 协议是支持丢失重传的,在收到对方的 ACK 之前,这个sk_buff
不能被删除,sk_buff
的副本会在传输层(TCP、UDP)被创建。 - 协议封装:
sk_buff
随后经历协议栈各层的处理,每一层添加它自己的头信息。对于TCP,会添加TCP头;对于IP层,添加IP头;在数据链路层,会添加如以太网帧头。 - 头部空间: 在
sk_buff
中,head
指针指向缓冲区的起始位置,data
指针指向当前层数据的起始位置。通过调整data
指针,可以在head
和data
之间预留空间,用于填充协议头。 - 数据传输: 一旦协议封装完成,数据包(
sk_buff
)就被发送到网络接口层,准备通过物理硬件发送出去。
- 在TCP协议封装之后,传输层将检查
sk_buff
中的数据量是否超过了MSS大小。- 如果数据量超过MSS,TCP层会将数据分为几个段,每个段的大小不会超过MSS。这是为了确保每个TCP段在加上TCP头之后形成的IP数据包,加上IP头之后仍然不会超过MTU的大小,避免在IP层进行分片。
- 这些分段后的数据会被封装在各自的
sk_buff
中,并加上TCP头。
在接收数据包的过程中,sk_buff
中的data
指针会向后移动(增加),以剥离协议头部,而在发送数据包的过程中,data
指针会向前移动(减少),为协议头预留空间。这种处理方式允许在不同的网络层之间传递数据包时避免不必要的数据拷贝,优化了CPU的使用效率。
(a) 空的
sk_buff
结构体: 初始化状态,没有数据。(b) 分配内存: 在
sk_buff
的缓冲区中分配了足够的内存,这包括head
、data
、tail
和end
指针,为添加协议头部留出了空间。此时data
和tail
指向相同的位置,表示还没有添加任何数据。© 添加L1层数据(比如MAC层或物理层头部): 在
data
指针之前添加了L1层的头部信息。此时data
指针向下移动,指向L1头部的开始。len
表示sk_buff
中总的数据长度。(d) 添加L2层数据(比如TCP头部): 在L1层头部之前进一步添加了L2层的头部(例如TCP头部)。
data
指针继续上移,len
增加。(e) 添加L3层数据(比如IP头部): 接着添加了L3层的头部(例如IP头部),
data
指针继续上移,len
再次增加。(f) 添加L4层数据(比如以太网头部): 最后,添加了L4层的头部信息(例如以太网头部),完成了数据包的封装过程。
data
指针现在指向整个封装好的数据包的起始位置,len
为整个数据包的长度。在发送数据包的过程中,每次添加新的协议头部,都会调整
sk_buff
中的data
指针,将其向上移动到新的头部的开始位置,同时更新len
来反映新的总长度。这样,就可以在不进行数据拷贝的情况下,动态地构建网络数据包。
上面就是传输层的过程的,接下来就是网络层。
- 网络层处理:
- 网络层选择路由(下一跳IP),填充IP头,执行netfilter过滤。
- 如果数据包超过MTU,进行IP层的分片。
- 分片后的数据包仍然封装在
sk_buff
结构体中。
- 网络接口层:
- 通过ARP协议获取下一跳的MAC地址。
- 填充数据链路层的帧头和帧尾到
sk_buff
。 - 将
sk_buff
放入网卡的发送队列。
- 硬件处理:
- 触发软中断,通知网卡驱动有新的数据包待发送。
- 网卡驱动从发送队列读取
sk_buff
,将其数据映射到网卡的DMA区域。 - 网卡完成物理发送。
- 资源清理和确认:
- 发送完成后,网卡设备触发硬中断,释放与发送相关的资源,包括
sk_buff
内存和RingBuffer的清理。 - 对于TCP数据包,当收到对方的ACK确认后,传输层释放保留的原始
sk_buff
副本。
- 发送完成后,网卡设备触发硬中断,释放与发送相关的资源,包括
发送网络数据的时候,涉及几次内存拷贝操作?
- 系统调用和
sk_buff
的初始分配:- 当应用程序第一次调用发送数据的系统调用时,内核为这些数据分配一个新的
sk_buff
。 - 用户空间的数据被复制到这个新分配的
sk_buff
中。 - 这个
sk_buff
随后被加入到与Socket关联的发送缓冲区。
- 当应用程序第一次调用发送数据的系统调用时,内核为这些数据分配一个新的
- TCP处理和
sk_buff
的克隆:- 在使用TCP协议时,传输层到网络层的过渡阶段中,为了支持TCP的可靠传输(重传机制),每个
sk_buff
被克隆为一个新副本。 - 副本
sk_buff
(包含TCP头和数据)被传递到网络层,用于实际发送,而原始sk_buff
则保留在传输层。 - 当数据包发送完成且对应的ACK收到后,保留在传输层的原始
sk_buff
会被释放。
- 在使用TCP协议时,传输层到网络层的过渡阶段中,为了支持TCP的可靠传输(重传机制),每个
- IP层的分片处理:
- 如果网络层(IP层)发现
sk_buff
的大小超过了MTU,它会进行分片处理。 - 为此,内核会为每个分片数据包申请新的
sk_buff
。 - 原始
sk_buff
的内容被分割拷贝到新的sk_buff
中,每个分片sk_buff
包含原始数据包的一部分,并添加相应的IP头。
- 如果网络层(IP层)发现
总结
- 应用层到传输层:
- 应用程序通过Socket API发送数据,触发用户态到内核态的切换。
- 内核分配
sk_buff
,将用户数据复制进去,并将其放入发送缓冲区。 - 在传输层,如果使用TCP,数据可能会被分段以符合MSS的要求。
- 网络层处理:
- 网络层选择路由(下一跳IP),填充IP头,执行netfilter过滤。
- 如果数据包超过MTU,进行IP层的分片。
- 分片后的数据包仍然封装在
sk_buff
结构体中。
- 网络接口层:
- 通过ARP协议获取下一跳的MAC地址。
- 填充数据链路层的帧头和帧尾到
sk_buff
。 - 将
sk_buff
放入网卡的发送队列。
- 硬件处理:
- 触发软中断,通知网卡驱动有新的数据包待发送。
- 网卡驱动从发送队列读取
sk_buff
,将其数据映射到网卡的DMA区域。 - 网卡完成物理发送。
- 资源清理和确认:
- 发送完成后,网卡设备触发硬中断,释放与发送相关的资源,包括
sk_buff
内存和RingBuffer的清理。 - 对于TCP数据包,当收到对方的ACK确认后,传输层释放保留的原始
sk_buff
副本。
- 发送完成后,网卡设备触发硬中断,释放与发送相关的资源,包括