目录
本文主要讲解跨主机的网络包发送过程,以及本机网络包发送过程
前置知识(也可以先看宏观过程再看这里)
系统调用(自行了解)
skb(sk_buff):
即Socket Buffer,是Linux内核网络子系统中的一个重要数据结构,用于在网络栈中管理和传输数据包,大概是以下这样
当用户态通过系统调用发送数据时,我们申请skb指向的数据区的大小是要大于用户的数据+传输层的头+网络层的头+数据链路层的头和尾的大小的。这样做的好处是,避免后续增加头部时空间不够需要重新申请空间和拷贝数据到新空间的消耗
DMA:
即直接内存访问(Direct Memory Access),是一种允许外部设备直接与计算机内存进行数据传输而不需要通过中央处理器(CPU)的技术。DMA控制器负责管理这种数据传输,使得外设可以与内存之间进行数据交换,而CPU在这一过程中只需要初始设置和最终的状态检查,大大提高了数据传输效率和系统性能。
RingBuffer:
网卡一般都是支持多队列的。每一个队列上都是由一个 RingBuffer 表示的,开启了多队列以后的的网卡就会对应有多个 RingBuffer。实际上一个 RingBuffer 的内部不仅仅是一个环形队列数组,而是有两个。
1)igb_tx_buffer 数组:这个数组是内核使用的,通过 vzalloc 申请的。
2)e1000_adv_tx_desc 数组:这个数组是网卡硬件使用的,硬件是可以通过 DMA 直接访问这块内存,通过 dma_alloc_coherent 分配。
这个时候它们之间还没有啥联系。将来在发送的时候,这两个环形数组中相同位置的指针将都将指向同一个 skb。这样,内核和硬件就能共同访问同样的数据了,内核往 skb 里写数据,网卡硬件负责发送。
跨主机发送的宏观过程
- 系统调用: 应用程序调用Socket API来发送数据。这会触发从用户态到内核态的切换。
- 内存分配: 内核为要发送的数据分配sk_buff结构体。用户空间的数据被复制到这个新分配的sk_buff中。
- 发送缓冲区: sk_buff被加入到Socket关联的发送缓冲区中。
- TCP协议处理: 如果使用TCP,因为 sk_buff 后续在调用网络层,最后到达网卡发送完成的时候,这个 sk_buff会被释放掉。而 TCP 协议是支持丢失重传的,在收到对方的 ACK 之前,这个 sk_buff 不能被删除,sk_buff的副本会在传输层(TCP、UDP)被创建。如果不是tcp直接跳过这一步
- 协议封装: sk_buff随后经历协议栈各层的处理,每一层添加它自己的头信息。对于TCP,会添加TCP头;对于IP层,添加IP头;在数据链路层,会添加如以太网帧头。
- 排队:将封装好后的数据放入排队队列进行等待
- 发送:在驱动函数里, skb 会挂到 RingBuffer上,以DMA的方式映射到网卡,然后数据包将真正从网卡发送出去
一些思考
发送网络数据的时候,涉及几次内存拷贝操作
第一次,调用发送数据的系统调用的时候,内核会申请一个内核态的 sk_buff 内存,将用户待发送的数据拷贝到 sk_buff 内存,并将其加入到发送缓冲区。
第二次,在使用 TCP 传输协议的情况下,从传输层进入网络层的时候,每一个 sk_buff 都会被克隆一个新的副本出来。副本 sk_buff 会被送往网络层,等它发送完的时候就会释放掉,然后原始的 sk_buff 还保留在传输层,目的是为了实现 TCP 的可靠传输,等收到这个数据包的 ACK 时,才会释放原始的 sk_buff 。(注意这次的拷贝是浅拷贝,也就是拷贝的是sk_buff结构体里面的数据,但是指向的内存数据区都是同一块)
第三次,当 IP 层发现 sk_buff 大于 MTU 时才需要进行。会再申请额外的 sk_buff,并将原来的 sk_buff 拷贝为多个小的 sk_buff。
本机网络发送是怎么样的?
大致过程如下图所示
和跨主机发送的主要区别是
网络的分片:
跨主机的MTU是1500字节,而本机发送的MTU是65535字节,所以本机的传输能力更强
跨主机需要进入排队队列等待,而本机发送不需要
跨主机发送需要经过网卡速度会比较慢,而本机发送不需要经过网卡,纯内存操作更快
跨主机发送需要网卡,而本机发送不需要网络也可以进行
推荐
25 张图,一万字,拆解 Linux 网络包发送过程-腾讯云开发者社区-腾讯云 (tencent.com)