服务器之家:专注于服务器技术及软件下载分享
分类导航

Linux|Centos|Ubuntu|系统进程|Fedora|注册表|Bios|Solaris|Windows7|Windows10|Windows11|windows server|

服务器之家 - 服务器系统 - Linux - Linux 网络发包流程

Linux 网络发包流程

2023-09-06 17:06未知服务器之家 Linux

哈喽大家好,我是咸鱼。 之前咸鱼在《Linux 网络收包流程》一文中介绍了 Linux 是如何实现网络接收数据包的。 简单回顾一下: 数据到达网卡之后,网卡通过 DMA 将数据放到内存分配好的一块ring buffer中,然后触发硬中断 CPU 收到硬

哈喽大家好,我是咸鱼。

之前咸鱼在《Linux 网络收包流程》一文中介绍了 Linux 是如何实现网络接收数据包的。

简单回顾一下:

  • 数据到达网卡之后,网卡通过 DMA 将数据放到内存分配好的一块ring buffer中,然后触发硬中断
  • CPU 收到硬中断之后简单的处理了一下(分配skb_buffer),然后触发软中断
  • 软中断进程ksoftirqd执行一系列操作(例如把数据帧从ring ruffer上取下来)然后将数据送到三层协议栈中
  • 在三层协议栈中数据被进一步处理发送到四层协议栈
  • 在四层协议栈中,数据会从内核拷贝到用户空间,供应用程序读取
  • 最后被处在应用层的应用程序去读取

当 Linux 要发送一个数据包的时候,这个包是怎么从应用程序再到 Linux 的内核最后由网卡发送出去的呢?

那么今天咸鱼将会为大家介绍 Linux 是如何实现网络发送数据包。

发包流程

假设我们的网卡已经启动好(分配和初始化 RingBuffer) 且 server 和 client 已经建立好 socket

这里需要注意的是,网卡在启动过程中申请分配的 RingBuffer 是有两个:

  • igb_tx_buffer数组:这个数组是内核使用的,用于存储要发送的数据包描述信息,通过vzalloc申请的
  • e1000_adv_tx_desc数组:这个数组是网卡硬件使用的,用于存储要发送的数据包,网卡硬件可以通过 DMA 直接访问这块内存,通过dma_alloc_coherent分配

igb_tx_buffer数组中的每个元素都有一个指针指向e1000_adv_tx_desc;

这样内核就可以把要发送的数据填充到e1000_adv_tx_desc数组上;

然后网卡硬件会直接从e1000_adv_tx_desc数组中读取实际数据,并将数据发送到网络上。

Linux 网络发包流程

拷贝到内核

socket 系统调用将数据拷贝到内核

应用程序首先通过 socket 提供的接口实现系统调用,我们在用户态使用的 send 函数和 sendto 函数其实都是 sendto 系统调用实现的,send/sendto函数 只是为了用户方便,封装出来的一个更易于调用的方式而已。

Linux 网络发包流程

在 sendto 系统调用内部,首先 sockfd_lookup_light 函数会查找与给定文件描述符(fd)关联的 socket;

接着调用 sock_sendmsg 函数(sock_sendmsg ==> __sock_sendmsg ==> __sock_sendmsg_nosec);

其中 sock->ops->sendmsg 函数实际执行的是 inet_sendmsg 协议栈函数:

Linux 网络发包流程

这时候内核会去找 socket 上对应的具体协议发送函数。

以 TCP 为例,具体协议发送函数为 tcp_sendmsg:

Linux 网络发包流程

tcp_sendmsg 会去申请一个内核态内存 skb(sk_buff) ,然后挂到发送队列上(发送队列是由 skb 组成的一个链表):

Linux 网络发包流程

接着把用户待发送的数据拷贝到 skb 中,拷贝之后会触发【发送】操作,这里说的发送是指在当前上下文中,待发送数据从 socket 层发送到传输层。

需要注意的是,这时候不一定开始真正发送,因为还要进行一些条件判断(比如说发送队列中的数据已经超过了窗口大小的一半)

只有满足了条件才能够发送,如果没有满足条件这次系统调用就可能直接返回了。

网络协议栈处理

传输层处理

接着数据来到了传输层,传输层主要看 tcp_write_xmit 函数,这个函数处理了传输层的拥塞控制、滑动窗口相关的工作,该函数会根据发送窗口和最大段大小等因素计算出本次发送的数据大小,然后将数据封装成 TCP 段并发送出去,如果满足窗口要求,设置 TCP 头然后将数据传到更低的网络层进行处理。

