DPDK-流分类与多队列

1.前言

多队列与流分类技术基本被应用到所有DPDK网关类项目中,比如开源的DPVS,美团的四层网关等等,利用多队列及分流技术可以使得网卡更好地与多核处理器,多任务系统配合,从而达到更高效IO处理的目的。

这章节以英特尔的网卡为例,介绍多队列和流分类是如何合工作的 ,各种分类方式适合哪些场景,DPDK怎么利用这些网卡特性。

2.多队列

2.1 网卡多队列的由来

谈到这个网卡多队列,显而易见说的就是传统网卡的DMA队列有多个,网卡有基于多个DMA队列的分配机制。图中是Intel 82580网卡示意图,我们能看到多队列分配在网卡中的位置。

图2-1 82580网卡简易框图

网卡多队列技术与多核处理器技术密不可分,早期的多数计算机,处理器只有一个核,从网卡上收到的以太网报文都需要这个处理核处理。随着半导体技术发展,处理器上核的数量不断在增加,与此同时就带来一个问题:网卡上收到的报文由哪个核来处理?如何分配不同的核上去处理呢?随着网络带宽的不断提升,当端口速率从10Mbit/s100Mbit/s进入1Gbit/s10Gbit/s,单个处理器核肯定是不够满足高速网卡快速处理的需求的,从2007年开始Intel的82575和82598网卡上就引入了多队列技术,可以将各个队列通过绑定到不同的核上来满足高速流量的需求。现在100G、400G的网卡更是支持越来越多的队列数目,除此之外,多队列技术也可以进行流分类处理,以及解决网络IO服务质量QOS,针对不同的优先级进行限速和调度。

强调一下网卡多队列技术是一个硬件手段,需要软件结合将它利用起来才能达到设计目的,分而治之,根据多队列进行流分类,这样应用就可以根据自己的需求将数据包进行控制,可以想象一下哪些场景我们需要这种技术去解决哪些问题,从数据包的响应速度,数据包分类,队列优先级,等等方向来考虑,我相信大家一定能在自己的工作场景使用此技术解决这类问题。

2.2 DPDK与多队列

我们从DPDK提供的一些列以太网设备API入手,可以发现Packet I/O机制与生俱来的支持多队列功能,可以根据不同平台或者需求,选择需要支持的队列数目,可以很方便地使用队列,指定队列发送或者接收,由于这样的特性,可以很容易实现CPU核、缓存与网卡多队列之间的亲和性从而到达很好的性能。

从例子里的l3fwd里也能看出,单个核上运行的程序从指定队列上接受,往指定队列上发送,可以达到很高的cache命中率,效率也就会高。

从RTC模型为例来看,核、内存、网卡队列之间的关系理解DPDK是如何利用网卡多队列技术带来的性能提升。

  • 将网卡的某个队列分配給某个核,从该队列中收到的报文都应当在该指定的核上处理结束。
  • 从核对应的本地存储分配内存池,接收报文和对应的报文描述符都位于该内存池。
  • 为每个核分配一个单独的发送队列,发送报文和对应的报文描述符都位于该核和发送队列对应的本地内存池中。

所以通常情况我们绑定一个核使用一个单独的发送队列来避免多个核访问同一个队列带来的锁开销。当然也可以绑定多个队列到一个核,总之在设计时尽量避免引入保护队列的机制。

对应的可以看源码中l3fwd的例子,其中每个核对应结构中记录了操作的队列是哪个队列。

除了队列和核之间的亲和性这个主要目的之外,网卡多队列机制还可以用与Qos调度、虚拟化等。

2.3 多队列分配

看到这里读者一定会疑问我们可以设定不同的队列包收到不同的核去处理,那么网卡又是怎么把网络报文分发到不同的队列的呢?还是以Intel网卡为例,大致的过程可以分为一下几个步骤:

1.监听到线上的报文

2.按照地址过滤报文

3.DMA队列分配

4.将报文暂存在接收数据的先进先出缓存中(FIFO)。

