Linux 收发网络包的流程

  1. 应用层:

    • 功能:提供应用程序间通信。
    • 例子:电子邮件客户端如Outlook或Thunderbird,它们提供用户界面来发送和接收电子邮件。这些客户端使用SMTP(用于发送邮件)和IMAP或POP3(用于接收邮件)这样的应用层协议。
  2. 表示层:

    • 功能:确保信息的语义在不同系统间传输时保持不变,通常涉及数据格式转换。
    • 例子:当一个Web服务将数据从一种格式(如数据库中的二进制数据)转换为另一种格式(如JSON或XML)以供Web客户端使用时,就是表示层在起作用。
  3. 会话层:

    • 功能:建立、管理和终止应用程序之间的会话。
    • 例子:在网银交易中,当用户登录到他们的账户进行操作时,会话层负责维护和管理这个会话的完整性和安全性,例如RPC。
  4. 传输层:

    • 功能:实现端到端的数据传输。
    • 例子:TCP(传输控制协议)确保数据包的正确顺序传输,比如在一个网页请求中从服务器传送HTML文件到你的浏览器。
  5. 网络层:

    • 功能:负责数据的路由选择和传输。
    • 例子:当你在浏览器中输入一个网址时,IP协议(网络层的一部分)帮助确定数据包应该如何从你的设备路由到目标服务器。
  6. 数据链路层:

    • 功能:确保在同一网络中或两个直接连接的网络设备间的可靠数据传输。
    • 例子:以太网(Ethernet)是数据链路层的一个实例,它通过MAC地址对网络中的设备进行识别和通信。
  7. 物理层:

    • 功能:在物理媒介上传输原始比特流。
    • 例子:双绞线、光纤或无线电波等传输媒介,在这些媒介上,物理层以电信号、光脉冲或无线信号的形式传输数据。

Linux 接收网络包的流程

网卡是计算机里的一个硬件,专门负责接收和发送网络包,当网卡接收到一个网络包后,会通过 DMA 技术,将网络包写入到指定的内存地址,也就是写入到 Ring Buffer ,这个是一个环形缓冲区,接着就会告诉操作系统这个网络包已经到达。

DMA(Direct Memory Access)是一种允许某些硬件子系统直接访问主内存的技术,用于数据传输。当网卡接收到一个网络包后,使用DMA技术可以直接将网络包写入到计算机内存的指定地址,而不需要CPU的介入,这样可以显著提高数据传输的效率。

DMA的工作原理如下:

  1. 硬件配置: 网卡(或其他DMA支持的硬件设备)被配置为对特定内存地址有直接访问的权限。
  2. 接收数据: 当网卡接收到数据(如网络包)时,它会启动DMA传输。
  3. 内存写入: DMA控制器接管数据传输任务,直接将数据从网卡传输到预定的内存地址,无需CPU参与。这意味着CPU可以同时处理其他任务,而不必被数据传输过程所占用。
  4. 中断信号: 数据传输完成后,DMA控制器会发送一个中断信号给CPU,告知它数据已经被成功传输并存储在内存中。

通过这种方式,DMA提高了数据处理的效率,尤其是在处理大量数据或高速数据流时。它是现代计算机和网络设备中不可或缺的一部分,用于优化数据传输过程,减轻CPU的负担。

如何知道网络包到了

每当网卡收到了一个网络包,就会触发一个硬中断,告诉操作系统要去拿数据咯。

但是这就会有一个致命的问题,因为网卡触发的是硬中断,优先级很高,如果这时候有很多很多的网络包发送过来,那岂不是得一直硬中断了,那还怎么处理其他的进程呢?

因此,为了解决频繁中断带来的性能开销,Linux 内核在 2.6 版本中引入了 NAPI 机制

NAPI(New API)机制是为了改进Linux内核处理网络数据包的方式,特别是在高速网络环境下。传统的网络数据包处理机制是完全中断驱动的:每收到一个数据包就产生一个硬中断,这在数据包到达速率非常高的时候会导致大量的CPU中断,从而影响系统性能。

NAPI的目标是通过引入轮询机制来减少在高负载下处理网络数据包时的中断数量。NAPI机制工作的基本流程如下:

  1. 正常模式: 当网络负载较低时,系统使用传统的中断驱动机制。网卡接收到数据包时会产生中断,由中断处理函数(Interrupt Service Routine, ISR)来处理。
  2. 高负载检测: 当网卡开始接收到大量的数据包时,ISR会判断是否需要转为NAPI模式。这通常基于接收到数据包的速率。
  3. NAPI模式: 在NAPI模式下,硬中断会被禁用,ISR会将网卡设置到一个轮询模式。在轮询模式中,不是对每个接收到的数据包产生硬中断,而是在网络驱动的轮询函数中处理多个数据包。
  4. 轮询函数处理: 内核会定期调用网卡驱动的轮询函数(也称为poll函数),该函数负责批量处理接收到的数据包。处理过程中,如果数据包到达速率降低,或者已经处理了足够多的数据包,轮询函数会重新启用硬中断并退出NAPI模式。

