目录
问题
在开发DPDK应用的时候,我们可以通过rte_eth_stats_get函数获取网卡统计信息中的imissed计数来判断网卡是否出现丢包。

注:对于ixgbe 驱动,则DPDK中 rte_eth_stats_get 调用的是 ixgbe_dev_stats_get;
基础
imiss 的定义

Q: imiss 是 网卡的接收队列满吗?还是 ring_buffer 满?
A: 根据上面的定义,是被硬件丢弃,应该是网卡的接收队列满。不应该是 ring_buffer 满,ring_buffer 是内核使用的,应该是一个软件的概念。
Q: imiss 统计是否可以基于队列?
A: imiss统计,是全局的。
ixgbe_dev_stats_get 中的实现如下。
如上:ixgbe 驱动的网卡的 oerror 一直是0。
其他统计
rx-errors/ierrors

ierrors: 表示该数据包存在错误,被网卡丢弃。此时该包不会存在于物理网卡的RX FIFO中,更不会存在于内存中的rte_rx_queue(ring buffer)中。
比如:packets with incorrect checksum, runts, giants etc.
rx-nombuf

rx_nombuf记录在读取数据包时分配mbuf错误的次数,一般情况下不会影响网卡的丢包(imissed、ierrors)。
比如:RX packet was drop due to lack of free mbufs in the mempool.
- 解决
直接增大mempool的大小。
我的理解
Most drivers interchange their use of the counters rx_missed_errors, rx_fifo_errors, and rx_over_errors,
but they typically set one or more of these counters to the MPC (missed packet count) counter, which is incremented when a packet arrives and is lost because the card's FIFO queue is full.
FIFO指的是网卡上实现的硬件「先进先出」队列;
整个包收发流程为:[ 网卡硬件 FIFO 队列 ] <---- PCIe Bus —> [ Rx Ring Buffer: host memory ]。
对应的丢包计数有两个:
一个是 Missed Packet Count,指的是 [ 网卡硬件 FIFO 队列 ] 满了导致的丢包;
一个是 Receive No Buffer Count,指的是 [ Rx Ring Buffer ] 满了导致的丢包。
PCIe版本以及速率
为什么需要Pcie
在电脑里,不同的设备要想交互数据,就必须要经过一定的通道。
- 总线
总线就是计算机里,用于走数据的“路”。 - 内部总线
CPU核心和cache缓存交互数据的时候,使用的就是内部总线,这个总线只在CPU内交互数据。 - 外部总线
但是CPU不可能就自己在那空算数吧,他总要和其他设备交互数据,就需要用到外部总线了,CPU会通过外部总线和其他的设备比如硬盘,网卡,声卡,USB设备沟通,我们这个文章想要介绍的PCI-E就是外部总线的一种。 - pcie的作用

PCI-E既是通道,也是接口,当他以接口形式存在的时候,就是我们主板上那长长的槽。
- pcie 通道
PCIe用于系统中的不同模块之间的通信。 网络适配器(网卡)需要与CPU和内存(以及其他模块)进行通信。 这意味着为了处理网络流量,应正确配置PCIe为不同设备进行通信。 将网络适配器连接到PCIe时,它将自动协商网络适配器和CPU之间支持的最大功能。- pcie 接口
目前的声卡和网卡都是主板集成了,不需要我们额外再插,所以PCI-E接口目前最大的作用就是插显卡,除了显卡还有无线网卡,万兆有线网卡这些高带宽设备,除了这些,PCI-E接口也可以转接成很多接口,比如USB3.0,Type-c。
pcie属性之宽度
PCIe宽度确定设备可以并行使用以进行通信的PCIe通道数。 宽度标记为xA,其中A是通道数(例如8通道为x8)。
Mellanox适配器支持x8和x16配置,具体取决于它们的类型。
# lspci -s 04:00.0 -vvv | grep Width
LnkCap: Port #0, Speed 8GT/s, Width x8, ASPM not supported, Exit Latency L0s unlimited, L1 unlimited
LnkSta: Speed 8GT/s, Width x8, TrErr- Train- SlotClk+ DLActive- BWMgmt- ABWMgmt-
pcie属性之速度
速度以GT / s表示,表示“每秒十亿笔传输”。
连同PCIe宽度一起,确定最大PCIe带宽(速度*宽度)。
PCIe速度被标识为“generations”,其中2.5GT / s被称为“ gen1”,5GT / s被称为“ gen2”,而8GT / s被称为“ gen3”。
# lspci -s 04:00.0 -vvv | grep PCIeGen
[V0] Vendor specific: PCIeGen3 x8
pcie 速率表
PCIE有不同的规格,通过下图来了解下PCIE的其中2种规格:
- 查看主板上的PCI插槽’
# dmidecode | grep -i "PCI"

- 不同PCIe版本对应的传输速率