5.将报文转移到主存中的指定队列中。

6.更新接收描述符的状态。

多队列选择顺序

从上面的过程不难看出DMA的分配在网卡收包过程中是关键的一步,那么如何进行队列分配呢?根据哪些关键信息?Poll select是与虚拟化策略相关的;从Queue Select来看,在接收方向常用的有微软提出的RSS与英特尔提出的Flow Director技术,前者是根据哈希值均匀的将包分配到多个队列中,后者是基于查找的精确匹配,将包送到指定队列中,此外,网卡还可以根据队列优先级提供对Qos的支持。不管哪一种,网卡都需要对包头解析。

3.流分类技术

这里介绍的流分类是指网卡依据数据包的特性将其分类的技术。分类的信息可以以不同的方式呈现給数据包的处理者,比如将分类信息记录于描述符中,将数据包丢弃或者将流导入某个或者某个队列中。

高级网卡都可以分析出包的类型,包的类型会携带在接收描述符中,应用程序可以以不同的方式呈现給数据包的处理者,网卡设备同时也可以根据包的类型确定它的关键字,从而根据关键字匹配确定它的接收队列。我下面提到的RSS和Flow Director技术都是根据包的类型匹配相应的关键字,从而决定其DMA的接收队列。

注意啊,不是所有网卡都支持这个功能,支持复杂度有的业有限,需要具体去看网卡参数和支持程度。

3.1 RSS(Receive-Side Scaling)

这必然是和和硬件相关的,必需要有网卡的硬件支持,RSS把数据包分配到不同的队列中,其中哈希值的计算在硬件中完成的,也可以定制修改,根据哈希值来决定队列,关键字又是怎么确定的呢?

网卡会根据不同的数据包类型确定不同的关键字,比如IPv4的UDP报文就是源IP 目的IP 源端口 目的端口,这部分比较灵活,使用者可以根据修改包类型确定对应的关键字满足不同的需求。

从整个过程不难看出,RSS是否能将数据包均匀地散列在多个队列中,取决于真实环境中的数据包构成和哈希函数的选取,哈希函数一般是选取微软托普利兹算法(Microsoft Toepliitz Based Hash)。也可以用对称哈希(Symmertic Hash),该算法可以保证Hash(srcc,dst)= Hash(dst,src)。也有之前做NAT时涉及的专利技术,能够保证NAT转换后的结果达到对称哈希的效果。在某些网络中这种对称哈希是能够提升性能,比较典型的就是网关,防火墙,服务质量保证等应用。能够保证同一条流在一个处理器上处理,减少不同核之间的数据同步。

网卡也能支持不同的哈希函数,具体看不同规格的网卡的具体参数,看是否能够定制修改。

3.2 Flow Director

Flow Director技术是Intel公司提出的根据包的字段精确匹配,将其分配到某个特定队列的技术。

从图中我们可以了解到Flow Director是如何中做的,网卡上存了一张Flow Director的表,受硬件资源限制大小,记录了需要匹配的关键字以及匹配后的动作,驱动负责操作这张表,初始化,增加,删除等操作;网卡从线上收到数据包后根据关键字查Flow Director这张表,匹配后按照动作处理,可以是分配队列,丢弃等。这部分和网卡支持的Flow Director包类型也是相关的,依赖硬件支持程度,下面以700系列为例说明FD的工作原理:

