本机环境为 Ubuntu20.04 ,dpdk-stable-20.11.10
完整代码见:git
流规则配置
在 DPDK 中,流规则配置(Flow Rules Configuration)是通过 rte_flow API 实现的,用于在网卡层配置数据包的匹配和处理规则。这些流规则可以根据不同的字段(如源或目的 MAC 地址、IP 地址、协议类型等)对数据包进行过滤和分类。配置流规则可以将数据包分配到不同的接收队列、指定特定的操作(如转发或丢弃),实现负载均衡和复杂的流处理逻辑。主要包括以下几个核心概念:
- 流属性(Flow Attributes):用于指定规则的属性,包括规则的组、优先级、入口或出口端口等。
- 匹配项(Flow Items):定义了规则的匹配条件,比如 MAC 地址、IP 地址、TCP/UDP 端口等。
- 动作(Flow Actions):定义了数据包匹配规则后需要执行的操作,比如转发到特定队列、丢弃、计数等。
示例
#include <rte_flow.h>
#include <rte_ether.h>
// 定义源 MAC 和目的 MAC 流规则的优先级
#define VNIC_SRC_MAC_PRIORITY 1 // 较高优先级
#define VNIC_DST_MAC_PRIORITY 65536 // 较低优先级
/**
* 创建基于源 MAC 地址的流规则。
* 该流规则匹配指定的源 MAC 地址的数据包,并按照传入的 `actions` 执行动作。
*
* @param port 端口 ID,用于在指定的端口上创建流规则。
* @param id 流规则组 ID,不同组的流规则在硬件上可以独立存在。
* @param mac 指向要匹配的源 MAC 地址的结构体。
* @param actions 指定匹配成功后对数据包执行的动作(如转发到特定队列)。
* @param err 用于输出错误信息的结构体,包含创建流规则时的错误码和错误信息。
* @return 成功返回指向新创建的流规则的指针,失败返回 NULL,并在 `err` 中填充错误信息。
*/
static struct rte_flow *
flow_src_mac(uint16_t port, uint32_t id,
const struct rte_ether_addr *mac,
const struct rte_flow_action actions[],
struct rte_flow_error *err)
{
// 定义流规则的属性
struct rte_flow_attr attr = {
.group = id + 1, // 设置流规则的组 ID
.priority = VNIC_SRC_MAC_PRIORITY, // 设置流规则的优先级
.ingress = 1, // 表示该规则适用于接收(Ingress)方向的数据包
};
// 定义源 MAC 地址的掩码,用于指定匹配字段
static const struct rte_flow_item_eth eth_src_mask = {
.src.addr_bytes = "\xff\xff\xff\xff\xff\xff", // 全 1 表示精确匹配源 MAC 地址的每一位
};
// 设置要匹配的源 MAC 地址
struct rte_flow_item_eth vnic_eth_src = {
.src = *mac, // 将传入的 MAC 地址设置为匹配条件
};
// 匹配项模式数组,用于定义匹配模式
struct rte_flow_item patterns[2]; // 包含两个元素,第一个为匹配项,第二个为结束标志
struct rte_flow_item *pat = patterns; // 定义一个指针指向 patterns,便于逐项设置匹配模式
// 设置第一个匹配项:以太网(Ethernet)头的源 MAC 地址
*pat++ = (struct rte_flow_item) {
.type = RTE_FLOW_ITEM_TYPE_ETH, // 匹配类型为以太网头
.spec = &vnic_eth_src, // 指定要匹配的源 MAC 地址
.mask = ð_src_mask, // 掩码定义了要匹配的字段,这里是源 MAC 地址的每一位
};
// 设置匹配项数组的结束符,用于指示匹配条件的结束
*pat = (struct rte_flow_item){ .type = RTE_FLOW_ITEM_TYPE_END };
// 创建流规则,匹配源 MAC 地址并执行指定动作
return rte_flow_create(port, &attr, patterns, actions, err);
}
部分代码实现
本文实现了一个包含虚拟网卡和流规则配置的demo:
**初始化网络端口和接收/发送队列:**应用程序初始化了一个物理网络端口,配置了接收(RX)和发送(TX)队列。根据输入参数,它可以选择是否启用中断(IRQ)模式和接收端硬件分散(RSS)模式。启用 RSS 时,数据包会均匀分布到多个接收队列,实现负载均衡。
创建多个虚拟网卡(VNIC): 在物理网卡上虚拟出多个网络接口,每个虚拟网卡具有独立的 MAC 地址和接收队列。该应用程序根据输入的 MAC 地址列表为每个虚拟网卡分配唯一的 MAC 地址,并为每个虚拟网卡设置单独的接收队列(或多个接收队列,在启用 RSS 时)。这种机制允许在一个物理端口上创建多个虚拟接口,为不同流量定义不同的处理逻辑。
配置流规则,基于 MAC 地址分发数据包: 应用程序通过 rte_flow API 为每个虚拟网卡配置流规则。这些规则可以基于源和/或目的 MAC 地址匹配数据包,将符合规则的数据包分发到相应的接收队列。应用程序可以针对每个虚拟网卡的 MAC 地址进行过滤,使得数据包到达指定的接收队列,实现流量的隔离和管理。
数据包接收和处理: 应用程序在接收数据包时可以选择轮询模式或中断模式。在轮询模式下,应用程序持续轮询接收队列;在中断模式下,应用程序在接收队列空闲时进入休眠状态,等待硬件中断来唤醒程序,从而降低 CPU 消耗。在每次数据包接收后,应用程序会根据 details 参数决定是否输出数据包的详细信息。
实时统计和监控: 应用程序包含了定时器来定时统计接收的数据包数量和字节数。在启用详细信息模式下,它会输出每个数据包的端口、队列 ID、时间戳等详细信息,便于实时监控和调试。
DPDK 初始化
int main(int argc, char **argv)
{
unsigned int num_mbufs, obj_size;
int r;
// 1. 初始化 DPDK 环境
r = rte_eal_init(argc, argv);
if (r < 0)
rte_exit(EXIT_FAILURE, "Invalid EAL arguments\n");
// 2. 检查可用端口数量
unsigned int n = rte_eth_dev_count_avail();
if (n != 1)
rte_exit(EXIT_FAILURE, "Expect one external port (got %u)\n", n);
// 3. 创建 mbuf 内存池
num_mbufs = rte_align32pow2(num_queue * RX_DESC_DEFAULT * 3) +
rte_lcore_count() * (MEMPOOL_CACHE + MAX_PKT_BURST) +
PKTMBUF_POOL_RESERVED;
num_mbufs = rte_align32pow2(num_mbufs + 1) - 1;
mb_pool = rte_pktmbuf_pool_create("mb_pool", num_mbufs,
MEMPOOL_CACHE, 0,
RTE_MBUF_DEFAULT_BUF_SIZE, 0);
if (!mb_pool)
rte_exit(EXIT_FAILURE, "Cannot init mbuf pool\n");
obj_size = rte_mempool_calc_obj_size(RTE_MBUF_DEFAULT_BUF_SIZE, 0, NULL);
printf("mbuf pool %u of %u bytes = %uMb\n",
num_mbufs, obj_size, (num_mbufs * obj_size) / (1024 * 1024));
// 4. 初始化定时器
rte_timer_subsystem_init();
// 剩下的代码处理端口配置和流规则
}
命令行参数解析
static void parse_args(int argc, char **argv)
{
int opt;
while ((opt = getopt_long(argc, argv, "avidsfpq:rm", longopts, NULL)) != EOF) {
switch (opt) {
case 'a':
any_mode = true;
break;
case 'i':
irq_mode = true;
break;
case 's':
flow_mode = FLOW_SRC_MODE;
break;
case 'd':
flow_mode = FLOW_DST_MODE;
break;
case 'q':
num_queue = atoi(optarg);
break;
case 'f':
flow_dump = true;
break;
case 'p':
promisc = false;
break;
case 'm':
random_mac = true;
break;
case 'r':
rss_enabled = true;
break;
case 'v':
++details;
break;
default:
fprintf(stderr, "Unknown option\n");
usage(argv[0]);
}
}
// Additional arguments are MAC address of VNICs
num_vnic = argc - optind;
vnic_mac = calloc(sizeof(struct rte_ether_addr), num_vnic);
for (unsigned int i = 0; i < num_vnic; i++) {
const char *asc = argv[optind + i];
if (rte_ether_unformat_addr(asc, &vnic_mac[i]) != 0)
rte_exit(EXIT_FAILURE, "Invalid mac address: %s\n", asc);
}
}
解析命令行参数:getopt_long 函数用于解析命令行选项,支持短选项和长选项。该函数会依次检查传入的参数,并根据指定的选项标志设置对应的标志位。
- -a:启用任意层数匹配,设置 any_mode 为 true。
- -i:启用中断模式,设置 irq_mode 为 true。
- -s:仅启用源 MAC 地址匹配,设置 flow_mode 为 FLOW_SRC_MODE。
- -d:仅启用目的 MAC 地址匹配,设置 flow_mode 为 FLOW_DST_MODE。
- -q:设置每个 VNIC 的队列数量,读取参数并赋值给 num_queue。
- -f:启用流规则转储功能,设置 flow_dump 为 true。
- -p:禁用混杂模式,设置 promisc 为 false。
- -m:随机生成 MAC 地址,设置 random_mac 为 true。
- -r:启用 RSS(接收端硬件分散技术),设置 rss_enabled 为 true。
- -v:增加详细信息的输出等级,每调用一次增加 details 值。
参数解析结束后,将未处理的参数视为 MAC 地址,用于配置虚拟网络接口。num_vnic 表示 VNIC 数量,为剩余参数的个数。程序分配一块内存来存储 VNIC 的 MAC 地址,并使用 rte_ether_unformat_addr 函数解析每个 MAC 地址字符串。
端口与队列配置
static void port_config(uint16_t portid, uint16_t ntxq, uint16_t nrxq)
{
struct rte_eth_dev_info dev_info;
struct rte_eth_txconf txq_conf;
struct rte_eth_rxconf rxq_conf;
uint16_t nb_rxd = RX_DESC_DEFAULT;
uint16_t nb_txd = TX_DESC_DEFAULT;
uint16_t firstq, q;
int r;
// 获取端口信息
r = rte_eth_dev_info_get(portid, &dev_info);
if (r < 0)
rte_exit(EXIT_FAILURE, "Could not get device information for port %u\n", portid);
// 检查所需的传输队列数量是否超过设备支持的最大数量
if (ntxq > dev_info.max_tx_queues)
rte_exit(EXIT_FAILURE, "Not enough transmit queues %u > %u\n", ntxq, dev_info.max_tx_queues);
// 检查所需的接收队列数量是否超过设备支持的最大数量
if (nrxq > dev_info.max_rx_queues)
rte_exit(EXIT_FAILURE, "Not enough receive queues %u > %u\n", nrxq, dev_info.max_rx_queues);
// 如果设置了随机 MAC 地址选项,随机生成一个 MAC 地址并分配给端口
if (random_mac) {
struct rte_ether_addr mac;
char ebuf[RTE_ETHER_ADDR_FMT_SIZE];
rte_eth_random_addr(mac.addr_bytes);
rte_ether_format_addr(ebuf, sizeof(ebuf), &mac);
printf("MAC: %s\n", ebuf);
r = rte_eth_dev_default_mac_addr_set(portid, &mac);
if (r < 0)
rte_exit(EXIT_FAILURE, "mac_addr_set failed: %d\n", r);
}
// 配置中断模式
if (irq_mode)
port_conf.intr_conf.rxq = 1;
// 配置 RSS 模式
if (rss_enabled)
port_conf.rxmode.mq_mode = ETH_MQ_RX_RSS;
// 配置端口,设置接收和传输队列的数量
printf("Configure %u Tx and %u Rx queues (RSS %s)\n", ntxq, nrxq,
(port_conf.rxmode.mq_mode & ETH_MQ_RX_RSS_FLAG) ? "enabled" : "disabled");
r = rte_eth_dev_configure(portid, nrxq, ntxq, &port_conf);
if (r < 0)
rte_exit(EXIT_FAILURE, "Cannot configure device: err=%d port=%u\n", r, portid);
// 根据设备的能力,调整接收和传输描述符的数量
r = rte_eth_dev_adjust_nb_rx_tx_desc(portid, &nb_rxd, &nb_txd);
if (r < 0)
rte_exit(EXIT_FAILURE, "Cannot adjust number of descriptors: err=%d, port=%d\n", r, portid);
// 获取默认的 TX 和 RX 队列配置,并应用端口配置中的 offload 设置
txq_conf = dev_info.default_txconf;
rxq_conf = dev_info.default_rxconf;
txq_conf.offloads = port_conf.txmode.offloads;
rxq_conf.offloads = port_conf.rxmode.offloads;
// 设置传输队列
for (q = 0; q < ntxq; q++) {
r = rte_eth_tx_queue_setup(portid, q, nb_txd, 0, &txq_conf);
if (r < 0)
rte_exit(EXIT_FAILURE, "rte_eth_tx_queue_setup failed %d\n", r);
}
// 设置接收队列
firstq = rss_enabled ? rte_lcore_count() : 1;
for (q = 0; q < nrxq; q++) {
if (q >= firstq)
rxq_conf.rx_deferred_start = 1;
r = rte_eth_rx_queue_setup(portid, q, nb_rxd, 0, &rxq_conf, mb_pool);
if (r < 0)
rte_exit(EXIT_FAILURE, "rte_eth_rx_queue_setup failed %d\n", r);
}
// 启动端口
r = rte_eth_dev_start(portid);
if (r < 0)
rte_exit(EXIT_FAILURE, "Start failed: err=%d\n", r);
}
函数一开始通过 rte_eth_dev_info_get 获取端口的设备信息,包括设备的能力和支持的最大队列数。该函数返回一个 rte_eth_dev_info 结构体,包含了端口的硬件支持特性,如最大 RX/TX 队列数和默认队列配置。
之后通过 dev_info.max_tx_queues 和 dev_info.max_rx_queues 检查设备支持的最大传输和接收队列数。如果请求的队列数超过设备支持的最大队列数,就会报错并退出。
如果设置了 random_mac 标志,程序会生成一个随机 MAC 地址,并将其设置为端口的默认 MAC 地址。
根据 irq_mode 和 rss_enabled 的标志,程序调整端口的配置:
- 中断模式:设置 port_conf.intr_conf.rxq = 1,启用接收队列的中断。
- RSS 模式:启用 RSS(接收端硬件分散技术)来分配数据包到多个 RX 队列。
调用 rte_eth_dev_configure 函数,根据指定的 RX 和 TX 队列数以及 port_conf 结构体中的其他配置来初始化端口。如果失败,程序会退出并报错。调用rte_eth_dev_adjust_nb_rx_tx_desc 自动调整接收和传输队列的描述符数量,以满足设备的最佳性能要求。nb_rxd 和 nb_txd 表示 RX 和 TX 描述符的数量。
传输队列:循环调用 rte_eth_tx_queue_setup 为指定数量的传输队列(ntxq)进行设置,并应用 txq_conf 中的配置。
接收队列:根据 rss_enabled 标志决定第一个接收队列的位置,循环调用 rte_eth_rx_queue_setup 配置接收队列。若 q 大于 firstq,则延迟启动接收队列。
流规则配置
流规则可以对特定的数据流进行识别和管理,例如将数据流分配到不同的队列,或是为特定的虚拟网络接口(VNIC)配置过滤规则。这部分代码通过 rte_flow API 配置流规则,以便在数据包接收时进行有选择性的处理。
流规则配置主要由以下三个函数组成:
- flow_src_mac():基于源 MAC 地址创建流规则。
- flow_dst_mac():基于目的 MAC 地址创建流规则。
- flow_configure():配置每个 VNIC 的流规则,包括源和目的 MAC 地址匹配规则,并设置数据包的分配目标(如队列或 RSS)。
/* Match any level mask */
static const struct rte_flow_item_any any_mask = {
.num = UINT32_MAX,
};
/* Match encapsulated packets */
static const struct rte_flow_item_any inner_flow = {
.num = 4,
};
static struct rte_flow *
flow_src_mac(uint16_t port, uint32_t id,
const struct rte_ether_addr *mac,
const struct rte_flow_action actions[],
struct rte_flow_error *err)
{
struct rte_flow_attr attr = {
.group = id + 1,
.priority = VNIC_SRC_MAC_PRIORITY,
.ingress = 1,
};
static const struct rte_flow_item_eth eth_src_mask = {
.src.addr_bytes = "\xff\xff\xff\xff\xff\xff",
};
struct rte_flow_item_eth vnic_eth_src = {
.src = *mac,
};
struct rte_flow_item patterns[2];
struct rte_flow_item *pat = patterns;
char ebuf[RTE_ETHER_ADDR_FMT_SIZE];
if (any_mode) {
*pat++ = (struct rte_flow_item) {
.type = RTE_FLOW_ITEM_TYPE_ANY,
.spec = &inner_flow,
.mask = &any_mask,
};
};
*pat++ = (struct rte_flow_item) {
.type = RTE_FLOW_ITEM_TYPE_ETH,
.spec = &vnic_eth_src,
.mask = ð_src_mask,
};
*pat = (struct rte_flow_item){ .type = RTE_FLOW_ITEM_TYPE_END };
rte_ether_format_addr(ebuf, sizeof(ebuf), mac);
printf("Matching on %ssrc MAC %s\n", any_mode ? "any ": "", ebuf);
if (flow_dump)
rte_flow_dump(stdout, &attr, patterns, actions);
return rte_flow_create(port, &attr, patterns, actions, err);
}
static struct rte_flow *
flow_dst_mac(uint16_t port, uint32_t id,
const struct rte_ether_addr *mac,
const struct rte_flow_action actions[],
struct rte_flow_error *err)
{
struct rte_flow_attr attr = {
.group = id + 1,
.priority = VNIC_DST_MAC_PRIORITY,
.ingress = 1,
};
struct rte_flow_item_eth vnic_eth_dst = {
.dst = *mac,
};
static const struct rte_flow_item_eth eth_dst_mask = {
.dst.addr_bytes = "\xff\xff\xff\xff\xff\xff",
};
struct rte_flow_item patterns[3];
struct rte_flow_item *pat = patterns;
char ebuf[RTE_ETHER_ADDR_FMT_SIZE];
if (any_mode) {
*pat++ = (struct rte_flow_item) {
.type = RTE_FLOW_ITEM_TYPE_ANY,
.spec = &inner_flow,
.mask = &any_mask,
};
};
*pat++ = (struct rte_flow_item) {
.type = RTE_FLOW_ITEM_TYPE_ETH,
.spec = &vnic_eth_dst,
.mask = ð_dst_mask,
};
*pat = (struct rte_flow_item){ .type = RTE_FLOW_ITEM_TYPE_END };
rte_ether_format_addr(ebuf, sizeof(ebuf), mac);
printf("Matching on %sdst MAC %s\n", any_mode ? "any ": "", ebuf);
if (flow_dump)
rte_flow_dump(stdout, &attr, patterns, actions);
return rte_flow_create(port, &attr, patterns, actions, err);
}
static void flow_configure(uint16_t portid, uint16_t id, uint16_t firstq,
const struct rte_ether_addr *mac)
{
uint16_t lastq = firstq + num_queue - 1;
uint16_t rss_queues[num_queue];
union {
struct rte_flow_action_rss rss;
struct rte_flow_action_queue queue;
} action;
struct rte_flow_action actions[] = {
{ .type = RTE_FLOW_ACTION_TYPE_VOID },
{ .type = RTE_FLOW_ACTION_TYPE_END },
};
struct rte_flow_error err;
char buf[64];
uint16_t q;
int r;
rte_ether_format_addr(buf, sizeof(buf), mac);
printf("Creating VNIC %u [%s] with queue %u..%u\n", id, buf, firstq, lastq);
if (num_queue > 1) {
uint16_t i;
for (i = 0; i < num_queue; i++)
rss_queues[i] = firstq + i;
/* Do RSS over N queues using the default RSS key */
action.rss = (struct rte_flow_action_rss) {
.types = VNIC_RSS_HASH_TYPES,
.queue_num = num_queue,
.queue = rss_queues,
};
actions[0] = (struct rte_flow_action) {
.type = RTE_FLOW_ACTION_TYPE_RSS,
.conf = &action.rss,
};
} else {
action.queue.index = firstq;
actions[0] = (struct rte_flow_action) {
.type = RTE_FLOW_ACTION_TYPE_QUEUE,
.conf = &action.queue,
};
}
if ((flow_mode & FLOW_SRC_MODE) &&
!flow_src_mac(portid, id, mac, actions, &err))
rte_exit(EXIT_FAILURE,
"src mac flow create failed: %s\n error type %u %s\n",
rte_strerror(rte_errno), err.type, err.message);
if ((flow_mode & FLOW_DST_MODE) &&
!flow_dst_mac(portid, id, mac, actions, &err))
rte_exit(EXIT_FAILURE,
"dst mac flow create failed: %s\n error type %u %s\n",
rte_strerror(rte_errno), err.type, err.message);
for (q = firstq; q <= lastq; q++) {
r = rte_eth_dev_rx_queue_start(portid, q);
if (r < 0)
rte_exit(EXIT_FAILURE,
"rte_eth_dev_rx_queue_start: q=%u failed\n", q);
}
}
flow_src_mac() - 基于源 MAC 地址创建流规则
使用 rte_flow_create API,配置规则匹配数据包的源 MAC 地址,并指定动作(如将数据包分配到指定的队列或启用 RSS )。
创建匹配模式:定义一个 rte_flow_item 数组 patterns 来存储匹配条件。首先检查 any_mode 标志,如果启用,则匹配任意封装层数。
if (any_mode) {
*pat++ = (struct rte_flow_item) {
.type = RTE_FLOW_ITEM_TYPE_ANY,
.spec = &inner_flow,
.mask = &any_mask,
};
};
匹配源 MAC 地址:使用 rte_flow_item_eth 匹配结构体中的 src 字段指定源 MAC 地址,并定义掩码。掩码 eth_src_mask 确保只匹配源 MAC 地址。
*pat++ = (struct rte_flow_item) {
.type = RTE_FLOW_ITEM_TYPE_ETH,
.spec = &vnic_eth_src,
.mask = ð_src_mask,
};
终止符:添加一个终止符 RTE_FLOW_ITEM_TYPE_END,指示匹配条件的结束。
*pat = (struct rte_flow_item){ .type = RTE_FLOW_ITEM_TYPE_END };
flow_dst_mac() - 基于目的 MAC 地址创建流规则
flow_dst_mac 函数与 flow_src_mac 类似,区别在于它匹配的是数据包的目的 MAC 地址,而非源 MAC 地址。
flow_configure() - 配置流规则
flow_configure 函数负责为每个 VNIC 配置源 MAC 地址和目的 MAC 地址的流规则。该函数结合了 flow_src_mac 和 flow_dst_mac,同时设置了数据包的分发方式。
配置目标队列或 RSS:通过 num_queue 参数决定是否启用 RSS。如果 num_queue 大于 1,则启用 RSS,将数据包分发到多个接收队列。否则,指定将数据包发送到 firstq 队列。
if (num_queue > 1) {
action.rss = (struct rte_flow_action_rss) {
.types = VNIC_RSS_HASH_TYPES,
.queue_num = num_queue,
.queue = rss_queues,
};
actions[0] = (struct rte_flow_action) {
.type = RTE_FLOW_ACTION_TYPE_RSS,
.conf = &action.rss,
};
} else {
action.queue.index = firstq;
actions[0] = (struct rte_flow_action) {
.type = RTE_FLOW_ACTION_TYPE_QUEUE,
.conf = &action.queue,
};
}
创建源和目的 MAC 流规则:根据 flow_mode 的值决定是否创建源或目的 MAC 的流规则。如果启用了 FLOW_SRC_MODE,调用 flow_src_mac 创建源 MAC 规则;如果启用了 FLOW_DST_MODE,则调用 flow_dst_mac 创建目的 MAC 规则。
if ((flow_mode & FLOW_SRC_MODE) &&
!flow_src_mac(portid, id, mac, actions, &err))
rte_exit(EXIT_FAILURE,
"src mac flow create failed: %s\n error type %u %s\n",
rte_strerror(rte_errno), err.type, err.message);
if ((flow_mode & FLOW_DST_MODE) &&
!flow_dst_mac(portid, id, mac, actions, &err))
rte_exit(EXIT_FAILURE,
"dst mac flow create failed: %s\n error type %u %s\n",
rte_strerror(rte_errno), err.type, err.message);
启动接收队列:循环启动每个分配的接收队列,以准备接收符合流规则的数据包。
for (q = firstq; q <= lastq; q++) {
r = rte_eth_dev_rx_queue_start(portid, q);
if (r < 0)
rte_exit(EXIT_FAILURE,
"rte_eth_dev_rx_queue_start: q=%u failed\n", q);
}
数据包接收处理
数据包接收处理模块支持两种处理模式:轮询模式和中断模式(IRQ)。该模块还包括定时器来统计接收的包和字节数。模块的关键部分包括 rx_poll、rx_thread、enable_rx_intr 和 sleep_until_interrupt 函数。
rx_poll 函数是核心的数据包接收函数。从指定的接收队列中批量接收数据包,处理并释放数据包。
static unsigned int rx_poll(struct rx_queue *rxq)
{
uint16_t portid = rxq->port_id;
uint16_t queueid = rxq->queue_id;
struct rte_mbuf *pkts[MAX_PKT_BURST];
unsigned int i, n;
// 从指定的端口和队列中批量接收数据包
n = rte_eth_rx_burst(portid, queueid, pkts, MAX_PKT_BURST);
if (n == 0)
return 0;
rxq->rx_packets += n; // 累计接收到的包数量
if (details) // 如果启用了详细信息输出
dump_rx_pkt(portid, queueid, pkts, n); // 输出数据包信息
// 释放接收的每个数据包的缓冲区
for (i = 0; i < n; i++)
rte_pktmbuf_free(pkts[i]);
return n;
}
dump_rx_pkt 函数用于打印接收到的数据包信息,包括端口和队列 ID、时间戳、包编号等。
static void dump_rx_pkt(uint16_t portid, uint16_t queueid,
struct rte_mbuf *pkts[], uint16_t n)
{
static unsigned int pktno;
uint64_t us = time_monotonic() / 1000;
uint16_t i;
for (i = 0; i < n; i++) {
printf("[%u:%u] ", portid, queueid);
if (details > 1)
printf("%-6u %"PRId64".%06"PRId64, ++pktno, us / US_PER_S, us % US_PER_S);
pkt_print(pkts[i]); // 调用外部函数打印包的详细内容
}
fflush(stdout);
}
rx_thread 是负责处理接收数据包的主线程函数。它根据设定的模式选择是否启用中断,如果启用了中断模式,则在空闲时进入休眠状态,以减少 CPU 消耗。
static int rx_thread(void *arg __rte_unused)
{
unsigned int core_id = rte_lcore_id();
struct lcore_conf *c = &lcore_conf[core_id];
uint64_t idle_start = 0;
// 如果该线程没有接收队列,直接返回
if (c->n_rx == 0)
return 0;
// 注册中断事件
if (irq_mode)
event_register(c);
while (running) {
unsigned int i, total = 0;
uint64_t cur_tsc;
int us;
// 管理定时器
rte_timer_manage();
// 轮询每个接收队列,处理接收到的数据包
for (i = 0; i < c->n_rx; i++)
total += rx_poll(&c->rx_queue_list[i]);
if (!irq_mode) // 如果不是中断模式,则继续循环
continue;
// 如果接收到数据包,则重置空闲时间
if (total > 0) {
idle_start = 0;
continue;
}
// 获取当前 TSC(时间戳计数器)值
cur_tsc = rte_rdtsc();
if (idle_start == 0) {
idle_start = cur_tsc;
continue;
}
// 计算空闲时间,超过设定值则进入中断模式休眠
us = elapsed_us(cur_tsc, idle_start);
if (us >= IDLE_POLL_US) {
sleep_until_interrupt(c);
idle_start = 0;
}
}
return 0;
}
sleep_until_interrupt - 等待中断唤醒
在中断模式下,如果数据包接收为空闲状态,程序会进入休眠模式,等待中断唤醒。sleep_until_interrupt 负责在空闲时启用接收队列的中断,并在中断到来时重新进入轮询。
static void sleep_until_interrupt(struct lcore_conf *c)
{
struct rte_epoll_event events[MAX_EVENTS];
int i, n;
// 启用接收队列中断
enable_rx_intr(c);
// 检查是否有数据包到来
for (i = 0; i < c->n_rx; i++) {
n = rte_eth_rx_queue_count(c->rx_queue_list[i].port_id,
c->rx_queue_list[i].queue_id);
if (n > 0) {
disable_rx_intr(c);
return;
}
}
// 进入休眠,等待中断事件
n = rte_epoll_wait(RTE_EPOLL_PER_THREAD, events, MAX_EVENTS, STAT_INTERVAL * MS_PER_S);
if (n < 0 && errno == EINTR)
rte_exit(EXIT_FAILURE, "rte_epoll_wait_interruptible: failed: %s\n", strerror(errno));
// 禁用中断
disable_rx_intr(c);
}
enable_rx_intr 和 disable_rx_intr 函数分别用于启用和禁用接收队列的中断。当进入休眠等待数据包到达时,需要启用中断;当数据包接收到时,禁用中断恢复轮询模式。
static void enable_rx_intr(const struct lcore_conf *c)
{
uint16_t i;
for (i = 0; i < c->n_rx; i++) {
rte_eth_dev_rx_intr_enable(c->rx_queue_list[i].port_id,
c->rx_queue_list[i].queue_id);
}
}
static void disable_rx_intr(const struct lcore_conf *c)
{
uint16_t i;
for (i = 0; i < c->n_rx; i++) {
rte_eth_dev_rx_intr_disable(c->rx_queue_list[i].port_id,
c->rx_queue_list[i].queue_id);
}
}
5275

被折叠的 条评论
为什么被折叠?



