DPDK 简易应用开发之路 6:流规则配置与多队列数据包处理

部署运行你感兴趣的模型镜像

本机环境为 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 = &eth_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 = &eth_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 = &eth_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 = &eth_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);
    }
}

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值