图中描绘了英特尔以太网控制器 700 系列灵活管道的简化表示,显示了主要的数据包处理块。
哈希和流导向器过滤器位于管道的末端,靠近主机接口,并为数据包定义目标队列。
在数据包进入哈希/流量导向器过滤器之前会发生很多预处理:
解析器块逐个解析数据包的所有标头,将数据包的不同字段提取到其“字段向量”,检测数据包的类型(PTYPE)。然后数据包类型将出现在数据包的 RX 描述符的相应部分。
Field Vector 作为元数据缓冲区附加到数据包上,并在从一个功能块到另一个功能块的过程中跟踪数据包。
开关块使用字段向量的某些字段,例如 MAC DA 或 VLAN 或 VXLAN VNI 或 MPLS 标签,来决定此数据包必须传送到哪个物理 (PF) 或虚拟 (VF) 功能。如果需要镜像,可以复制数据包,然后将其传送到多个目的地。
最后,数据包到达 Filters 块,首先到达 Flow Director,然后到达 Hash/RSS 过滤器,以在 PF/VF 中设置目标队列,该数据包已在上一步中由 Switch 块分配到。过滤器将使用字段向量中的字段来执行它们通常的魔法来选择数据包的目标队列。如果两个过滤器都被禁用,则数据包将进入队列 0。

所以相对于RSS的负载分担功能,它更关注强调特定性。

实际应用场景,比如交换机与防火墙的互联IP,实际上这种报文不需要转发面处理,我们就可以将它引流到固定队列去交给Linux kernel或者其他应用去处理相关的BGP三层报文,或者LLDP等这类的二层报文。所以根据具体IP的处理流程不同就可以使用该技术引流到特定队列处理。

3.3 服务质量(QoS)

我们经常在网卡的发送侧将多队列应用于QoS,不同的发送队列分配给不同的流量类别,网卡可以在发送侧做调度,如果做在收包侧,则可以基于流做限速。根据流中优先级或者业务类型字段,可以为不同的流指定调度优先级和分配相应的带宽,一般网卡依照VLAN标签的UP(User Priority)字段,将流划分到某个业务类型(TC),网卡根据(TC)对业务作相应的处理,比如确定相对应的队列,根据优先级调度。

以Intel 82599为例

1.收包方向

没有使能QoS功能的情况下,对描述符而言,网卡是按照轮询的方式来调度;对数据包而言,网卡从buffer 0中获取数据包。

在使能QoS功能的情况下,现根据UP来决定属于哪个TC。TC内部的不同队列之间,网卡通过轮询的方式获取描述符。不同的TC之间则是根据加权严格优先级来调度,同时不同的TC有不同的数据包buffer。对描述符而言,网卡是根据加权严格优先级调度;对数据包而言,网卡从对应的buffer中获取数据包。加权严格优先级是常用的调度算法,其基于优先级来调度,优先级高的描述符或者数据包优先被获取,同时会考虑权重,权重与为TC分配的带宽有关。当然这部分也与网卡的有关,可参考网卡的操作手册。

2.发包方向

使能QoS的场景下,与手包方向类似,现根据UP来决定其属于哪个TC,一个TC会对应一组队列,然后再使用RSS或者其他分类规则将其分配给不同的队列。TC之间的调度同样采用加权严格优先级的调度算法。当然不同网卡可能也是有不同的调度方法。

3.4 流过滤

流的合法性验证的主要任务决定哪些数据包是合法的、可被接收的。

主要过滤集中在以太网二层功能,包括对VLAN和MAC过滤,从图中可以看出主要分为以下几步:

1.MAC 地址过滤。

2.VLAN标签的过滤

3.管理数据包过滤

不同的网卡可能有所差异,不过总的来说就是决定数据包进入主存、管理控制器还是丢弃的过程。

4 流分类技术的使用

一个设备一定需要转发功能来处理数据平面的报文,同时需要处理一定量的控制报文。对于转发功能而言,需要多个core来处理,因为要求较高的性能,对于控制报文需要保证其可靠性,使用量不一定很大,处理逻辑也不同于转发逻辑。就可以使用Flow Director技术把控制报文分到指定的队列,用RSS分发数据报文。实际应用中会有更多的解决方案和处理方案。

4.1 DPDK配置使用RSS

	static const struct rte_eth_conf port_conf_default = {
		.rxmode = {
			.mq_mode = RTE_ETH_MQ_RX_RSS,
		},
		.rx_adv_conf = {
			.rss_conf = {
				.rss_hf = RTE_ETH_RSS_IP |
					  RTE_ETH_RSS_TCP |
					  RTE_ETH_RSS_UDP,
			}
		}
	};