在传输层中,内核主要做了两件事:

(1) 复制一份数据(skb)

为什么要复制一份出来呢?因为网卡发送完成之后,skb 会被释放掉,但 TCP 协议是支持丢失重传的,所以在收到对方的 ACK 之前必须要备份一个 skb 去为重传做准备。

实际上一开始发送的是 skb 的拷贝版,收到了对方的 ACK 之后系统才会把真正的 skb 删除掉。

(2) 封装 TCP 头

系统会根据实际情况添加 TCP 头封装成 TCP 段。

这里需要知道的是:每个 skb 内部包含了网络协议中的所有头部信息,例如 MAC 头、IP 头、TCP/UDP 头等,在设置这些头部时,内核会通过调整指针的位置来填充相应的字段,而不是频繁申请和拷贝内存。

Linux 网络发包流程

比如说在设置 TCP 头的时候,只是把指针指向 skb 的合适位置。后面再设置 IP 头的时候,再把指针挪一挪就行。

这种方式利用了 skb 数据结构的链表特性可以避免内存分配和数据拷贝所带来的性能开销,从而提高数据传输的效率。

网络层处理

数据离开了传输层之后,就来到了网络层。

网络层主要做下面的事情:

(1) 路由项查找:

根据目标 IP 地址查找路由表,确定数据包的下一跳(ip_queue_xmit 函数)。

(2) IP 头设置:

根据路由表查找的结果,设置 IP 头中的源和目标 IP 地址、TTL(生存时间)、IP 协议等字段。

(3) netfilter 过滤:

netfilter 是 Linux 内核中的一个框架,用于实现数据包的过滤和修改。

在网络层,netfilter 可以用于对数据包进行过滤、NAT(网络地址转换)等操作。

(4) skb 切分:

如果数据包的大小超过了 MTU(最大传输单元),需要将数据包进行切分成多个片段,以适应网络传输,每个片段会被封装成单独的 skb。

数据链路层处理

当数据来到了数据链路层之后,会有两个子系统协同工作,确保数据包在发送和接收过程中能够正确地对数据进行封装、解析和传输。

(1) 邻居子系统

管理和维护主机或路由器与其它设备之间的邻居关系,邻居子系统里会发送 arp 请求找邻居,然后把邻居信息存在邻居缓存表里,用于存储目标主机的 MAC 地址。

当需要发送数据包到某个目标主机时,数据链路层会首先查询邻居缓存表,以获取目标主机的 MAC 地址,从而正确地封装数据包(封装 MAC 头)。

(2) 网络设备子系统

网络设备子系统负责处理与物理网络接口相关的操作,包括数据包的封装和发送,以及从物理接口接收数据包并进行解析。

网络设备子系统不但处理数据包的格式转换,如在以太网中添加帧头和帧尾,以及从帧中提取数据,还负责处理硬件相关的操作,如发送和接收数据包的时钟同步、物理层错误检测等。

(3) 到达网卡发送队列

接着网络设备子系统会选择一个合适的网卡发送队列并把 skb 添加到队列中(绕过软中断处理程序),然后,内核会调用网卡驱动的入口函数 dev_hard_start_xmit 来触发数据包的发送。

在一些情况下,邻居子系统还会将 skb 数据包添加到软中断队列(softnet_data)上,并触发软中断(NET_TX_SOFTIRQ),这个过程是为了将 skb 数据包交给软中断处理程序进行进一步处理和发送。软中断处理程序会负责实际的数据包发送,这就是为什么一般服务器上查看 /proc/softirqs,一般 NET_RX 都要比 NET_TX 大的多的原因之一。

即对于收包来说,都是要经过 NET_RX 软中断;而对于发包来说,只有某些情况下才触发 NET_TX 软中断:

数据发送

Linux 网络发包流程

驱动程序从发送队列中读取 skb 的描述信息,将其挂到 RingBuffer 上(前面提到的igb_tx_buffer 数组)

接着将 skb 的描述信息映射到网卡可访问的内存 DMA 区域中(前面提到的e1000_adv_tx_desc 数组)

网卡会直接从 e1000_adv_tx_desc 数组中根据描述信息读取实际数据并将数据发送到网络。这样就完成了数据包的发送过程

收尾工作

当数据发送完成后,网卡设备会触发一个中断(NET_RX_SOFTIRQ),这个中断通常称为“发送完成中断”或者“发送队列清理中断”;

