DPDK l2fwd

前置定义

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/queue.h>
#include <setjmp.h>
#include <stdarg.h>
#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <signal.h>
#include <stdbool.h>

#include <rte_common.h>
#include <rte_log.h>
#include <rte_malloc.h>
#include <rte_memory.h>
#include <rte_memcpy.h>
#include <rte_eal.h>
#include <rte_launch.h>
#include <rte_cycles.h>
#include <rte_prefetch.h>
#include <rte_lcore.h>
#include <rte_per_lcore.h>
#include <rte_branch_prediction.h>
#include <rte_interrupts.h>
#include <rte_random.h>
#include <rte_debug.h>
#include <rte_ether.h>
#include <rte_ethdev.h>
#include <rte_mempool.h>
#include <rte_mbuf.h>
#include <rte_string_fns.h>

/* Flag变量 值为True表示程序需终止 */
static volatile bool force_quit;

/* 设置是否启用转发时的MAC地址更新功能 */
static int mac_updating = 1;

/* 每调用一次函数 从底层获取包的最大数量 */
#define MAX_PKT_BURST 32

/* TX drain every 100us */
#define BURST_TX_DRAIN_US 100

/* Local Cache的大小 */
#define MEMPOOL_CACHE_SIZE 256

/* 接收队列与发送队列Ring的大小 */
#define RTE_TEST_RX_DESC_DEFAULT 1024
#define RTE_TEST_TX_DESC_DEFAULT 1024
static uint16_t nb_rxd = RTE_TEST_RX_DESC_DEFAULT;
static uint16_t nb_txd = RTE_TEST_TX_DESC_DEFAULT;

/* 用于存放端口MAC地址的数组 下标为端口号 值为其MAC地址 */
static struct rte_ether_addr l2fwd_ports_eth_addr[RTE_MAX_ETHPORTS];

/* 用于标识启用端口的掩码 其取决于解析出的参数值 */
static uint32_t l2fwd_enabled_port_mask = 0;

/* 各端口的目的端口列表 */
static uint32_t l2fwd_dst_ports[RTE_MAX_ETHPORTS];

/* 类似于类 结构体 port_pair_params 中存放的端口数组 */
/* 端口数组[0]为端口A 端口数组[1]为端口B */
/* 表示端口A将数据转发给端口B 端口B将数据转发给端口A */
struct port_pair_params{
    #define NUM_PORTS 2
    uint16_t port[NUM_PORTS];
} __rte_cache_aligned;

/* 将结构体 port_pair_params 以数组形式存放;port_pair_params为指向数组的指针 */
static struct port_pair_params port_pair_params_array[RTE_MAX_ETHPORTS / 2];
static struct port_pair_params *port_pair_params;

/* nb_port_pair_params 变量表示记录的端口对的数量 */
static uint16_t nb_port_pair_params;

/* 分配给每个逻辑核的收发队列数量  其取决于解析出的参数值 */
static uint l2fwd_rx_queue_per_lcore = 1;
// static uint l2fwd_tx_queue_per_lcore = 1;

/* 每个逻辑核拥有的最大接收队列数量限制 */
#define MAX_RX_QUEUE_PER_LCORE 16

/* 每个逻辑核拥有的最大发送队列数量限制 */
#define MAX_TX_QUEUE_PER_LCORE 16

/* 逻辑核与端口的绑定关系 */
struct lcore_queue_conf {
    /* 绑定端口数量 */
    uint n_rx_ports; /* number */
    /* 绑定的端口数组 数组中元素的值为端口的端口号 */
    uint rx_port_list[MAX_RX_QUEUE_PER_LCORE];
} __rte_cache_aligned;

/* 将结构体 lcore_queue_conf 以数组的形式组织 存储各逻辑核与端口的绑定关系 */
struct lcore_queue_conf lcore_queue_conf[RTE_MAX_ETHPORTS];

/* 下标为端口号 值为该端口对应的发送队列缓存 */
static struct rte_eth_dev_tx_buffer *tx_buffer[RTE_MAX_ETHPORTS];
static struct rte_eth_conf port_conf = {
    .rxmode = {
        .split_hdr_size = 0,
    },
    .txmode = {
        .mq_mode = ETH_MQ_TX_NONE,
    },
};

/* 初始化内存池 */
struct rte_mempool *l2fwd_pktmbuf_pool = NULL;

/* 端口的统计数据  包括接收、发送、丢包 */
struct l2fwd_port_statistics {
    uint64_t tx; /* 发包 */
    uint64_t rx; /* 收包 */
    uint64_t dropped; /* 丢包 */
} __rte_cache_aligned;

/* 将结构体以数组形式组织  端口统计数据 */
struct l2fwd_port_statistics port_statistics[RTE_MAX_ETHPORTS];

/* 打印输出统计数据的时间间隔最大值为1天 */
#define MAX_TIMER_PERIOD 86400

/* 打印输出统计数据的时间间隔 默认为10s */
static uint64_t timer_period = 10;

1.调用rte_eal_init(argc, argv) 函数解析eal参数 并完成eal环境的初始化

返回值为成功解析的参数个数 失败返回-1 (检查返回值)

2.argv指针后移 跳过eal参数部分 调用l2fwd_parse_args(argc, argv)解析程序自身参数

返回值表示成功解析的参数个数 失败返回-1 (检查返回值)

在此之前注册SIGINT和SIGTERM信号 信号触发调用信号处理函数

3.检查端口数量 建立端口之间的转发关系

调用 rte_eth_dev_count_avail() 函数获取可用端口数量

如果存在端口映射关系配置 检查配置是否合法

检查掩码是否合法

配置目的端口数组 l2fwd_dst_ports

检查是否有孤立的端口 若有 将其目的端口设为自身端口

4.为每个端口配置逻辑核

循环遍历每一个逻辑核 从中找到一个已启用并且端口未配置满的逻辑核分配给该端口

qconf指针指向当前逻辑核与端口的绑定关系

将该端口写入绑定关系结构体数组中 绑定端口数量 +1

5.创建内存池

调用函数 rte_pktmbuf_pool_create(“mbuf_pool”, nb_mbufs,
MEMPOOL_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());

6.初始化各个端口

使用 RTE_ETH_FOREACH_DEV(portid) 宏, 遍历各个端口

为端口配置队列数量 使用函数 rte_eth_dev_configure()

检查 Rx 和 Tx 描述符数量是否满足网卡的描述符限制 不满足将其调整为边界值

获取该端口的 MAC地址 并写入 l2fwd_ports_eth_addr 数组中

初始化接收队列

fflush(stdout);

rxq_conf = dev_info.default_rxconf;

rxq_conf.offloads = local_port_conf.rxmode.offloads;

rte_eth_rx_queue_setup(portid, 0, nb_rxd, rte_eth_dev_socket_id(portid), &rxq_conf, l2fwd_pktmbuf_pool);

