3.Linux系统如何收发网络包
1.网络模型
为了使多种设备能够通过网络通信,为解决不同设备在网络中的兼容性,国际标准性组织开发了OSI网络模型,即应用层、表示层、会话层、传输层、网络层、数据链路层以及物理层。
- 网络层:给应用程序提供统一接口;
- 表示层:将数据转换成兼容另一个系统识别的格式;
- 会话层:负责建立、管理和终止表示层实体之间的通信会话;
- 传输层:负责端到端的数据传输;
- 网络层:负责数据路由、转发、分片;
- 数据链路层:负责数据的封帧以及差错校验,以及MAC寻址
- 物理层:负责在物理网络中传输帧。
TCP/IP网络模型共有四层,分别是应用层、传输层、网络层、网络接口层
- 应用层:负责向用户提供一组应用程序,如HTTP、DNS、FTP等;
- 传输层:负责端对端的通信,如TCP、UDP等;
- 网络层:负责网络包的封装、分片、路由、转发,如IP、ICP等;
- 网络接口层:负责网络包在物理网络中的传输,如网络包的封帧、MAC寻址、差错校验,通过网卡传输数据帧。
2. Linux网络协议栈
网络分层格式如下:
传输层中添加了TCP头,网络层给TCP数据加上IP头,网络接口层给IP数据前后分别加上帧头和帧尾。在数据链路层中传输数据并不能传输任意大小的数据包,在以太网中规定了最大传输党员(MYU)是1500字节,即规定单次传输的最大IP包大小。
当网络包数据大小超过1500字节时,就会在网络层分片,确保分片后IP包不能超过MTU大小,如果MTU太小,分包较多,网络吞吐能力越差。反之越好。
网络数据传输协议栈结构如下:
应用层程序通通过过系统调用,跟socket层进行数据交互;Socket层下面是传输层、网络层、网络接口层;最下面一层是网卡驱动程序和硬件网卡设备;LVS(Linux Virtual Server)是基于Linux的高性能、高可靠性负载均衡器。
3.Linux接收网络包的流程
网卡时计算机中的一个硬件,专门负责接收和发送网络包,当网卡接收到一个网络包时,利用DMA技术将网络包写入到指定的内存地址,即写入RingBuffer,RingBuffer是一个环形缓冲区,接着告诉操作系统该网络已到达。
DMA (Direct Memory Access)是一种计算机输入/输出技术,它允许外设(网卡、硬盘)直接访问系统内存,而不需要CPU干预,从而提高数据传输的效率。DMA技术的目的是减少CPU在数据传输和处理中的负担,从而提高系统的整体性能。
3.1 如何告诉操作系统这个网络包已到达?
每当网卡接收到一个网络包时就会触发一个中断,以此来告诉操作系统接收到了一个网络包。高性能网络场景下,网络包数据多,就会触发非常多的中断,当CPU收到中断,就会暂停当前任务,去处理网络包,处理完毕后,才会去执行暂停的任务。频繁中断会导致CPU一直没完没了的中断当前任务,导致系统效率降低。
为解决频繁中断带来的性能下降问题,Linux在2.6版本引入NAPI机制,它混合[中断和轮询]的方式接收网络包,它的核心是不采用中断的方式读取数据,而是首先采用中断唤醒数据接收服务程序,然后以poll的方式来轮询数据。
操作系统接收网络流程:当网络包到达时,通过DMA技术将网络包写入到指定的内存地址即Ringbuffer,接着网卡向CPU发送硬件中断。当CPU接收到硬件中断请求后,根据中断表,调用已注册的中断处理函数。
硬件中断处理函数作用:
- 先暂时屏蔽中断,表示已经知道内存有数据,告诉网卡下次接收到数据包直接写内存即可,不用通知CPU,因此可提高系统效率,避免CPU频繁中断;
- 发起软中断,恢复刚才屏蔽的中断。
- 硬件中分段处理函数做的事情少,主要耗时的任务交给runaway中断处理程序。
3.2 软中断的处理
内核中的ksoftirqd线程专门处理软中断任务,当ksoftirqd内核线程收到中断后,就会轮询处理数据。ksoftirqd线程从Ringbuffer中获取一个数据帧,用sk_buff表示它,从而将其作为一个网络包嫁给网络协议栈进行逐层处理。
3.3 网络协议栈
首先网络包进入网络接口层,该层检查报文的合法性,如不合法丢弃,合法则会找出该网络包上层协议的类型,如IPV4,还是IPV6,接着去掉帧头和帧尾,交给网络层。
当网络包进入到网络层后,先取出IP包,判断包下一步走向,如交给上层处理还是转发出去,当确定该网络包时发送给本机后,就从IP头查看上一层协议类型是TCP还是UDP,接着去掉IP头,交给传输层。
当网络包到达传输层后,从中去除TCP头或UDP头,根据四元组[源IP、源端口、目标IP、目标端口]作为标识,找到对应的Socket,并将数据放到Socket的接收缓存区。最后应用程序调用Socket接口,将内核的Socket接收缓存区的数据[拷贝]到应用层的缓存区,然后唤醒用户进程。发送和接收流程如图所示:
4 Linux发送网络包的流程
如上图右边所示,发送网络包流程图的过程与左边的接收网络包相反。首先,应用程序会调用Socket发送数据包的接口,由于该操作数据系统调用,因此从用户态陷入到内核态中的Socket层,内核会申请一个内核态的sk_buff内存,将用户待发送的数据拷贝到sk_buff内存,并将其加入到发送缓存区。接着,网络协议栈从Socket发送缓冲区中去除sk_buff,并按照TCP/IP协议是从上到下处理。如果是使用TCP协议传输协议发送数据,会先拷贝一个新的sk_buff副本,因此TCP协议是可靠性传输协议,如果数据包成功传输到网口之前丢失数据,可以进行重传,且会将该sk_buff释放掉。当对方没有ACK,该sk_buff不会被删除,实际传输的是sk_buff的拷贝。然后对sk_buff填充TCP头。
Linux操作系统为了在层级之间传输数据时,不发生拷贝,只用sk_buff一个结构体描述所有网络包,该操作主要是通过调整sk_buff的data指针完成:
- 当接收到报文时,从网卡驱动开始通过协议层网上传输数据包,通过增加skb->data的值,来逐步剥离协议首部;
- 当发送报文时,创建sk_buff结构体,数据缓存区的头部预留足够空间来填充各层首部。在经过各层协议时,通过减少skb_data的值增加协议首部。
发送报文时,data指针移动过程:
当网络包到达网络层时,首先选取路由(确认下一跳的 IP)、填充 IP 头、netfilter 过滤、对超过 MTU 大小的数据包进行分片。处理完这些工作后会交给网络接口层处理。
当网络包到达网络接口,通过 ARP 协议获得下一跳的 MAC 地址,然后对 sk_buff 填充帧头和帧尾,接着将 sk_buff 放到网卡的发送队列中。
当网络包到达网卡后,会触发「软中断」告诉网卡驱动程序,新的网络包需要发送,驱动程序会从发送队列中读取 sk_buff,将这个 sk_buff 挂到 RingBuffer 中,接着将 sk_buff 数据映射到网卡可访问的内存 DMA 区域,最后触发真实的发送。
当发送完数据后,网卡设备会触发一个硬中断来释放内存,主要是释放 sk_buff 内存和清理 RingBuffer 内存。最后,当收到这个 TCP 报文的 ACK 应答时,传输层就会释放原始的 sk_buff 。
4.1 发送网络数据时,涉及几次内存拷贝
-
-
使用TCP传输协议情况下,从传输层进入网络层的时候,每一个sk_buff都会被克隆一个新的副本。副本sk_buff会被发送到网络层,等她发送完时会被释放掉,然后原始的sk_buff还保留在传输层,目的是实现TCP可靠传输,等收到这个数据包的ACK时,才会释放原始的sk_buff(TCP层)。
-
5.参考博客
本人博客内容是基于小林coding的计算机网络写的,中间省略了部分内容,大家可以去小林coding博客看更详细的图解网络,链接为:小林coding (xiaolincoding.com)。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
2021-06-28 python数据类型、变量以及编码和字符串、格式化