例子:

假设你有一个网卡支持10Gb/s的速率,当网络流量低时,每个数据包的到达会触发一个硬中断,由CPU处理。这在低流量下是可行的。

然而,在一个高流量场景下,比如服务器正在处理成千上万的并发连接,或者正在进行大规模数据传输,这时每个数据包都触发中断就会导致CPU花费大量时间去处理中断本身,而不是数据包。在这种情况下,启用NAPI模式可以显著减少中断的数量,网卡驱动会在轮询函数中一次处理多个数据包,减轻了CPU的负担,提高了系统的整体性能。

也就是说,正常情况下还是通过硬中断的方式去处理网络包,当请求较大的时候会开启NAPI模式,提升整体性能。

在Linux内核当中有一个ksoftirqd线程是专门来处理负载很重的软中断的。

  1. 网络包接收: 当网卡接收到网络数据包,它们首先被放入到网卡的接收环形缓冲区(Ring Buffer)中。
  2. 触发软中断: 一旦环形缓冲区中有数据包,网卡会触发一个软中断(或者在高流量情况下由硬中断切换到轮询模式后,通过轮询来处理)。这个软中断是通过内核的软中断系统(softirq)来处理的,而不是传统的硬中断处理方式。
  3. ksoftirqd处理: 如果软中断的处理负载很重,可能会唤醒专门的内核线程ksoftirqd来处理。这个内核线程会从接收环形缓冲区中取出数据包,并封装成sk_buff结构体。
  4. 交付协议栈: 一旦数据包被封装到sk_buff,它就会被交付到网络协议栈进行进一步处理。这包括从数据链路层到网络层,然后到传输层,最终可能到应用层。
  5. 逐层处理: 在协议栈的每一层,sk_buff会被相应的层处理。例如,在网络层,IP头部会被解析,并根据需要进行路由。在传输层,TCP或UDP头部会被处理,并且数据会被传递到相应的socket缓冲区。

总结

  1. 网络接口层(数据链路层):
    • 数据包首先到达网络接口,并被放置在环形缓冲区(Ring Buffer)中。
    • 当环形缓冲区接收到数据后,会触发一个软中断(或者在某些情况下是NAPI轮询机制)。
    • ksoftirqd内核线程(或者在高负载时直接由中断上下文)处理这些软中断,从环形缓冲区中取出数据帧。
    • 使用sk_buff结构来表示网络包,并进行基本的合法性检查,如CRC检验等,不合法的帧会被丢弃。
    • 识别上层协议(如IPv4, IPv6),并移除数据链路层的头(如以太网帧头)。
  2. 网络层:
    • 在网络层,包括IP头的网络数据包会被进一步处理。
    • 内核检查IP包的目的地址,决定是将数据包传递给上层协议还是路由转发。
    • 对于本机处理的包,内核检查IP头部确定上层协议(如TCP或UDP),并移除IP头。
  3. 传输层:
    • 在传输层,TCP或UDP头被处理。
    • 根据源IP地址、源端口、目的IP地址和目的端口四元组标识找到对应的套接字(Socket)。
    • 数据被放入相应Socket的接收缓冲区。
  4. 应用层:
    • 应用程序通过系统调用(如readrecvfrom),请求从套接字缓冲区读取数据。
    • 数据从内核空间的Socket接收缓冲区复制到用户空间的应用层缓冲区。
    • 应用程序继续处理接收到的数据。

在这个过程中,内核对网络数据包的处理是分层的,并且每层只处理对应层次的协议头和数据。利用sk_buff来在各层之间传递数据包可以实现有效的数据处理,因为它避免了不必要的数据复制,从而提高了性能。