为每一个端口初始化发送队列

fflush(stdout);

txq_conf = dev_info.default_txconf;

tx_conf.offloads = local_port_conf.txmode.offloads;

rte_eth_tx_queue_setup(portid, 0, bn_txd, rte_eth_dev_socket_id(portid), &rxq_conf);

初始化端口的发送缓存

// 创建缓冲区
tx_buffer[portid] = rte_zmalloc_socket("tx_buffer". RTE_ETH_RX_BUFFER_SIZE(MAX_PKT_BURST), 0, rte_eth_dev_socket_id(portid));

// 初始化刚刚创建的发送缓冲
rte_eth_tx_buffer_init(tx_buffer[portid], MAX_PKT_BURST);

为不能成功发送的数据包设置回调函数

rte_eth_tx_buffer_set_err_callback(tx_buffer[portid], rte_eth_tx_buffer_count_callback, &port_statistics[portid].dropped);
rte_eth_dev_set_ptypes(portid, RTE_PTYPE_UNKNOWN, NULL, 0);

启用并运行端口

rte_eth_dev_start(portid);

如果允许打开混杂模式 就打开混杂模式

if (promiscuouts_on) ret = rte_eth_promiscuous_enable(portid);
if (ret) rte_exit(EXIT_FAILURE, "error");

初始化端口统计数据

memset(&port_statistics, 0, sizeof(port_statistics));

若无可用端口 报错退出

if (!n0b_ports_available) rte_exit(EXIT_FAILURE, "error");

检查各端口的连接状态

check_all_ports_link_status(l2fwd_enabled_port_mask);

7.启动任务

在所有核心上启动任务

// 在每个逻辑核上执行l2fwd_launch_one_lcore
rte_eal_mp_remote_launch(l2fwd_launch_one_lcore, NULL, CALL_MAIN);
RTE_LCORE_FOREACH_WORKER(lcore_id) {
    if (rte_eal_wait_lcore(lcore_id) < 0) {
        ret = -1;
        break;
    }
}

8.退出

遍历停用并关闭各端口

RTE_ETH_FOREACH_DEV(portid) {
    if ((l2fwd_enabled_port_mask & (1 << portid)) == 0) {
        continue;
    }
    rte_eth_dev_stop(portid);
    rte_eth_dev_close(portid);
}

// 释放rte_eal_init()创建的资源
rte_eal_cleanup();
/**
 * @brief SIGINT和SIGTERM信号注册的处理函数
 * @param signum 信号
 */
static int signal_handler(int signum) {
    /* 若信号为SIGINT和SIGTERM */
    if (signum == SIGINT || signum == SIGTERM) {
        printf("\n\nSignal %d received, preparing to exit...\n", signum);
        force_quit = true;
    }
}

/**
 * @brief 检查输入的端口是否合法
 * 
 */
static int check_port_pair_config() {
    
    uint32_t port_pair_config_mask = 0;
    uint32_t port_pair_mask = 0;
    uint16_t index, i, portid;

    /* 遍历解析出各端口对 */
    for (index = 0; index < nb_port_pair_params; index++)  {
        port_pair_mask = 0;

        /* NUM_PORTS 的值为2  即遍历端口对中的两个端口 */
        for (i = 0; i < NUM_PORTS; i++) {
            portid = port_pair_params[index].port[i];

            /* 检查该端口未被掩码屏蔽 */
            if ((l2fwd_enabled_port_mask & (1 << portid) == 0)) {
                printf("portid %u is not enabled in port mask\n", portid);
                return -1;
            }

            /* 检查该端口处于活动状态 */
            if (!rte_eth_dev_is_valid_port(portid)) {
                printf("port %u is not present on the board\n");
                return -1;
            }

            /* 需要确保端口未被复用 */
            /* 即可能出现 端口1-->端口2、端口1-->端口3 两条记录同时出现的情况 */
            /* 将掩码中portid对应位置1  表示该端口已被使用 */
            port_pair_mask |= 1 << portid;
        }

        /* 若与运算的结果为1  则表明该位此前即为1  端口已被使用 报错退出 */
        if (port_pair_config_mask & port_pair_mask) {
            printf("port %u is used in other port pairs\n", portid);
            return -1;
        }

        port_pair_config_mask |= port_pair_mask;
    }

    /**
     * 使用与运算  将未参与映射关系的端口禁用
     * 即在掩码中将对应位 置0
     */
    l2fwd_enabled_port_mask &= port_pair_config_mask;
    return 0;
}

/**
 * @brief 检查各端口的链路状态
 * @param portmask 端口掩码
 */
static void check_all_ports_link_status(uint32_t port_mask) {
    
/* 检测间隔为100毫秒 */
#define CHECK_INTERVAL 100

/* 最大检查时长为9秒 即90*100ms=9s */
#define MAX_CHECK_TIME 90
    
    uint16_t portid;
    uint8_t count, all_ports_up, print_flag = 0;
    struct rte_eth_link link;
    int ret;
    char link_status_text[RTE_ETH_LINK_MAX_STR_LEN];

    printf("\nChecking link status");
    fflush(stdout);
    for (count = 0; count <= MAX_CHECK_TIME; count++) {
        /* 若突发退出事件  事件处理函数 force_quit为True 退出函数 */
        if (force_quit) return;
        all_ports_up = 1;
        /* 遍历各端口 */
        RTE_ETH_FOREACH_DEV(portid) {
            /* 若突发退出事件  事件处理函数 force_quit为True */
            if (force_quit) {
                return;
            }
            
            /* 跳过被掩码屏蔽的端口 */
            if ((port_mask & (1 << portid)) == 0) {
                continue;
            }

            /* 初始化rte_eth_link结构体 该struct可存储链路的状态 */
            memset(&link, 0, sizeof(link));

            /* 获取指定端口的链路状态为up/down */
            ret = rte_eth_link_get_nowait(portid, &link);

            /* 链路状态获取失败 */
            if (ret < 0) {
                all_ports_up = 0;
                if (print_flag == 1) {
                    printf("Port %u link get failed: %s\n", portid, &link);
                }
                continue;
            }

            /* 打印链路信息 */
            if (print_flag == 1) {
                rte_eth_link_to_str(link_status_text, sizeof(link_status_text), &link);
                printf("Port %d %s\n", portid, link_status_text);
                continue;
            }

            /* 若存在链路为DOWN 则将all_ports_up 置为0  表明存在链路非up */
            if (link.link_status == ETH_LINK_DOWN) {
                all_ports_up = 0;
                break;
            }

        }

        /* 打印日志完成后 跳出循环 结束检查任务 */
        if (print_flag == 1) break;

        /* 若存在链路非up  则等待check_interval事件后重试 */
        if (all_ports_up == 0) {
            printf(".");
            fflush(stdout);
            rte_delay_ms(CHECK_INTERVAL);
        }

        /* 若在超越总限制时长后 或 全部链路均up时 */
        if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
            /* 将print_flag置为1 以最后一次检查并打印日志 */
            print_flag = 1;
            printf("done\n");
        }
    }
}

