DPDK-初学(12)-vmdq
在尝试完成mdns-D的时候,碰到了在配置eth_dev_config时的一个参数问题,rte_eth_conf
参数中的rx/tx配置mode,存在一个VMDQ选项,不知道这是个啥玩意。查询gpt后,给出了如下回答
VMDq 可以为每个虚拟机分配一个或多个队列,这样每个 VM 的网络流量都被独立处理,而不会相互干扰。这种方式特别适用于高密度虚拟化环境,其中大量的 VM 需要共享同一个物理 NIC。通过使用 VMDq,可以提高这些环境中的网络效率和性能。
看起来似乎是一种为虚拟机分配队列的东西?再去查了查资料,有下面段描述
试想,每个虚机如果都需要10G的交换能力,服务器要配置几十块物理网卡,且不说主板是否支持这么多的接口,光成本上就难以接受。 另外,如果给vm分配的接口都是软件交换机的虚拟接口,维护这些接口和转发本身就要消耗大量的服务器计算资源。
因此,业界推出了VMDq和SR-IOV技术来提升虚机的网络性能。在服务器的物理网卡中为每个虚机分配一个独立的队列,这样虚机出来的流量可以直接经过软件交换机发送到指定队列上,软件交换机无需进行排序和路由操作。 但是,VMM和虚拟交换机仍然需要将网络流量在VMDq和虚机之间进行复制。
对于SR-IOV来说,则更加彻底,它通过创建不同虚拟功能(VF)的方式,呈现给虚拟机的就是独立的网卡,因此,虚拟机直接跟网卡通信,不需要经过软件交换机。 VF和VM之间通过DMA进行高速数据传输。 SR-IOV的性能是最好的,但是需要一系列的支持,包括网卡、主板、VMM等。
看起来,VMDq和SR-IOV是为了应对多虚拟机对网络的需求提出的解决方法。不同的是,VMDq选择在一个物理的nic上实现多队列,每个队列为特定的VM服务。而SR-IOV则是从一个真实的PCI设备中虚拟出几个轻量PCI设备供虚拟机使用,这样每个虚拟机能看到的就是一个标准的PCI设备。
更多资料,查阅这个链接。
有了这些个基础了解,再来看DPDK中的VMDq example。
port_init
关键操作基本都在这个函数里面。遗憾的是,虚拟机这个网卡驱动,vmdq相关的参数(vmdq_queue_num、max_vmdq_pools)为0。
检查硬件支持情况,不能申请过多的pool,这个和nic硬件相关。随后开始设置port_conf。
1
2
3
retval = rte_eth_dev_info_get(port, &dev_info);
max_nb_pools = (uint32_t)dev_info.max_vmdq_pools;
get_eth_conf(&port_conf, max_nb_pools);
初始化struct rte_eth_vmdq_rx_conf
结构。
- nb_queue_pools。一个枚举类型,支持8/16/32/64 pools模式。
- nb_pool_maps。后面后多少个maps。
通过一个for循环,构造好pool_map,即每一个vlan_id对应着一个pool。 后面这两个rte_memcpy
有点没看懂,直接在外面构造不好吗,难道是为了演示这个函数? 最后,如果开启了rss,顺便设置rss的hash_function相关选项(采用什么数据来进行hash)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static inline int
get_eth_conf(struct rte_eth_conf *eth_conf, uint32_t num_pools)
{
struct rte_eth_vmdq_rx_conf conf;
unsigned i;
conf.nb_queue_pools = (enum rte_eth_nb_pools)num_pools;
conf.nb_pool_maps = num_pools;
conf.enable_default_pool = 0;
conf.default_pool = 0; /* set explicit value, even if not used */
for (i = 0; i < conf.nb_pool_maps; i++) {
conf.pool_map[i].vlan_id = vlan_tags[i];
conf.pool_map[i].pools = (1UL << (i % num_pools));
}
(void)(rte_memcpy(eth_conf, &vmdq_conf_default, sizeof(*eth_conf)));
(void)(rte_memcpy(ð_conf->rx_adv_conf.vmdq_rx_conf, &conf,
sizeof(eth_conf->rx_adv_conf.vmdq_rx_conf)));
if (rss_enable) {
eth_conf->rxmode.mq_mode = RTE_ETH_MQ_RX_VMDQ_RSS;
eth_conf->rx_adv_conf.rss_conf.rss_hf = RTE_ETH_RSS_IP |
RTE_ETH_RSS_UDP |
RTE_ETH_RSS_TCP |
RTE_ETH_RSS_SCTP;
}
return 0;
}
每一个vmdq中,一个pool代表一个要被分离开的流量入口。每一个pool下面有自己专属的queue。随后就开始设置每一个队列,这里有个注释,提到了实际上只会从每个pool的第一个queue接收数据,并且发送到第一个lcore的发送队列。这是因为vmdq队列并不总是从0开始计数,并且PMD也不支持选择性的初始化rx/tx,所以需要全部初始化。
初始化完成后,每一个lcore开始循环。
每一个lcore拥有自己应该去处理的queue,有点没太看懂这里的逻辑。上面不是说只从每个pool的第一个queue接数据吗,怎么下面就是从startQueue接到endQueue了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const uint16_t remainder = (uint16_t)(num_vmdq_queues % num_cores);
for (i = 0; i < num_cores; i++)
if (lcore_ids[i] == lcore_id) {
core_id = i;
break;
}
if (remainder != 0) {
if (core_id < remainder) {
startQueue = (uint16_t)(core_id *
(num_vmdq_queues / num_cores + 1));
endQueue = (uint16_t)(startQueue +
(num_vmdq_queues / num_cores) + 1);
} else {
startQueue = (uint16_t)(core_id *
(num_vmdq_queues / num_cores) +
remainder);
endQueue = (uint16_t)(startQueue +
(num_vmdq_queues / num_cores));
}
} else {
startQueue = (uint16_t)(core_id *
(num_vmdq_queues / num_cores));
endQueue = (uint16_t)(startQueue +
(num_vmdq_queues / num_cores));
}
...
for (q = startQueue; q < endQueue; q++) {
const uint16_t rxCount = rte_eth_rx_burst(sport,
q, buf, buf_size);
...
}