Linux 发送网络包的流程

  1. 系统调用: 应用程序调用Socket API来发送数据。这会触发从用户态到内核态的切换。
  2. 内存分配: 内核为要发送的数据分配sk_buff结构体。用户空间的数据被复制到这个新分配的sk_buff中。
  3. 发送缓冲区: sk_buff被加入到Socket关联的发送缓冲区中。
  4. TCP协议处理: 如果使用TCP,因为 sk_buff 后续在调用网络层,最后到达网卡发送完成的时候,这个 sk_buff会被释放掉。而 TCP 协议是支持丢失重传的,在收到对方的 ACK 之前,这个 sk_buff 不能被删除,sk_buff的副本会在传输层(TCP、UDP)被创建。
  5. 协议封装: sk_buff随后经历协议栈各层的处理,每一层添加它自己的头信息。对于TCP,会添加TCP头;对于IP层,添加IP头;在数据链路层,会添加如以太网帧头。
  6. 头部空间:sk_buff中,head指针指向缓冲区的起始位置,data指针指向当前层数据的起始位置。通过调整data指针,可以在headdata之间预留空间,用于填充协议头。
  7. 数据传输: 一旦协议封装完成,数据包(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的缓冲区中分配了足够的内存,这包括headdatatailend指针,为添加协议头部留出了空间。此时datatail指向相同的位置,表示还没有添加任何数据。

© 添加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来反映新的总长度。这样,就可以在不进行数据拷贝的情况下,动态地构建网络数据包。

上面就是传输层的过程的,接下来就是网络层。

  1. 网络层处理:
    • 网络层选择路由(下一跳IP),填充IP头,执行netfilter过滤。
    • 如果数据包超过MTU,进行IP层的分片。
    • 分片后的数据包仍然封装在sk_buff结构体中。
  2. 网络接口层:
    • 通过ARP协议获取下一跳的MAC地址。
    • 填充数据链路层的帧头和帧尾到sk_buff
    • sk_buff放入网卡的发送队列。
  3. 硬件处理:
    • 触发软中断,通知网卡驱动有新的数据包待发送。
    • 网卡驱动从发送队列读取sk_buff,将其数据映射到网卡的DMA区域。
    • 网卡完成物理发送。
  4. 资源清理和确认:
    • 发送完成后,网卡设备触发硬中断,释放与发送相关的资源,包括sk_buff内存和RingBuffer的清理。
    • 对于TCP数据包,当收到对方的ACK确认后,传输层释放保留的原始sk_buff副本。

发送网络数据的时候,涉及几次内存拷贝操作?

  1. 系统调用和sk_buff的初始分配:
    • 当应用程序第一次调用发送数据的系统调用时,内核为这些数据分配一个新的sk_buff
    • 用户空间的数据被复制到这个新分配的sk_buff中。
    • 这个sk_buff随后被加入到与Socket关联的发送缓冲区。
  2. TCP处理和sk_buff的克隆:
    • 在使用TCP协议时,传输层到网络层的过渡阶段中,为了支持TCP的可靠传输(重传机制),每个sk_buff被克隆为一个新副本。
    • 副本sk_buff(包含TCP头和数据)被传递到网络层,用于实际发送,而原始sk_buff则保留在传输层。
    • 当数据包发送完成且对应的ACK收到后,保留在传输层的原始sk_buff会被释放。
  3. IP层的分片处理:
    • 如果网络层(IP层)发现sk_buff的大小超过了MTU,它会进行分片处理。
    • 为此,内核会为每个分片数据包申请新的sk_buff
    • 原始sk_buff的内容被分割拷贝到新的sk_buff中,每个分片sk_buff包含原始数据包的一部分,并添加相应的IP头。

总结

  1. 应用层到传输层:
    • 应用程序通过Socket API发送数据,触发用户态到内核态的切换。
    • 内核分配sk_buff,将用户数据复制进去,并将其放入发送缓冲区。
    • 在传输层,如果使用TCP,数据可能会被分段以符合MSS的要求。
  2. 网络层处理:
    • 网络层选择路由(下一跳IP),填充IP头,执行netfilter过滤。
    • 如果数据包超过MTU,进行IP层的分片。
    • 分片后的数据包仍然封装在sk_buff结构体中。
  3. 网络接口层:
    • 通过ARP协议获取下一跳的MAC地址。
    • 填充数据链路层的帧头和帧尾到sk_buff
    • sk_buff放入网卡的发送队列。
  4. 硬件处理:
    • 触发软中断,通知网卡驱动有新的数据包待发送。
    • 网卡驱动从发送队列读取sk_buff,将其数据映射到网卡的DMA区域。
    • 网卡完成物理发送。
  5. 资源清理和确认:
    • 发送完成后,网卡设备触发硬中断,释放与发送相关的资源,包括sk_buff内存和RingBuffer的清理。
    • 对于TCP数据包,当收到对方的ACK确认后,传输层释放保留的原始sk_buff副本。
相关推荐
A小辣椒15 小时前
TShark:Wireshark CLI 功能
linux
A小辣椒19 小时前
TShark:基础知识
linux
AlfredZhao21 小时前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao1 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
大树882 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质2 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush42 天前
嵌入式linux学习记录十四、术语
linux·嵌入式