/**
 * @brief 打印统计信息
 * 
 */
// 打印输出统计信息
static void print_stats(void) {
    /**
     * @param total_packets_dropped     总丢包数量
     * @param total_packets_tx          总发包数量
     * @param total_packets_rx          总收包数量
     */
    uint64_t total_packets_dropped, total_packets_tx, total_packets_rx;
    unsigned portid;

    total_packets_dropped = 0;
    total_packets_tx = 0;
    total_packets_rx = 0;

    const char clr[] = { 27, '[', '2', 'J', '\0' };
    const char topLeft[] = { 27, '[', '1', ';', '1', 'H','\0' };

    /* Clear screen and move to top left */
    printf("%s%s", clr, topLeft);

    printf("\nPort statistics ====================================");

    for (portid = 0; portid < RTE_MAX_ETHPORTS; portid++) {
        /* skip disabled ports */
        if ((l2fwd_enabled_port_mask & (1 << portid)) == 0)
            continue;
        printf("\nStatistics for port %u ------------------------------"
               "\nPackets sent: %24"PRIu64
               "\nPackets received: %20"PRIu64
               "\nPackets dropped: %21"PRIu64,
               portid,
               port_statistics[portid].tx,
               port_statistics[portid].rx,
               port_statistics[portid].dropped);

        total_packets_dropped += port_statistics[portid].dropped;
        total_packets_tx += port_statistics[portid].tx;
        total_packets_rx += port_statistics[portid].rx;
    }
    printf("\nAggregate statistics ==============================="
           "\nTotal packets sent: %18"PRIu64
           "\nTotal packets received: %14"PRIu64
           "\nTotal packets dropped: %15"PRIu64,
           total_packets_tx,
           total_packets_rx,
           total_packets_dropped);
    printf("\n====================================================\n");

    fflush(stdout);
}

/**
 * @brief MAC地址更新功能
 * @param mbuf
 * @param dest_portid 目的端口号
 * 
 */
static void l2fwd_mac_updating(struct rte_mbuf *mbuf, uint dest_portid) {


    struct rte_ether_hdr *eth;
    void *tmp;

    /* 返回mbuf中 data 的起始地址  起始部分即为MAC头地址 */
    eth = rte_pktmbuf_mtod(mbuf, struct rte_ether_hdr *);

    /* 获取目的MAC地址 */
    tmp = &eth->dst_addr.addr_bytes[0];

    /* 将目的MAC地址更改为形如 02:00:00:00:00:xx 的个数 */
    *((uint64_t *)tmp) = 0x000000000002 + ((uint64_t)dest_portid << 40);

    /* 将源MAC地址改为发送端口的MAC地址 */
    rte_ether_addr_copy(&l2fwd_ports_eth_addr[dest_portid], &eth->src_addr);
}

/**
 * @brief 转发函数
 * @param mbuf
 * @param portid 端口号
 * 
 */
static void l2fwd_simple_forward(struct rte_mbuf *mbuf, int port_id) {

    uint dst_port;/* destination port 目的端口 */
    int sent;
    struct rte_eth_dev_tx_buffer *buffer; /* tx_buffer */

    /* 在端口转发映射表中  查询对应的目的转发端口 */
    dst_port = l2fwd_dst_ports[port_id];

    /* 若开启了MAC地址更新功能  则调用l2fwd_mac_updating函数执行MAC地址更新 */
    if (mac_updating) {
        l2fwd_mac_updating(mbuf, dst_port);
    }

    /* 获取目的端口对应的发送buffer */
    buffer = tx_buffer[dst_port];

    /* 将收到的包缓存在tx_buffer里  用于未来的发送 */
    /**
     * @param *buffer 对方用于接收数据的缓冲区
     * @param *tx_pkt 数据缓冲区
     */
    sent = rte_eth_tx_buffer(dst_port, 0, buffer, mbuf);

    /* 更新计数器 */
    if (sent) {
        port_statistics[dst_port].tx += sent;
    }
}

/**
 * @brief 具体任务核心函数
 * 
 */
static void l2fwd_main_loop() {
    /* 创建报文缓冲区  将每次接收到的数据包存于此处  也是将要被发送除去的数据包 */
    struct rte_mbuf *pkts_burst[MAX_PKT_BURST];
    struct rte_mbuf *m;
    int sent;
    /* 逻辑核 id */
    uint lcore_id;
    /* 一堆时间戳 */
    uint64_t prev_tsc, diff_tsc, cur_tsc, timer_tsc;
    uint i, j, portid, nb_rx;
    struct lcore_queue_conf *qconf;
    const uint64_t drain_tsc = (rte_get_tsc_hz() + US_PER_S - 1) / US_PER_S * BURST_TX_DRAIN_US;
    struct rte_eth_dev_tx_buffer *buffer;

    prev_tsc = 0;
    timer_tsc = 0;

    /* 获取当前逻辑核的 lcore_id */
    lcore_id = rte_lcore_id();
    /* 获取初始化时配置的逻辑核与端口的对应关系表 */
    qconf = &lcore_queue_conf[lcore_id];

    /* 若当前逻辑核无对应的端口 即无任务 则退出 */
    if (qconf->n_rx_ports == 0) {
        RTE_LOG(INFO, EAL, "lcore %u has nothing to do\n", lcore_id);
        return;
    }

    RTE_LOG(INFO, EAL, "entering main loop on lcore %u\n", lcore_id);

    for (i = 0; i < qconf->n_rx_ports; i++) {
        /* 输出显示当前逻辑核所接管的端口 */
        portid = qconf->rx_port_list[i];
        RTE_LOG(INFO, EAL, " -- lcoreid = %u portid = %u\n", lcore_id, portid);
    }

    /* 当发生强制退出事件时  事件处理函数会将 force_quit 置为 True */
    /* 使得在本轮任务完成后  退出 while 循环 结束任务 */
    while (!force_quit) {
        /* 获取从开机起到当前的时间戳 */
        cur_tsc = rte_rdtsc();

        /* 计算上次发包时间和当前时间差 */
        diff_tsc = cur_tsc - prev_tsc;

        /* 若超时 则开始执行报文的发送逻辑 */
        if (unlikely(diff_tsc > drain_tsc)) {
            /* 遍历该核所接管的所有端口 */
            for (i = 0; i < qconf->n_rx_ports; i++) {

                /* 获取当前端口的目的端口 */
                portid = l2fwd_dst_ports[qconf->rx_port_list[i]];
                /* 索引到该端口的发送队列 */
                buffer = tx_buffer[portid];
                /* 调用函数将数据发送出去 */
                sent = rte_eth_tx_buffer_flush(portid, 0, buffer);
                /* 若发送成功  更新计数器 */
                if (sent) {
                    port_statistics[portid].tx += sent;
                }
            }

            /* 若 timer_period 有效  即开启了打印统计信息的功能 */
            if (timer_period > 0) {
                /* 统计上次打印统计时间到现在的累计时间 */
                timer_tsc += diff_tsc;
                /* 若累积时间超过了设定的阈值  则打印统计信息 */
                if (unlikely(timer_tsc >= timer_period)) {
                    /**
                     * 打印统计信息仅在主逻辑核上进行
                     * 因此若 lcore_id 等于主逻辑核的编号  调用 print_stats 函数进行打印
                     */
                    if (lcore_id == rte_get_main_lcore()) {
                        print_stats();
                        /* 将计时器重置为 0  */
                        timer_tsc = 0;
                    }
                }
            }

            prev_tsc = cur_tsc;

        }

        /* 遍历当前逻辑核所接管的各端口 接收数据包 */
        for (i = 0; i < qconf->n_rx_ports; i++) {
            portid = qconf->rx_port_list[i];
            /* 一次最多接收 MAX_PKT_BURST 个数据包  放入pkts_burst 中 */
            nb_rx = rte_eth_rx_burst(portid, 0, pkts_burst, MAX_PKT_BURST);

            /* 刷新统计数据 */
            port_statistics[portid].rx += nb_rx;

            for (j = 0; j < nb_rx; j++) {
                /* 对于接收队列中的各数据 */
                m = pkts_burst[j];
                /**
                 * @brief 
                 * Prefetch 预取一个 Cache Line  参数是要取的地址  其类型是 void *
                 * 该指令主要的作用是人为判断下面将要处理的内存  指示 CPU 加载到缓存中  提升性能
                 * rte_pktmbuf_mtod 返回 mbuf 中 data 的起始地址
                 */
                rte_prefetch0(rte_pktmbuf_mtod(m, void *));
                /* 调用函数执行转发 */
                l2fwd_simple_forward(m, portid);
            }
        }

    }



}