这个中断的主要作用是执行发送完成的清理工作,包括释放之前为数据包分配的内存,即释放 skb 内存和 RingBuffer 内存;

最后,当收到这个 TCP 报文的 ACK 应答时,传输层就会释放原始的 skb(前面有讲到发送的其实是 skb 的拷贝版)。

可以看到,当数据发送完成以后,通过硬中断的方式来通知驱动发送完毕,而这个中断类型是NET_RX_SOFTIRQ。

前面我们讲到过网卡收到一个网络包的时候,会触发NET_RX_SOFTIRQ中断去告诉 CPU 有数据要处理,也就是说,无论是网卡接收一个网络包还是发送网络包结束之后,触发的都是 NET_RX_SOFTIRQ。

总结

最后总结一下在 Linux 系统中发送网络数据包的流程:

Linux 网络发包流程

最后总结一下在 Linux 系统中发送网络数据包的流程:

(1) 应用程序通过 socket 提供的接口进行系统调用,将数据从用户态拷贝到内核态的 socket 缓冲区中

(2) 网络协议栈从 socket 缓冲区中拿取数据,并按照 TCP/IP 协议栈从上到下逐层处理

  • 传输层处理:以 TCP 为例,在传输层中会复制一份数据(为了丢失重传),然后为数据封装 TCP 头
  • 网络层处理:选取路由(确认下一跳的 IP)、填充 IP 头、netfilter 过滤、对超过 MTU 大小的数据包进行分片等操作
  • 邻居子系统和网络设备子系统处理:在这里数据会被进一步处理和封装,然后被添加到网卡的发送队列中

(4) 驱动程序从发送队列中读取 skb 的描述信息然后挂在 RingBuffer 上,接着将 skb 的描述信息映射到网卡可访问的内存 DMA 区域中

(5) 网卡将数据发送到网络

(6) 当数据发送完成后触发硬中断,释放 skb 内存和 RingBuffer 内存

延伸 · 阅读

精彩推荐
  • Linuxlinux设置tomcat自启动的方法

    linux设置tomcat自启动的方法

    这篇文章主要介绍了linux设置tomcat自启动的方法,需要的朋友可以参考下...

    Linux教程网8512021-10-10
  • Linux理解 Linux/Unix 登录脚本的技巧

    理解 Linux/Unix 登录脚本的技巧

    有一些常见的情况,例如从Debian的包管理程序到Iaas的管理中,很多任务需要设置环境变量才能正常运行。 有时,程序通常只需要在 登陆时运行一次,例如...

    未知1042023-05-12
  • LinuxLinux系统下无法卸载挂载的目录怎么办?

    Linux系统下无法卸载挂载的目录怎么办?

    我们在日常运维中经常性会遇到需要进行磁盘的扩容、卸载、挂载等操作,但是有时候这个系统上跑的应用并没有停止或者有其他的运维同事在操作这个目...

    今日头条10302020-12-30
  • Linux将 Linux 终端与 Nautilus 文件管理器结合起来

    将 Linux 终端与 Nautilus 文件管理器结合起来

    Nautilus 是 GNOME 桌面环境中的图形化文件浏览器。你可以使用它来访问和管理系统中的文件和文件夹。 尽管并非所有人都喜欢使用终端来管理文件和目录,...

    未知812023-08-08
  • Linux在Linux系统中创建新的亚马逊AWS访问密钥的方法

    在Linux系统中创建新的亚马逊AWS访问密钥的方法

    如何在Linux系统中创建新的亚马逊AWS访问密钥?我在配置一个需要访问我的亚马逊AWS帐号的应用时被要求提供AWS访问密钥ID和秘密访问密钥,我怎样创建一个...

    Linux教程网6182019-10-30
  • Linuxssh 登录很慢该如何解决

    ssh 登录很慢该如何解决

    这篇文章主要介绍了ssh 登录很慢该如何解决的相关资料,这里提供了两种方法,DNS反向解析及关闭ssh的gssapi认证的解决办法,需要的朋友可以参考下...

    linuxeye9922021-12-16
  • Linuxlinux驱动程序开发详细介绍

    linux驱动程序开发详细介绍

    前提,一般来说内核代码的错误可能会引起一个用户进程的死亡,或者整个系统的瘫痪,更严重的后果,可能导致磁盘损伤~因此建议最好有一台实验机进行...

    Linux教程网5392019-12-17
  • Linuxlinux top命令详解

    linux top命令详解

    这篇文章主要介绍了linux top命令详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    sparkdev5622022-03-01