4.2 DPDK使用Flow

21.11.1版本的DPDK已经删掉了Flow Director的接口,统一使用Flow API来调用网卡的Flow Director相关的功能,

Flow API 提供了一种通用方法来配置硬件以匹配特定流量、根据任意数量的用户定义规则更改其命运和查询相关计数器。它以用于所有符号的前缀命名为 rte_flow,并在 rte_flow.h 中定义。
可以对数据包数据(协议标头、有效负载)和属性(例如关联的物理端口、虚拟设备功能 ID)执行匹配。
可能的操作包括丢弃流量、将其转移到特定队列、虚拟/物理设备功能或端口、执行隧道卸载、添加标记等。

具体的使用方法可以参考DPDK的使用手册,其实看个例子就知道怎么用了,DPDK的例子

struct rte_flow *
generate_ipv4_flow(uint16_t port_id, uint16_t rx_q,
		uint32_t src_ip, uint32_t src_mask,
		uint32_t dest_ip, uint32_t dest_mask,
		struct rte_flow_error *error)
{
	/* Declaring structs being used. 8< */
	struct rte_flow_attr attr;
	struct rte_flow_item pattern[MAX_PATTERN_NUM];
	struct rte_flow_action action[MAX_ACTION_NUM];
	struct rte_flow *flow = NULL;
	struct rte_flow_action_queue queue = { .index = rx_q };
	struct rte_flow_item_ipv4 ip_spec;
	struct rte_flow_item_ipv4 ip_mask;
	/* >8 End of declaring structs being used. */
	int res;

	memset(pattern, 0, sizeof(pattern));
	memset(action, 0, sizeof(action));

	/* Set the rule attribute, only ingress packets will be checked. 8< */
	memset(&attr, 0, sizeof(struct rte_flow_attr));
	attr.ingress = 1;
	/* >8 End of setting the rule attribute. */

	/*
	 * create the action sequence.
	 * one action only,  move packet to queue
	 */
	action[0].type = RTE_FLOW_ACTION_TYPE_QUEUE;
	action[0].conf = &queue;
	action[1].type = RTE_FLOW_ACTION_TYPE_END;

	/*
	 * set the first level of the pattern (ETH).
	 * since in this example we just want to get the
	 * ipv4 we set this level to allow all.
	 */

	/* Set this level to allow all. 8< */
	pattern[0].type = RTE_FLOW_ITEM_TYPE_ETH;
	/* >8 End of setting the first level of the pattern. */

	/*
	 * setting the second level of the pattern (IP).
	 * in this example this is the level we care about
	 * so we set it according to the parameters.
	 */

	/* Setting the second level of the pattern. 8< */
	memset(&ip_spec, 0, sizeof(struct rte_flow_item_ipv4));
	memset(&ip_mask, 0, sizeof(struct rte_flow_item_ipv4));
	ip_spec.hdr.dst_addr = htonl(dest_ip);
	ip_mask.hdr.dst_addr = dest_mask;
	ip_spec.hdr.src_addr = htonl(src_ip);
	ip_mask.hdr.src_addr = src_mask;
	pattern[1].type = RTE_FLOW_ITEM_TYPE_IPV4;
	pattern[1].spec = &ip_spec;
	pattern[1].mask = &ip_mask;
	/* >8 End of setting the second level of the pattern. */

	/* The final level must be always type end. 8< */
	pattern[2].type = RTE_FLOW_ITEM_TYPE_END;
	/* >8 End of final level must be always type end. */

	/* Validate the rule and create it. 8< */
	res = rte_flow_validate(port_id, &attr, pattern, action, error);
	if (!res)
		flow = rte_flow_create(port_id, &attr, pattern, action, error);
	/* >8 End of validation the rule and create it. */

	return flow;
}

转自;https://zhuanlan.zhihu.com/p/611784822

猜你喜欢

转载自blog.csdn.net/lingshengxiyou/article/details/130176134
今日推荐