/**
 * @brief 该函数为分配至所有逻辑核执行函数
 * 
 */
static int l2fwd_launch_one_lcore(__rte_unused void *dummy) {
    /* 再次调用 l2fwd_main_loop 函数 */
    l2fwd_main_loop();
    return 0;
}

/*  */
/* 解析输入参数 */
/* 定义短选项 其数据类型为字符串 */
static const char short_options[] = 
    "p:" /* portmask */
    "q:" /* number of queues */
    "T:" /* timer period */
    ;

/* 定义长选项的名称name */
#define CMD_LINE_OPT_NO_MAC_UPDATING "no-mac-updating"
#define CMD_LINE_OPT_PORTMAP_CONFIG "portmap"

/* 定义长选型的返回值val */
enum {
    CMD_LINE_OPT_NO_MAC_UPDATING_NUM = 256,
    CMD_LINE_OPT_PORTMAP_NUM,
};

/**
 * 定义长选项
 * **************************************************************
 * struct option{
 *     const char *name;
 *     int has_arg;
 *     int *flag;
 *     int val;
 * };
 * **************************************************************
 * 属性name表示选项的名字
 * 属性has_arg表示选项后面是否携带参数
 * [*] no_argument或是0时 表示参数后面不跟参数值
 * [*] required_argument或是1时 参数输入格式为 "--参数 值" 或 "--参数=值"
 * [*] optional_argument或是2时 参数输入格式只能是 "--参数=值"
 * 属性flag如果为Null 当选中某个长选项时 getopt_long函数将返回val值
 * [*] 例如{"help", no_argmuent, NULL, "h"} 将返回 h
 * 属性flag如果不为Null 当选中某个长选项时 getopt_long函数将返回0 并将flag指针指向val值
 * [*] 例如{"http-proxy", required_argument, &lopt, 1} 将返回0 并且lopt的值为 1
 * 属性val表示指定函数找到选项时的返回值 或 当flag非空时指定flag指向的数据的值val
 * [注意] 长选项的最后一个元素必须是全0填充 否则会报错
 */
static const struct option lgopts[] = {
    {CMD_LINE_OPT_NO_MAC_UPDATING, no_argument, 0, CMD_LINE_OPT_NO_MAC_UPDATING_NUM},
    {CMD_LINE_OPT_PORTMAP_CONFIG, 1, 0, CMD_LINE_OPT_PORTMAP_NUM},
    {NULL, 0, 0, 0}
};

/**
 * @brief 参数p为portmask 依据这个十六进制掩码选择使用的端口
 * 
 * @param portmask 
 * @return uint32_t 
 */
static uint32_t l2fwd_parse_portmask(const char *portmask) {
    char *end = NULL;
    ulong pm;
    // *******************************************************************
    // char str[30] = "2030300 This is test";
    // char *ptr;
    // long ret;
    // ret = strtoul(str, &ptr, 10);
    // printf("数字是 %lu\n", ret);
    // printf("字符串部分是 %s", ptr);
    // *******************************************************************
    // 调用函数strtoul将值为16进制的portmask字符串转换为无符号长整数pm
    pm = strtoul(portmask, &end, 16);
    /* 检查保证传入字符串的值为纯数字 即传入字符串非空 & 无尾部字符串部分 */
    if ((portmask[0] == '\0') || (end == NULL) || (*end != '\0')) {
        return 0;
    }

    return pm;
}

/**
 * @brief 当参数解析错误时 调用该函数打印程序参数与选项的说明
 * 
 * @param optarg 
 */
static void l2fwd_usage(const char *prgname) {
    printf("%s [EAL options] -- -p PORTMASK [-q NQ]\n"
           "  -p PORTMASK: hexadecimal bitmask of ports to configure\n"
           "  -q NQ: number of queue (=ports) per lcore (default is 1)\n"
           "  -T PERIOD: statistics will be refreshed each PERIOD seconds (0 to disable, 10 default, 86400 maximum)\n"
           "  --no-mac-updating: Disable MAC addresses updating (enabled by default)\n"
           "      When enabled:\n"
           "       - The source MAC address is replaced by the TX port MAC address\n"
           "       - The destination MAC address is replaced by 02:00:00:00:00:TX_PORT_ID\n"
           "  --portmap: Configure forwarding port pair mapping\n"
           "          Default: alternate port pairs\n\n",
           prgname);
}

/**
 * @brief 参数q为nqueue 表示分配给每个逻辑核的收发队列数量
 * 
 * @param q_arg 
 * @return uint 
 */
