一、传统linux网络协议栈流程和性能分析
Linux网络协议栈是处理网络数据包的典型系统,它包含了从物理层直到应用层的全过程。
- 数据包到达网卡设备。
- 网卡设备依据配置进行DMA操作。(第1次拷贝:网卡寄存器->内核为网卡分配的缓冲区ring buffer)
- 网卡发送中断,唤醒处理器。
- 驱动软件从ring buffer中读取,填充内核skbuff结构(第2次拷贝:内核网卡缓冲区ring buffer->内核专用数据结构skbuff)
- 数据报文达到内核协议栈,进行高层处理。
- socket系统调用将数据从内核搬移到用户态。(第3次拷贝:内核空间->用户空间)
研究者们发现,Linux内核协议栈在数据包的收发过程中,内存拷贝操作的时间开销占了整个处理过程时间开销的65%,此外层间传递的系统调用时间也占据了8%~10%。
协议栈的主要问题:
1. 针对单个数据包级别的资源分配和释放
每当一个数据包到达网卡,系统就会分配一个分组描述符用于存储数据包的信息和头部,直到分组传送到用户态空间,其描述符才被释放。此外,sk_buff庞大的数据结构中的大部分信息对于大多数网络任务而言都是无用的。
2.流量的串行访问
现代网卡包括多个硬件的接收端扩展(receiver-side scaling, RSS)队列可以将分组按照五元组散列函数分配到不同的接收队列。使用这种技术,分组的捕获过程可以被并行化,因为每个RSS队列可以映射到一个特定的CPU核,并且可以对应相应的NAPI线程。这样整个捕获过程就可以做到并行化。
但是问题出现在之上的层次,Linux中的协议栈在网络层和传输层需要分析合并的所有数据包
- 所有流量在一个单一模块中被处理,产生性能瓶颈;
- 用户进程不能够从一个单一的RSS队列接收消息.
这就造成了上层应用无法利用现代硬件的并行化处理能力,这种在用户态分配流量先后序列的过程降低了系统的性能,丢失了驱动层面所获得的加速.
此外,从不同队列合并的流量可能会产生额外的乱序分组
3.从驱动到用户态的数据拷贝
从网卡收到数据包到应用取走数据的过程中,存在至少2次数据包的复制
4.内核到用户空间的上下文切换
从应用程序的视角来看,它需要执行系统调用来接收每个分组.每个系统调用包含一次从用户态到内核态的上下文切换,随之而来的是大量的CPU时间消耗.在每个数据包上执行系统调用时产生的上下文切换可能消耗近1 000个CPU周期.
5.跨内存访问
例如,当接收一个64 B分组时,cache未命中造成了额外13.8%的CPU周期的消耗.另外,在一个基于NUMA的系统中,内存访问的时间取决于访问的存储节点.因此,cache未命中在跨内存块访问环境下会产生更大的内存访问延迟,从而导致性能下降.
二、提高捕获效率的技术
目前高性能报文捕获引擎中常用的提高捕获效率的技术,这些技术能够克服之前架构的性能限制.
1.预分配和重用内存资源
这种技术包括:
开始分组接收之前,预先分配好将要到达的数据包所需的内存空间用来存储数据和元数据(分组描述符).尤其体现在,在加载网卡驱动程序时就分配好 N 个描述符队列(每个硬件队列和设备一个).
同样,当一个数据包被传送到用户空间,其对应的描述符也不会被释放,而是重新用于存储新到达的分组.得益于这一策略,在每个数据包分配/释放所产生的性能瓶颈得到了消除.此外,也可以通过简化sk_buff的数据结构来减少内存开销.
2.数据包采用并行直接通道传递.
为了解决序列化的访问流量,需要建立从RSS队列到应用之间的直接并行数据通道.这种技术通过特定的RSS队列、特定的CPU核和应用三者的绑定来实现性能的提升.
这种技术也存在一些缺点:
- 数据包可能会乱序地到达用户态,从而影响某些应用的性能;
- RSS使用Hash函数在每个接收队列间分配流量.当不同核的数据包间没有相互关联时,它们可以被独立地分析,但如果同一条流的往返数据包被分配到不同的CPU核上时,就会造成低效的跨核访问.
3.内存映射.
使用这种方法,应用程序的内存区域可以映射到内核态的内存区域,应用能够在没有中间副本的情况下读写这片内存区域.
用这种方式我们可以使应用直接访问网卡的DMA内存区域,这种技术被称为零拷贝.但零拷贝也存在潜在的安全问题,向应用暴露出网卡环形队列和寄存器会影响系统的安全性和稳定性 .
4.数据包的批处理.
为了避免对每个数据包的重复操作的开销,可以使用对数据包的批量处理.
这个策略将数据包划分为组,按组分配缓冲区,将它们一起复制到内核/用户内存.运用这种技术减少了系统调用以及随之而来的上下文切换的次数;同时也减少了拷贝的次数,从而减少了平摊到处理和复制每个数据包的开销.
但由于分组必须等到一个批次已满或定时器期满才会递交给上层,批处理技术的主要问题是延迟抖动以及接收报文时间戳误差的增加.
5.亲和性与预取.
由于程序运行的局部性原理,为进程分配的内存必须与正在执行它的处理器操作的内存块一致,这种技术被称为内存的亲和性.
CPU亲和性是一种技术,它允许进程或线程在指定的处理器核心上运行.
在内核与驱动层面,软件和硬件中断可以用同样的方法指定具体的CPU核或处理器来处理,称为中断亲和力.每当一个线程希望访问所接收的数据,如果先前这些数据已被分配到相同CPU核的中断处理程序接收,则它们在本地cache能够更容易被访问到.
三、libpcap
libpcap的包捕获机制是在数据链路层增加一个旁路处理,不干扰系统自身的网路协议栈的处理,对发送和接收的数据包通过Linux内核做过滤和缓冲处理,最后直接传递给上层应用程序。
-
数据包到达网卡设备。
-
网卡设备依据配置进行DMA操作。(第1次拷贝:网卡寄存器->内核为网卡分配的缓冲区ring buffer)
-
网卡发送中断,唤醒处理器。
-
驱动软件从ring buffer中读取,填充内核skbuff结构(第2次拷贝:内核网卡缓冲区ring buffer->内核专用数据结构skbuff)
-
接着调用netif_receive_skb函数:
- 如果有抓包程序,由网络分接口进入BPF过滤器,将规则匹配的报文拷贝到系统内核缓存(第3次拷贝)。BPF为每一个要求服务的抓包程序关联一个filter和两个buffer。BPF分配buffer且通常情况下它的额度是4KB;the store buffer 被使用来接收来自适配器的数据; the hold buffer被使用来拷贝包到应用程序。
- 处理数据链路层的桥接功能;
- 根据skb->protocol字段确定上层协议并提交给网络层处理,进入网络协议栈,进行高层处理。
-
libpcap绕过了Linux内核收包流程中协议栈部分的处理,使得用户空间API可以直接调用套接字PF_PACKET从链路层驱动程序中获得数据报文的拷贝,将其从内核缓冲区拷贝至用户空间缓冲区(第4次拷贝)
四、libpcap-mmap
libpcap-mmap是对旧的libpcap实现的改进,新版本的libpcap基本都采用packet_mmap机制。PACKET_MMAP通过mmap,减少一次内存拷贝(第4次拷贝没有了),减少了频繁的系统调用,大大提高了报文捕获的效率。
原文链接:https://blog.csdn.net/gengzhikui1992/article/details/103142848