传输速率为每秒传输量GT/s,而不是每秒位数Gbps,因为传输量包括开销位; 比如 PCIe 1.x和PCIe 2.x使用8b / 10b编码方案,导致占用了20% (= 2/10)的原始信道带宽。
- GT/s —— Giga transation per second (千兆传输/秒
即每一秒内传输的次数。重点在于描述物理层通信协议的速率属性,可以不和链路宽度等关联。- Gbps —— Giga Bits Per Second (千兆位/秒)。
GT/s 与Gbps 之间不存在成比例的换算关系。- PCIe 吞吐量(可用带宽)计算方法:
吞吐量 = 传输速率 * 编码方案
例如:PCI-e2.0 协议支持 5.0 GT/s,即每一条Lane 上支持每秒钟内传输 5G次;但这并不意味着 PCIe 2.0协议的每一条Lane支持 5Gbps 的速率。
因为PCIe 2.0 的物理层协议中使用的是 8b/10b 的编码方案。 即每传输8个Bit,需要发送10个Bit;这多出的2个Bit并不是对上层有意义的信息。
那么, PCIe 2.0协议的每一条Lane支持 5 * 8 / 10 = 4 Gbitps = 500 MB/s 的速率。
以一个PCIe 2.0 x8的通道为例,x8的可用带宽为 4 * 8 = 32 Gbps = 4 GB/s。
查看 pcie 设备带宽(带宽上限和实际带宽)
lspci -vv | grep -i PCI-ID -A 40

- LnkCap: 链路能力;
LnkCap 代表的是卡片本身支持的最高速度。- LnkSta: 链路状态;
LnkSta 代表是实际跑的的速度, 如果正常应该要和 LnkCap 一样, 才能获得最大的带宽。如果发现 LnkSta 的速度比LnkCap 小, 那就要追一下是不是插槽本身的速度就有限制。
pcie 插槽插入单卡双口/单卡四口网卡
早期的网卡,一个芯片只能处理一个通道,所以,都是单口的。后来,由于总线带宽的提高, 加上芯片性能的提高,所以出现了双口以上的网卡。
其实原因很简单:
PCI槽有限。所以, 单个槽里的网口越多,机器上能接的网卡越多。
所以:Pcie 插槽的带宽 要 大于 单卡两口/多口的带宽之和。
分析

一个网络报文从网卡接收到被应用处理,中间主要需要经历两个阶段:
- 阶段一:网卡通过其DMA硬件将收到的报文写入到收包队列中(入队)
如果入队道路拥塞将会导致报文无法入队(入队)
- 阶段二:应用从收包队列中读取报文(出队)
如果出队慢将导致队列溢出(出队)
注:DMA是硬件,目的将数据包从网卡送到操作系统(ring buffer),期间不需要CPU的参与,节省了CPU。
网卡初始化与收包
整体流程
- DPDK对数据包的处理流程

物理网卡监听物理链路上的信息号,解析得到数据包,并将其存放在物理网卡上的RX FIFO中(Rx fifo : 网卡的内存);
物理网卡上的DMA将数据包写入到内存(系统内存)中的 rte_rx_queue(ring buffer);
应用程序通过PMD的形式轮询从rte_rx_queue读取数据包。

收包队列的构造
下面以ixgbe网卡在dpdk框架下工作为例,分别介绍下收包队列的构造、启动和收包三个流程。

收包队列的构造主要是通过调用网卡队列设置函数rte_eth_rx_queue_setup(dpdk rte_ethdev.h)来完成。
收包队列的结构体为ixgbe_rx_queue,该结构体里包含两个重要的环形队列rx_ring和sw_ring,rx_ring和sw_ring的关系可以简单如下:
rx_ring
主要存储报文数据的物理地址,物理地址供网卡DMA使用,也称为DMA地址(硬件使用物理地址,将报文copy到报文物理位置上)。
sw_ring
sw_ring主要存储报文数据的虚拟地址,虚拟地址供应用使用(软件使用虚拟地址,读取报文)。
其中,报文数据的物理地址可以由报文数据的虚拟地址转化得到。
rx_ring

rx_ring是由一个动态申请的数组构建的环形队列,队列的元素是ixgbe_adv_rx_desc类型,队列的长度为(4096+4-1)。
pkt_addr:报文数据的物理地址,网卡DMA将报文数据通过该物理地址写入对应的内存空间。
hdr_addr:报文的头信息。
注:status_error的最后一个bit也对应DD位。
DD位(Descriptor Done Status)用于标志标识一个描述符buf是否可用。
- 放入队列
网卡每次来了新的数据包,就检查rx_ring当前这个buf的DD位是否为0,如果为0那么表示当前buf可以使用,就让DMA将数据包copy到这个buf中,然后设置DD为1。如果为1,即最上面的buf都不可用,那么网卡就认为rx_ring队列满了,直接会将这个包给丢弃掉,记录一次imiss。(0->1)- 从队列中取
对于应用而言,DD位使用恰恰相反,在读取数据包时,先检查DD位是否为1,如果为1,表示网卡已经把数据包放到了内存中,可以读取,读取完后,再放入一个新的buf并把对应DD位设置为0。如果为0,就表示没有数据包可读。(1->0)
sw_ring

sw_ring是由一个动态申请的数组构建的环形队列,队列的元素是ixgbe_rx_entry类型,队列的大小可配,一般最大可配4096。
mbuf:报文mbuf结构指针,mbuf用于管理一个报文,主要包含报文相关信息和报文数据。
网卡启动
收包队列的启动主要是通过调用 rte_eth_dev_start(dpdk rte_ethdev.h)函数完成,收包队列初始化的核心流程如下。
循环从mbuf pool中申请mbuf,从mbuf中得到报文数据对应的物理地址,物理地址存入rx_ring中,mbuf指针存入sw_ring中。其中通过rxd->read.hdr_addr = 0,完成了DD位设置为0。
收包流程
收包由网卡入队和应用出队两个操作完成。
1、网卡使用DMA写Rx FIFO中的Frame到Rx Ring Buffer中的mbuf,设置desc的DD为1;
2、网卡驱动取走mbuf后,设置desc的DD为0;
入队

入队的操作是由网卡DMA来完成的。
DMA(Direct Memory Access,直接存储器访问)是系统和网卡(外设)打交道的一种方式,该种方式允许在网卡(外部设备)和系统内存之间通过PCIe总线直接读写数据,这样能有效减轻CPU的工作。

网卡收到报文后,先存于网卡本地的buffer-Rx(Rx FIFO)中,然后由DMA通过PCI总线将报文数据写入操作系统的内存中,即数据报文完成入队操作。(PS:PCIe总线可能成为网卡带宽的瓶颈)
出队
应用调用rte_eth_rx_burst(dpdk rte_ethdev.h)函数开始批量收包,最大收包数量由参数nb_pkts决定(比如设置为64)。其核心流程由ixgbe_recv_pkts(dpdk ixgbe_rxtx.c)实现。
从接口的收包队列rxq->rx_ring 的 rx_tail位置开始收,循环读取一个报文、填空一个报文(空报文数据),读取64个后,重新标记rx_tail的位置,完成出队操作,将收取的报文作返回供应用处理。
入队丢包可能性
入队问题主要集中在PCIe异常“降速”方面。因为报文从网卡到系统是通过DMA经过PCIe总线来传输的,PCIe总线的吞吐将直接影响入队的速率。
pcie 实际带宽和理论带宽不符合
若网口出现imissed,可通过lspci -vv命令查看PCIe理论能力与实际使用是否一致。
情况1:
网口能力是传输速率5GT/s,总线宽带x8(LnkCap),实际使用的是传输速率5GT/s,总线宽带x4(LnkSta)。吞吐能力从4GB/s下降到2GB/s。
情况2:
网口能力是传输速率8GT/s,总线宽带x8(LnkCap),实际使用的是传输速率5GT/s,总线宽带x8(LnkSta)。吞吐能力从7.877GB/s下降到4GB/s。
解决
一般是服务器PCIe插槽与网卡兼容性问题,可以更换网卡或者更换服务器PCIe插槽。如果有条件,可以找服务器厂商从bios等方面进行详细定位解决兼容性问题。
出队丢包可能性
出队问题主要集中在应用程序性能不高、程序设计不优和CPU错误降频等方面。
DPDK程序性能不高
DPDK程序的实际CPU使用率达到100%,不能及时处理队列报文,导致队列报文溢出,持续imissed++,此时出队平均速率是小于入队平均速率。
解决
inux系统下可以使用perf性能分析工具,做热点函数分析,perf安装命令yum install perf。perf常用的热点函数定位命令如下:
- 进程级:perf top -p
- 线程级:perf top -t
线程tid可以通过pidstat -t -p 或者 top -p PID -H 获取。
DPDK程序设计不优
DPDK程序的实际CPU使用率没有达到100%,但依然偶尔会丢包,断续imissed++。一般这是因为出队速率抖动引发溢出,即出队最低速率小于入队平均速率,而收包队列的规格(几K)不足以缓存拥塞的报文。
解决
偶尔丢包有时不容易从热点函数中发现,需要分析代码流程进行调优。
比如:某个报文会触发密集cpu计算,需要更长处理时间,如收到fin报文流结束,对流内容进行复杂处理。针对这种情况,我们可以从以下两个方面进行调优:
- 异步处理
将复杂处理由同步处理(Run-to-completion)改为异步处理(Pipeline),降低抖动。- 加软队列
增加一级容量更大的软队列,缓存抖动(一般业务有按规则分发的需要也需要构建一级软队列,软队列可能会或多或少增加报文处理的延时)。
CPU错误降频
如果CPU被降频,将直接影响出队性能。
现象
查看系统日志,出现CPU被错误降频。
分析

解决

查看当前的CPU频率 与运行模式


查看cpu的每个core的频率:
如果当前运行在powersave模式下,可以将其修改为performance,提升CPU频率。
cpupower frequency-set -g performance。

总结
出现 imiss,主要是由于 数据包被硬件丢弃。可能性分析如下:
- 入队慢:
pci-e 实际带宽慢。- 出队慢:
1》程序性能不行;
2》程序设计缺陷;
3》cpu降频
参考
https://mp.weixin.qq.com/s/jM7R-LXsLWbGqqXLlowxSw