static uint l2fwd_parse_nqueue(const char *q_arg) {
    char *end = NULL;
    ulong n;

    /* 与l2fwd_parse_portmask函数同理 转换为无符号长整数n */
    n = strtoul(q_arg, &end, 10);

    /* 检查保证解析结果的正确性 */
    if ((q_arg[0] == '\0') || (end == NULL) || (*end != '\0')) {
        return 0;
    }

    /* 收发队列数不能为0 */
    if (n == 0) {
        return 0;
    }

    /* 收发队列数不能超过设定的限制 */
    if (n > MAX_RX_QUEUE_PER_LCORE) {
        return 0;
    }

    return n;
}

static int l2fwd_parse_timer_period(const char *q_arg) {
    char *end = NULL;
    int n;

    n = strtoul(q_arg, &end, 10);

    if ((q_arg[0] == '\0') || (end == NULL) || (*end != '\0')) return -1;

    if (n > MAX_TIMER_PERIOD) return -1;
    return n;
}

/**
 * @brief 长选项CMD_LINE_OPT_PORTMAP_NUM的解析调用
 * 
 * @param q_arg 
 * @return int 
 */
static int l2fwd_parse_port_pair_config(const char *q_arg) {
    enum fieldnames {
        FLD_PORT1 = 0,
        FLD_PORT2,
        _NUM_FLD
    };
    ulong int_fld[_NUM_FLD];
    const char *p, *p0 = q_arg;
    char *str_fld[_NUM_FLD];
    uint size;
    char s[256];
    char *end;
    int i;

    /* 计数器表示已解析记录的端口对数量 */
    nb_port_pair_params = 0;

    /* 使用while循环进行解析  知道无法找到下一个 '(' */
    while ((p = strchr(p0, '(')) != NULL) {
        ++p;
        /* 找到与当前 '('匹配的 ')' 左右括号之间的字符串表示的即是一对端口 */
        p0 = strchr(p, ')');
        if (p0 == NULL) {
            return -1;
        }

        /* 获取左右括号之间字符串的大小  并检查是否超过容器s的大小 */
        size = p0 - p;
        if (size >= sizeof(s)) {
            return -1;
        }

        /* 将左右括号之间的字符串copy至容器s */
        memcpy(s, p, size);

        /* 在s的末尾处增加'\0' 以符合字符串格式 */
        s[size] = '\0';
        /* 将字符串使用 ',' 进行划分  理论上应该划分出2部分 */
        /* 划分出的字符串存于数组str_fld中 */
        if (rte_strsplit(s, sizeof(s), str_fld, _NUM_FLD, ',') != _NUM_FLD) return -1;

        /* 遍历数组str_fld */
        for (i = 0; i < _NUM_FLD; i++) {
            errno = 0;
            /* 将数组str_fld中的字符串转化为数值并存于数组int_fld中 */
            int_fld[i] = strtoul(str_fld[i], &end, 0);
            if (errno != 0 || end == str_fld[i] || int_fld[i] >= RTE_MAX_ETHPORTS) return -1;
        }

        /* 检查端口数量是否超过了设定的端口限制 */
        if (nb_port_pair_params >= RTE_MAX_ETHPORTS / 2) {
            printf("exceeded max number of port pair params : %hu\n", nb_port_pair_params);
            return -1;
        }

        /* 将解析出的端口映射关系存于port_pair_params_array数组中 */
        port_pair_params_array[nb_port_pair_params].port[0] = (uint16_t)int_fld[FLD_PORT1];
        port_pair_params_array[nb_port_pair_params].port[1] = (uint16_t)int_fld[FLD_PORT2];

        /* 记录的端口数量增加了 1 个 */
        ++nb_port_pair_params;
    }

    port_pair_params = port_pair_params_array;
    return 0;

}

/**
 * @brief 解析参数
 * 
 * @param argc 要解析参数的个数
 * @param argv 参数数组的数组指针
 * @return int 
 */
static int l2fwd_parse_args(int argc, char **argv) {
    int opt, ret, timer_secs;
    char **argvopt;
    int option_index;
    char *prgname = argv[0];

    argvopt = argv[0];
    port_pair_params = NULL;
    /**
     * 短选项是指由一个连字符和一个字母构成的选项  例如-a, -s 
     * 长选项是指由两个连字符和一些大小写字母组合的单词构成的选项  如--size, --help
     * getopt函数用来解析命令行选项参数  但是只能解析短选项
     * getopt_long函数在getopt的基础上  增加了解析长选项的功能
     * 参数optstring为短选项的选项数组
     * 参数longopts为长选项的选项数组
     * 参数longindex指示长选项中的哪一个选项  即为longopts中的下标值
     * 对于短选项 如果选项成功找到 返回选项字符
     * 对于长选项 如果选项成功找到 如果flag为空就返回val 如果flag非空就返回 0
     * 如果所有命令行选项都解析完毕 返回 -1
     */

    while (~(opt = getopt_long(argc, argvopt, short_options, lgopts, &option_index))) {
        /* 对于短选项 返回值为选项的字符 */
        /* 对于长选项 由于设置中flag均为NULL 因此返回值为设置的val值 */
        switch (opt) {
            /* 参数p为portmask 依据该掩码选择使用的端口 */
            case 'p':
                /* 返回值为解析出的掩码 */
                l2fwd_enabled_port_mask = l2fwd_parse_portmask(optarg);
                if (l2fwd_enabled_port_mask == 0) {
                    /* 参数解析失败  调用函数打印参数选项的说明  并退出 */
                    printf("invalid portmask\n");
                    l2fwd_usage(prgname);
                    return -1;
                }
                break;
            case 'q':
                /* 返回值为解析出的队列数量 */
                l2fwd_rx_queue_per_lcore = l2fwd_parse_nqueue(optarg);
                if (l2fwd_rx_queue_per_lcore == 0) {
                    printf("invalid queue number\n");
                    l2fwd_usage(prgname);
                    return -1;
                }
                break;
            /* 参数T为timer_period 表示打印输出统计数据的时间间隔 */
            case 'T':
                /* 返回值为解析出的时间间隔 */
                timer_secs = l2fwd_parse_timer_period(optarg);
                if (timer_secs < 0) {
                    printf("invalid timer period\n");
                    l2fwd_usage(prgname);
                    return -1;
                }
                break;
            /* 长选项 CMD_LINE_OPT_PORTMAP_NUM */
            case CMD_LINE_OPT_PORTMAP_NUM:
                /* 调用函数配置端口间的映射关系 */
                ret = l2fwd_parse_port_pair_config(optarg);
                if (ret) {
                    fprintf(stderr, "Invalid config\n");
                    l2fwd_usage(prgname);
                    return -1;
                }
                break;
            /* 长选项CMD_LINE_OPT_NO_MAC_UPDATING_NUM */
            case CMD_LINE_OPT_NO_MAC_UPDATING_NUM:
                /* 关闭转发时的MAC地址更新功能 */
                mac_updating = 0;
                break;
            /* 解析失败 */
            default: 
                /* 调用函数打印参数选项的说明  并退出 */
                l2fwd_usage(prgname);
                return -1;
        }
    }

    if (optind >= 0) {
        argv[optind - 1] = prgname;
    }
    /* optind 变量表示再次调用getopt时的下一个argv的索引 */
    /* 因此解析的参数数量为 optind - 1 */

    ret = optind - 1;
    
    /* 本轮调用getopt完成后 需将optind变量初始化为 1 */
    optind = 1;
    return ret;
}


/* 入口函数  进行环境的初始化和命令行参数的解析 */
int main(int argc, char **argv) {
    
    struct lcore_queue_conf *qconf;
    int ret;
    /* 端口数量 */
    uint16_t nb_ports;
    /* 可用端口数量 */
    uint16_t nb_ports_available;
    uint16_t portid, last_port;

    uint lcore_id, rx_lcore_id;
    uint nb_ports_in_mask = 0;
    /* 逻辑核数量 */
    uint nb_lcores = 0;
    /* 内存池数量 */
    uint nb_mbufs;

    /* TODO: part1 */
    /* 调用 rte_eal_init 函数初始化EAL环境 */
    /**
     * @brief 
     * @return 返回值为解析的参数个数,返回-1表示环境初始化失败
     * 
     */
    ret = rte_eal_init(argc, argv);
    if (ret < 0) {
        rte_exit(EXIT_FAILURE, "Invalid EAL arguments\n");
    }

    /* 更新 argc 参数个数与 argv 参数指针 跳过EAL参数部分 */
    argc -= ret;
    argv += ret;

    /* 初始化程序终止 Flag 为 false */
    force_quit = false;
    
    /**
     * @brief 
     * 注册程序终止信号 即 Ctrl + C
     * 此时通过按 Ctrl + C 将不再直接终止程序的运行
     * 按键产生的 SIGINT 信号将触发调用注册的 signal_handler函数
     * signal_handler 函数中核心操作为 将 force_quit 设置为 true
     * 
     */
    signal(SIGINT, signal_handler);

    /* 同理注册 SIGTERM 信号触发调用 signal_handler 函数 */
    signal(SIGTERM, signal_handler);

    /* 调用 l2fwd_parse_args 解析程序自身部分的参数 */
    /**
     * @brief 
     * @return 返回值为解析的参数个数 返回 -1 表示参数解析失败
     * 
     */
    ret = l2fwd_parse_args(argc, argv);
    if (ret < 0) {
        rte_exit(EXIT_FAILURE, "Invalid L2FWD arguments\n");
    }

    printf("MAC updating %s\n", mac_updating ? "enabled" : "disabled");

    /* 将 timer_period 表示的秒数 乘以一秒钟的时钟周期数 */
    timer_period *= rte_get_timer_hz();

    /* TODO: part2 */
    /* 检查端口数量  并建立端口之间的转发关系 */

    /* 使用 rte_eth_dev_count_avail 函数获取可用的端口数量 nb_ports */
    nb_ports = rte_eth_dev_count_avail();
    /* 当无可用端口  报错并退出 */
    if (nb_ports == 0) {
        rte_exit(EXIT_FAILURE, "No Ethernet ports available\n");
    }

    /* 当存在端口映射关系的配置时  检查端口映射关系的配置是否合法  报错则退出 */
    if (port_pair_params != NULL) {
        if (check_port_pair_config() < 0) {
            rte_exit(EXIT_FAILURE, "Invalid port pair configuration\n");
        }
    }


    /**
     * @brief 
     * 检查掩码是否合法
     * 位运算  将 1 左移 nb_ports 位  后减 1 取反  与掩码做与运算
     * 设有 4 个端口  1 左移 4 位得到 10000(binary) 减 1 得到 1111 取反得到 0000
     * 若掩码为 0100 与掩码与运算得到 0000 不触发 if 条件
     * 若掩码为 10100 与运算得到 10000 触发 if 条件 即掩码不合法
     */
    if (l2fwd_enabled_port_mask & ~((1 << nb_ports) - 1)) {
        rte_exit(EXIT_FAILURE, "Invalid portmask; possible (0x%x)\n", (1 << nb_ports) - 1);
    }

    /* 初始化目的端口数组 各端口的目的转发端口初始化均为0 */
    // for (portid = 0; portid < RTE_MAX_ETHPORTS; portid++) {
    //     l2fwd_dst_ports[portid] = 0;
    // }
    memset(l2fwd_dst_ports, 0, sizeof(l2fwd_dst_ports));
    
    /* last_port 用于便利及避免孤立端口的存在 同样初始化为 0 */
    last_port = 0;

    /* 配置 l2fwd_dst_ports 目的端口数组 */
    if (port_pair_params != NULL) {
        /* 若已经输入配置端口配对 */
        uint16_t idx, p;

        /* 将配置的端口写入 l2fwd_dst_ports 数组中 */
        for (idx = 0; idx < (nb_port_pair_params << 1); idx++) {
            p = idx & 1;
            portid = port_pair_params[idx >> 1].port[p];
            l2fwd_dst_ports[portid] = port_pair_params[idx >> 1].port[p ^ 1];
        }
    }
    else {
        /* 若未输入配置端口配对 */
        /* 遍历各端口  由程序自动设置每个端口的目的端口 */
        RTE_ETH_FOREACH_DEV(portid) {
            /* 跳过被掩码屏蔽的端口 */
            if ((l2fwd_enabled_port_mask & (1 << portid)) == 0) {
                continue;
            }

            /* 将端口两两配对 */
            /* 如 l2fwd_dst_ports[3] = 2 l2fwd_dst_ports[2] = 3 */
            if (nb_ports_in_mask & 1) {
                l2fwd_dst_ports[portid] = last_port;
                l2fwd_dst_ports[last_port] = portid;
            }
            else {
                last_port = portid;
            }

            nb_ports_in_mask++;
            /* nb_ports_in_mask 相当于一个循环变量 */
        }

        /* 端口两两配对后多出来一个孤端口  那么出端口设置为本身 同进同出 */
        /* 最后结束的时候如果还多出来一个端口... */
        if (nb_ports_in_mask & 1) {
            printf("Notice : odd number of ports in port mask\n");
            l2fwd_dst_ports[last_port] = last_port;
        }
    }

    /* TODO: part3 */
    /* 为每个端口分配逻辑核 */
    rx_lcore_id = 0;
    qconf = NULL;

    /* 遍历各端口  为每一个端口分配一个可用的逻辑核 */
    RTE_ETH_FOREACH_DEV(portid) {
        /* 检查掩码 跳过未开启的端口 */
        /* 此处为位运算  掩码中端口对应的位若为 0 表示端口未被使用 */
        if ((l2fwd_enabled_port_mask & (1 << portid)) == 0) continue;

        /**
         * @brief 
         * 从编号为 0 的核心开始尝试逻辑核是否可分配给该端口
         * 条件A 编号为 rx_lcore_id 的逻辑核已使用
         * 条件B 不超过当前逻辑核的最大队列数量
         */
         /* TODO: the lcore must be enabled */
        while (rte_lcore_is_enabled(rx_lcore_id) == 0 ||
                lcore_queue_conf[rx_lcore_id].n_rx_ports == 
                l2fwd_rx_queue_per_lcore) {
            /* 尝试下一个逻辑核 */
            rx_lcore_id++;

            /* 若尝试完 全部的逻辑核  即无可用的逻辑核供分配  报错并退出 */
            if (rx_lcore_id >= RTE_MAX_LCORE) {
                rte_exit(EXIT_FAILURE, "Not enough cores available\n");
            }
        }

        /*  */
        if (qconf != &lcore_queue_conf[rx_lcore_id]) {
            /* 临时指针 qconf 指向该逻辑核的 lcore_queue_conf */
            qconf = &lcore_queue_conf[rx_lcore_id];
            nb_lcores++;
        }

        /* 在 rx_port_list 数组中添加这个端口号  并将 n_rx_ports 数量加 1 */
        qconf->rx_port_list[qconf->n_rx_ports] = portid;
        qconf->n_rx_ports++;
        printf("Lcore %u: RX port %u TX port %u\n", rx_lcore_id, portid, l2fwd_dst_ports[portid]);


    }

    /* TODO: part4 */
    /* 创建内存池  初始化各端口 */

    /* 计算 mbuf 的大小 最大为8192个 */
    /* 每个端口 * (接收 Ring 大小 + 发送 Ring 大小 + 一次接收的数据包 + 逻辑核数量 * Local Cache) */
    nb_mbufs = RTE_MAX(nb_ports * (nb_rxd + nb_txd + MAX_PKT_BURST + 
    nb_lcores * MEMPOOL_CACHE_SIZE), 8192U);

    /**
     * @brief 
     * 创建内存池
     * 内存池名为mbuf_pool
     * 内存池有 nb_mbufs 个 mbuf
     * Local Cache 的大小为 MEMPOOL_CACHE_SIZE
     * 私有数据空间大小为 0
     * 每个 mbuf 的数据区大小为 RTE_MBUF_DEFAULT_BUF_SIZE
     * 在 rte_socket_id() 指示的 socket 上创建
     */
    l2fwd_pktmbuf_pool = rte_pktmbuf_pool_create("mbuf_pool", nb_mbufs, 
    MEMPOOL_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());

    if (l2fwd_pktmbuf_pool == NULL) {
        rte_exit(EXIT_FAILURE, "Cannot init mbuf pool\n");
    }

    /* 遍历各端口  以初始化各端口  即写入端口的相关配置信息 */
    RTE_ETH_FOREACH_DEV(portid) {
        /* 配置以太网端口的接收队列 */
        struct rte_eth_rxconf rxq_conf;
        /* 配置以太网端口的发送队列 */
        struct rte_eth_txconf txq_conf;
        /* 配置以太网端口 */
        struct rte_eth_conf local_port_conf = port_conf;
        /*  */
        struct rte_eth_dev_info dev_info;

        /* 跳过被掩码屏蔽的端口 */
        if ((l2fwd_enabled_port_mask & (1 << portid)) == 0) {
            printf("Skipping disabled port %u\n", portid);
            continue;
        }

        /* 可用端口计数器加 1 */
        nb_ports_available++;

        printf("Initializing port %u\n", portid);
        /* 清除 stdout 缓冲区  即强制将未写入磁盘的内容立即写入 */
        fflush(stdout);

        /**
         * @brief 
         * 判断传入的 portid 是否合法 非法则退出程序
         * 传入端口 ID
         * 函数 rte_eth_dev_info_get 使 dev_info 指针指向对应设备信息的结构体
         */

        ret = rte_eth_dev_info_get(portid, &dev_info);
        if (ret) {
            rte_exit(EXIT_FAILURE, "Error: during getting device (port %u) info: %s", portid, strerror(-ret));
        }

        /* 当设备信息表明支持 DEV_TX_OFFLOAD_MBUF_FAST_FREE 功能时 */
        /* 将信息置入端口配置中 以启用新特性 */
        if (dev_info.tx_offload_capa & DEV_TX_OFFLOAD_MBUF_FAST_FREE) {
            local_port_conf.txmode.offloads |= DEV_TX_OFFLOAD_MBUF_FAST_FREE;
        }
        
        /**
         * @brief 
         * 调用函数 rte_eth_dev_configure 配置端口
         * @param port_id 为端口的编号
         * @param nb_rx_q 为给该端口分配的收包队列数量 此处为 1 个
         * @param nb_tx_q 为给该端口分配的发包队列数量 此处为 1 个
         * @param dev_conf 为对该端口的配置
         * @return 返回值为 0 表示设备已被成功配置
         * 
         */
         /* Configure the number of queues for a port. */
        ret = rte_eth_dev_configure(portid, 1, 1, &local_port_conf);
        if (ret != 0) {
            rte_exit(EXIT_FAILURE, "Cannot adjust number of descriptors: err = %d, port = %u\n", ret, portid);
        }

        /* 检查 Rx 和 Tx 描述符数量是否满足网卡的描述符限制 不满足将其调整为边界值 */
        ret = rte_eth_dev_adjust_nb_rx_tx_desc(portid, &nb_rxd, &nb_txd);

        if (ret < 0) {
            rte_exit(EXIT_FAILURE, "error");
        }

        /* 获取该端口的 MAC地址 并将 MAC地址填写入 l2fwd_ports_eth_addr 数组中 */
        ret = rte_eth_macaddr_get(portid, &l2fwd_ports_eth_addr[portid]);
        if (ret < 0) {
            rte_exit(EXIT_FAILURE, "Cannot get MAC address");
        }

        /* 初始化接收队列 */
        fflush(stdout);
        /* 为接收队列填入默认的配置信息 */
        rxq_conf = dev_info.default_rxconf;
        rxq_conf.offloads = local_port_conf.rxmode.offloads;

        /* 参数 nb_rxd 指接收队列的大小 即最大能存储 mbuf 的数量 */
        ret = rte_eth_rx_queue_setup(portid, 0, nb_rxd, rte_eth_dev_socket_id(portid), &rxq_conf, l2fwd_pktmbuf_pool);
        if (ret != 0) {
            rte_exit(EXIT_FAILURE, "rte_eth_rx_queue_setup:err = %d, port = %u\n", ret, portid);
        }

        /* 在每个端口上初始化发送队列 */
        fflush(stdout);
        txq_conf = dev_info.default_txconf;
        txq_conf.offloads = local_port_conf.txmode.offloads;
        ret = rte_eth_tx_queue_setup(portid, 0, nb_txd, rte_eth_dev_socket_id(portid), &txq_conf);
        if (ret) {
            rte_exit(EXIT_FAILURE, "rte_eth_tx_queue_setup:err = %d, port = %u\n", ret, portid);
        }
        

        /* 初始化端口 portid 的发送缓存 */
        /* 函数 rte_zmalloc_socket 用于指定从哪个 socket 上分配内存空间 */
        tx_buffer[portid] = rte_zmalloc_socket("tx_buffer", RTE_ETH_TX_BUFFER_SIZE(MAX_PKT_BURST), 
        0, rte_eth_dev_socket_id(portid));

        if (tx_buffer[portid] == NULL) {
            rte_exit(EXIT_FAILURE, "Cannot allocate buffer for tx on port %u", portid);
        }

        /* 初始化创建的发送缓存区 参数分别为缓存区指针与大小 */
        rte_eth_tx_buffer_init(tx_buffer[portid], MAX_PKT_BURST);

        /**
         * @brief 
         * 对于无法发送的数据包  默认动作是丢包 并调用回调函数
         * rte_eth_tx_buffer_count_callback 为回调函数的指针
         * &port_statistics[portid].dropped 为回调函数的参数
         * 因此 此次丢包后更新计数器 将丢包计数 +1
         */
        ret = rte_eth_tx_buffer_set_err_callback(tx_buffer[portid], rte_eth_tx_buffer_count_callback,
        &port_statistics[portid].dropped);
        if (ret < 0) {
            rte_exit(EXIT_FAILURE, "Cannot set error callback for tx buffer on port %u", portid);
        }

        ret = rte_eth_dev_set_ptypes(portid, RTE_PTYPE_UNKNOWN, NULL, 0);
        if (ret < 0) {
            printf("Port %u, Failed to disabled Ptype parsing\n", portid);
        }

        /* 配置完成后 启动运行该端口 */
        ret = rte_eth_dev_start(portid);
        if (ret < 0) {
            rte_exit(EXIT_FAILURE, "rte_eth_dev_start:err = %d, port = %u\n", ret, portid);
        }

        printf("done: \n");

        /* 打开端口的混杂模式 允许所有报文进入 */
        ret = rte_eth_promiscuous_enable(portid);
        if (ret < 0) {
            rte_exit(EXIT_FAILURE, "rte_eth_promiscuous_enable:err = %s, port = %u\n", rte_strerror(-ret), portid);
        }

        printf("Port %u, MAC address: " RTE_ETHER_ADDR_PRT_FMT "\n\n", portid, RTE_ETHER_ADDR_BYTES(&l2fwd_ports_eth_addr[portid]));

        /* 初始化端口的统计数据 */
        memset(&port_statistics, 0, sizeof(port_statistics));
        // memset(port_statistics, 0, sizeof(port_statistics));

        /* 此时可用端口数应非 0 */
        if (!nb_ports_available) {
            rte_exit(EXIT_FAILURE, "All available ports are disabled. Please set portmask.\n");
        }

        /* 检查每个端口的连接状态 */
        check_all_ports_link_status(l2fwd_enabled_port_mask);
    }


    /* TODO: part5 */
    /* 任务分发 启动任务 */
    /* 分配所有的逻辑核执行函数 l2fwd_launch_one_lcore() */
    /* CALL_MAIN 表示在 Mater Core 上也执行任务 */

    ret = 0;
    rte_eal_mp_remote_launch(l2fwd_launch_one_lcore, NULL, CALL_MAIN);

    RTE_LCORE_FOREACH_WORKER(lcore_id) {
        /**
         * @brief 
         * rte_eal_wait_lcore() 函数只能在主核心上运行  用于等待 lcore_id 核完成任务
         * 函数内含有 while 循环 用于一直等待其他核心完成任务
         * 主核心进入该函数  表明主核心上的业务逻辑已经完成 也说明其他进程即将结束
         * 因此在业务完成之前  不会运行 while 循环  也不会存在长期空跑 CPU 的情况
         */
        if (rte_eal_wait_lcore(lcore_id) < 0) {
            ret = -1;
            break;
        }
    }

    /* 当运行到此处表明程序进入退出状态 */
    RTE_ETH_FOREACH_DEV(portid) {
        /* 跳过被掩码屏蔽的端口 */
        if ((l2fwd_enabled_port_mask & (1 << portid)) == 0) {
            continue;
        }
        printf("Closing port %d...\n", portid);
        /* 遍历停用各端口 */
        ret = rte_eth_dev_stop(portid);
        if (ret < 0) {
            printf("rte_eth_dev_stop: err = %d, port = %u\n", ret, portid);
        }

        /* 遍历关闭各端口 */
        rte_eth_dev_close(portid);
        printf("done\n");

    }

    /* 调用 rte_eal_cleanup 函数释放 rte_eal_init 函数创建的资源 */
    rte_eal_cleanup();
    printf("Bye...\n");

    return 0;
}

参考
计算机网络之DPDK(五)l2fwd程序
L2 Forwarding Sample Application (in Real and Virtualized Environments)

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
DPDK提供了一个示例应用程序l3fwd,它可以用于转发IPv4流量。以下是在Linux环境下运行l3fwd的步骤: 1. 安装DPDK并设置环境变量。 2. 绑定网卡。使用DPDK需要将网卡与igb_uio驱动程序绑定。可以使用以下命令绑定网卡: sudo $RTE_SDK/usertools/dpdk-devbind.py --bind=igb_uio eth1 其中,eth1是要绑定的网卡名称。 3. 设置Hugepage。DPDK需要使用大页来提高性能。可以使用以下命令设置大页: sudo mkdir -p /mnt/huge sudo mount -t hugetlbfs nodev /mnt/huge sudo echo 1024 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages 这将创建一个名为/mnt/huge的目录,并在其中创建1024个2MB大页。 4. 编译l3fwd程序。可以使用以下命令编译l3fwd程序: cd $RTE_SDK/examples/l3fwd make 5. 运行l3fwd程序。可以使用以下命令运行l3fwd程序: sudo ./build/l3fwd -l 0-3 -n 4 -- -p 0x3 --config="(0,0,1),(1,0,2),(2,0,3)" --parse-ptype 其中,-l选项指定要使用的CPU核心,-n选项指定要使用的内存通道数,-p选项指定要使用的网卡端口,--config选项指定端口与CPU核心的映射关系,--parse-ptype选项指定要解析的协议类型。 6. 测试l3fwd程序。可以使用以下命令向l3fwd程序发送流量: sudo $RTE_SDK/examples/l3fwd/build/app/testpmd -c 0x3 -n 4 -- -i --portmask=0x3 --forward-mode=io --auto-start 然后在testpmd程序中输入start命令开始发送流量。 以上是在Linux环境下运行l3fwd程序的步骤。请注意,具体的命令和参数可能因系统配置不同而异。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值