DPDK单核收发包 源码解读

dpdk 使用 mbuf 保存 packet,mempool 用于操作 mbuf。

数据结构:
rte_mbuf——dpdk对报文的封装结构
rte_ring——dpdk无锁缓冲区,用于高性能的生产者消费者场景,比如virtio的前后端收发报文。

常用函数:
rte_eal_init——dpdk初始化函数,里面读了各种命令行参数,解析配置,初始化大页及其管理结构,创建dpdk线程均在里面实现。线程正式运行我们的写的函数通过另一个函数实现
rte_memcpy——dpdk拷贝函数,充分利用了单个指令的带宽长度。
rte_eal_remote_launch——正式执行线程函数
rte_eth_rx_burst——物理口收包函数
rte_eth_tx_burst——物理口发包函数

收发包过程大致可以分为2个部分:

1.收发包的配置和初始化,主要是配置收发队列等。
2.数据包的获取和发送,主要是从队列中获取到数据包或者把数据包放到队列中。

主函数

/*
 * The main function, which does initialization and calls the per-lcore
 * functions.
 */
int
main(int argc, char *argv[])
{
        struct rte_mempool *mbuf_pool;  //指向内存池结构的指针变量
        unsigned nb_ports;              //网口个数
        uint8_t portid;                 //网口号,临时的标记变量

        /* Initialize the Environment Abstraction Layer (EAL). */
        int ret = rte_eal_init(argc, argv);     //初始化
        if (ret < 0)
                rte_exit(EXIT_FAILURE, "Error with EAL initialization\n");

        argc -= ret;                    
        argv += ret;                    
        /* Check that there is an even number of ports to send/receive on. */
        nb_ports = rte_eth_dev_count(); //获取当前有效网口的个数
        if (nb_ports < 2 || (nb_ports & 1))     //如果有效网口数小于2或有效网口数为奇数0,则出错
                rte_exit(EXIT_FAILURE, "Error: number of ports must be even\n");

        /* Creates a new mempool in memory to hold the mbufs. */
        /*创建一个新的内存池*/
        //"MBUF_POOL"内存池名, NUM_MBUFS * nb_ports网口数,
        //此函数为rte_mempoll_create()的封装
        mbuf_pool = rte_pktmbuf_pool_create("MBUF_POOL", NUM_MBUFS * nb_ports,
                MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
        if (mbuf_pool == NULL)
                rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n");

        //初始化所有的网口
        /* Initialize all ports. */
        for (portid = 0; portid < nb_ports; portid++)   //遍历所有网口
                if (port_init(portid, mbuf_pool) != 0)  //初始化指定网口,需要网口号和内存池
                        rte_exit(EXIT_FAILURE, "Cannot init port %"PRIu8 "\n",
                                        portid);

        //如果逻辑核心总数>1 ,打印警告信息,此程序用不上多个逻辑核心
        //逻辑核心可以通过传递参数 -c 逻辑核掩码来设置
        if (rte_lcore_count() > 1)
                printf("\nWARNING: Too many lcores enabled. Only 1 used.\n");

        /* Call lcore_main on the master core only. */
        //执行主函数
        lcore_main();

        return 0;
}

1 port 初始化:port_init(portid, mbuf_pool)

/*
 * Initializes a given port using global settings and with the RX buffers
 * coming from the mbuf_pool passed as a parameter.
 */

/*
        指定网口的队列数,本列中指定的是单队列
        在tx、rx两个方向上,设置缓冲区
*/
static inline int
port_init(uint8_t port, struct rte_mempool *mbuf_pool)
{
        struct rte_eth_conf port_conf = port_conf_default;      //网口配置=默认的网口配置
        const uint16_t rx_rings = 1, tx_rings = 1;              //网口tx、rx队列的个数
        int retval;                     //临时变量,返回值
        uint16_t q;                     //临时变量,队列号

        // rte_eth_dev_count()获取可用eth的个数
        if (port >= rte_eth_dev_count()) 
                return -1;

        /* Configure the Ethernet device. */
        //配置以太网口设备
        //网口号、发送队列个数、接收队列个数、网口的配置
        retval = rte_eth_dev_configure(port, rx_rings, tx_rings, &port_conf);   //设置网卡设备
        if (retval != 0)
                return retval;

        /* Allocate and set up 1 RX queue per Ethernet port. */
        //RX队列初始化
        for (q = 0; q < rx_rings; q++) { //遍历指定网口的所有rx队列
                //申请并设置一个收包队列
                //指定网口,指定队列,指定队列RING的大小,指定SOCKET_ID号,指定队列选项(默认NULL),指定内存池
                retval = rte_eth_rx_queue_setup(port, q, RX_RING_SIZE,
                                rte_eth_dev_socket_id(port), NULL, mbuf_pool);
                if (retval < 0)
                        return retval;
        }

        //TX队列初始化
        /* Allocate and set up 1 TX queue per Ethernet port. */
        for (q = 0; q < tx_rings; q++) {   //遍历指定网口的所有tx队列
                //申请并设置一个发包队列
                //指定网口,指定队列,指定队列RING大小,指定SOCKET_ID号,指定选项(NULL为默认)
                retval = rte_eth_tx_queue_setup(port, q, TX_RING_SIZE, rte_eth_dev_socket_id(port), NULL);
                if (retval < 0)
                        return retval;
        }

        /* Start the Ethernet port. */
        retval = rte_eth_dev_start(port);       //启动网卡
        if (retval < 0)
                return retval;

        /* Display the port MAC address. */
        struct ether_addr addr;
        rte_eth_macaddr_get(port, &addr);       //获取网卡的MAC地址,并打印
        printf("Port %u MAC: %02" PRIx8 " %02" PRIx8 " %02" PRIx8
                           " %02" PRIx8 " %02" PRIx8 " %02" PRIx8 "\n",
                        (unsigned)port,
                        addr.addr_bytes[0], addr.addr_bytes[1],
                        addr.addr_bytes[2], addr.addr_bytes[3],
                        addr.addr_bytes[4], addr.addr_bytes[5]);

        /* Enable RX in promiscuous mode for the Ethernet device. */
        rte_eth_promiscuous_enable(port);       //设置网卡为混杂模式

        return 0;
}

收发包的配置最主要的工作就是配置网卡的收发队列,设置DMA拷贝数据包的地址等,配置好地址后,网卡收到数据包后会通过DMA控制器直接把数据包拷贝到指定的内存地址。我们使用数据包时,只要去对应队列取出指定地址的数据即可。

收发包的配置是从rte_eth_dev_configure()开始的,这里根据参数会配置队列的个数,以及接口的配置信息,如队列的使用模式,多队列的方式等。

接收队列的初始化是从rte_eth_rx_queue_setup()开始的,这里的参数需要指定要初始化的port_id,queue_id,以及描述符的个数,还可以指定接收的配置,如释放和回写的阈值等。

前面会先进行各种检查,如初始化的队列号是否合法有效,设备如果已经启动,就不能继续初始化了。检查函数指针是否有效等。检查mbuf的数据大小是否满足默认的设备信息里的配置。最后会调用到队列的setup函数做最后的初始化。对于ixgbe设备,rx_queue_setup就是函数ixgbe_dev_rx_queue_setup(),依然是先检查,检查描述符的数量最大不能大于IXGBE_MAX_RING_DESC个,最小不能小于IXGBE_MIN_RING_DESC个。接下来的都是重点咯:

<1>.分配队列结构体,并填充结构

//填充结构体的所属内存池,描述符个数,队列号,队列所属接口号等成员。
rxq = rte_zmalloc_socket("ethdev RX queue", sizeof(struct ixgbe_rx_queue),
                 RTE_CACHE_LINE_SIZE, socket_id);

<2>.分配描述符队列的空间,按照最大的描述符个数进行分配

rz = rte_eth_dma_zone_reserve(dev, "rx_ring", queue_idx,
                      RX_RING_SZ, IXGBE_ALIGN, socket_id);

//接着获取描述符队列的头和尾寄存器的地址,在收发包后,软件要对这个寄存器进行处理。
rxq->rdt_reg_addr =
            IXGBE_PCI_REG_ADDR(hw, IXGBE_RDT(rxq->reg_idx));
        rxq->rdh_reg_addr =
            IXGBE_PCI_REG_ADDR(hw, IXGBE_RDH(rxq->reg_idx));

//设置队列的接收描述符ring的物理地址和虚拟地址。
rxq->rx_ring_phys_addr = rte_mem_phy2mch(rz->memseg_id, rz->phys_addr);
rxq->rx_ring = (union ixgbe_adv_rx_desc *) rz->addr;

<3>分配sw_ring,这个ring中存储的对象是struct ixgbe_rx_entry,其实里面就是数据包mbuf的指针。

rxq->sw_ring = rte_zmalloc_socket("rxq->sw_ring",
                      sizeof(struct ixgbe_rx_entry) * len,
                      RTE_CACHE_LINE_SIZE, socket_id);

以上三步做完以后,新分配的队列结构体重要的部分就已经填充完了,下面需要重置一下其他成员

ixgbe_reset_rx_queue()

// 先把分配的描述符队列清空,其实清空在分配的时候就已经做了,没必要重复做
for (i = 0; i < len; i++) {
        rxq->rx_ring[i] = zeroed_desc;
    }

//然后初始化队列中一下其他成员
rxq->rx_nb_avail = 0;
rxq->rx_next_avail = 0;
rxq->rx_free_trigger = (uint16_t)(rxq->rx_free_thresh - 1);
rxq->rx_tail = 0;
rxq->nb_rx_hold = 0;
rxq->pkt_first_seg = NULL;
rxq->pkt_last_seg = NULL;

这样,接收队列就初始化完了。发送队列的初始化在前面的检查基本和接收队列一样,只有些许区别在于setup环节。经过上面的队列初始化,队列的ring和sw_ring都分配了,但是发现木有,DMA仍然还不知道要把数据包拷贝到哪里,我们说过,DPDK是零拷贝的,那么我们分配的mempool中的对象怎么和队列以及驱动联系起来呢?接下来就是最精彩的时刻了—-建立mempool、queue、DMA、ring之间的关系。

设备的启动是从rte_eth_dev_start()中开始的:diag = (*dev->dev_ops->dev_start)(dev);

进而,找到设备启动的真正启动函数:ixgbe_dev_start():先检查设备的链路设置,暂时不支持半双工和固定速率的模式。看来是暂时只有自适应模式咯。然后把中断禁掉,同时,停掉适配器ixgbe_stop_adapter(hw);在其中,就是调用了ixgbe_stop_adapter_generic(),主要的工作就是停止发送和接收单元。这是直接写寄存器来完成的。然后重启硬件,ixgbe_pf_reset_hw()->ixgbe_reset_hw()->ixgbe_reset_hw_82599(),最终都是设置寄存器,这里就不细究了。之后,就启动了硬件。

再然后是初始化接收单元:ixgbe_dev_rx_init():在这个函数中,主要就是设置各类寄存器,比如配置CRC校验,如果支持巨帧,配置对应的寄存器。还有如果配置了loopback模式,也要配置寄存器。

接下来最重要的就是为每个队列设置DMA寄存器,标识每个队列的描述符ring的地址,长度,头,尾等。

bus_addr = rxq->rx_ring_phys_addr;
IXGBE_WRITE_REG(hw, IXGBE_RDBAL(rxq->reg_idx),
        (uint32_t)(bus_addr & 0x00000000ffffffffULL));
IXGBE_WRITE_REG(hw, IXGBE_RDBAH(rxq->reg_idx),
        (uint32_t)(bus_addr >> 32));
IXGBE_WRITE_REG(hw, IXGBE_RDLEN(rxq->reg_idx),
        rxq->nb_rx_desc * sizeof(union ixgbe_adv_rx_desc));
IXGBE_WRITE_REG(hw, IXGBE_RDH(rxq->reg_idx), 0);
IXGBE_WRITE_REG(hw, IXGBE_RDT(rxq->reg_idx), 0);

这里可以看到把描述符ring的物理地址写入了寄存器,还写入了描述符ring的长度。之后还计算了数据包数据的长度,写入到寄存器中.然后对于网卡的多队列设置,也进行了配置。这样,接收单元的初始化就完成了。

接下来再初始化发送单元:ixgbe_dev_tx_init(),发送单元的的初始化和接收单元的初始化基本操作是一样的,都是填充寄存器的值,重点是设置描述符队列的基地址和长度。

bus_addr = txq->tx_ring_phys_addr;
IXGBE_WRITE_REG(hw, IXGBE_TDBAL(txq->reg_idx),
        (uint32_t)(bus_addr & 0x00000000ffffffffULL));
IXGBE_WRITE_REG(hw, IXGBE_TDBAH(txq->reg_idx),
        (uint32_t)(bus_addr >> 32));
IXGBE_WRITE_REG(hw, IXGBE_TDLEN(txq->reg_idx),
        txq->nb_tx_desc * sizeof(union ixgbe_adv_tx_desc));
/* Setup the HW Tx Head and TX Tail descriptor pointers */
IXGBE_WRITE_REG(hw, IXGBE_TDH(txq->reg_idx), 0);
IXGBE_WRITE_REG(hw, IXGBE_TDT(txq->reg_idx), 0);

收发单元初始化完毕后,就可以启动设备的收发单元咯:ixgbe_dev_rxtx_start()
先对每个发送队列的threshold相关寄存器进行设置,这是发送时的阈值参数,这个东西在发送部分有说明。然后就是依次启动每个接收队列啦:ixgbe_dev_rx_queue_start()

//先检查,如果要启动的队列是合法的,那么就为这个接收队列分配存放mbuf的实际空间
if (ixgbe_alloc_rx_queue_mbufs(rxq) != 0) 
{
    PMD_INIT_LOG(ERR, "Could not alloc mbuf for queue:%d",
             rx_queue_id);
    return -1;
}

在这里,你将找到终极答案–mempool、ring、queue ring、queue sw_ring的关系!

static int __attribute__((cold))
ixgbe_alloc_rx_queue_mbufs(struct ixgbe_rx_queue *rxq)
{
    struct ixgbe_rx_entry *rxe = rxq->sw_ring;
    uint64_t dma_addr;
    unsigned int i;

    /* Initialize software ring entries */
    for (i = 0; i < rxq->nb_rx_desc; i++) {
        volatile union ixgbe_adv_rx_desc *rxd;
        struct rte_mbuf *mbuf = rte_mbuf_raw_alloc(rxq->mb_pool);

        if (mbuf == NULL) {
            PMD_INIT_LOG(ERR, "RX mbuf alloc failed queue_id=%u",
                     (unsigned) rxq->queue_id);
            return -ENOMEM;
        }

        rte_mbuf_refcnt_set(mbuf, 1);
        mbuf->next = NULL;
        mbuf->data_off = RTE_PKTMBUF_HEADROOM;
        mbuf->nb_segs = 1;
        mbuf->port = rxq->port_id;

        dma_addr =
            rte_cpu_to_le_64(rte_mbuf_data_dma_addr_default(mbuf));
        rxd = &rxq->rx_ring[i];
        rxd->read.hdr_addr = 0;
        rxd->read.pkt_addr = dma_addr;
        rxe[i].mbuf = mbuf;
    }

    return 0;
}

我们看到,从队列所属内存池的ring中循环取出了nb_rx_desc个mbuf指针,也就是为了填充rxq->sw_ring。每个指针都指向内存池里的一个数据包空间。

然后就先填充了新分配的mbuf结构,最最重要的是填充计算了dma_addr。然后初始化queue ring,即rxd的信息,标明了驱动把数据包放在dma_addr处。最后一句,把分配的mbuf 放入 queue 的sw_ring中,这样,驱动收过来的包,就直接放在了sw_ring中。

以上最重要的工作就完成了,下面就可以使能DMA引擎啦,准备收包

hw->mac.ops.enable_rx_dma(hw, rxctrl);

然后再设置一下队列ring的头尾寄存器的值,这也是很重要的一点!头设置为0,尾设置为描述符个数减1,就是描述符填满整个ring。

IXGBE_WRITE_REG(hw, IXGBE_RDH(rxq->reg_idx), 0);
IXGBE_WRITE_REG(hw, IXGBE_RDT(rxq->reg_idx), rxq->nb_rx_desc - 1);

随着这步做完,剩余的就没有什么重要的事啦,就此打住!

发送队列的启动比接收队列的启动要简单,只是配置了txdctl寄存器,延时等待TX使能完成,最后,设置队列的头和尾位置都为0。

txdctl = IXGBE_READ_REG(hw, IXGBE_TXDCTL(txq->reg_idx));
txdctl |= IXGBE_TXDCTL_ENABLE;
IXGBE_WRITE_REG(hw, IXGBE_TXDCTL(txq->reg_idx), txdctl);

IXGBE_WRITE_REG(hw, IXGBE_TDH(txq->reg_idx), 0);
IXGBE_WRITE_REG(hw, IXGBE_TDT(txq->reg_idx), 0);

发送队列就启动完成了。

2 主循环部分

/*
 * The lcore main. This is the main thread that does the work, reading from
 * an input port and writing to an output port.
 */
/*
//业务函数入口点
//__attribute__((noreturn))标明函数无返回值
*/
/*
//1、检测CPU与网卡是否匹配
//2、检查收发网卡是否在同一 NUMA 节点
//3、数据接收、发送的while(1)
*/
static __attribute__((noreturn)) void
lcore_main(void)
{
        const uint8_t nb_ports = rte_eth_dev_count();   //网口总数
        uint8_t port;                                   //临时变量,网口号

        /*
         * Check that the port is on the same NUMA node as the polling thread
         * for best performance.
         * 为了更好的性能,检查收发网卡是否在同一 NUMA 节点
         */
        for (port = 0; port < nb_ports; port++)                 //遍历所有网口
                if (rte_eth_dev_socket_id(port) > 0 &&          //检测的IF语句
                                rte_eth_dev_socket_id(port) !=
                                                (int)rte_socket_id())
                        printf("WARNING, port %u is on remote NUMA node to "
                                        "polling thread.\n\tPerformance will "
                                        "not be optimal.\n", port);

        printf("\nCore %u forwarding packets. [Ctrl+C to quit]\n",
                        rte_lcore_id());

        /* Run until the application is quit or killed. */
        /*运行 直到 应用程序 退出 或 被kill*/
        for (;;) {
                /*
                 * Receive packets on a port and forward them on the paired
                 * port. The mapping is 0 -> 1, 1 -> 0, 2 -> 3, 3 -> 2, etc.
                 * 从Eth接收数据包 ,并发送到 ETH上。
                 * 发送顺序为:0 的接收 到 1的 发送,
                 *              1 的接收 到 0的 发送
                 * 每两个端口为一对
                 */
                for (port = 0; port < nb_ports; port++) {       //遍历所有网口

                        /* Get burst of RX packets, from first port of pair. */
                        struct rte_mbuf *bufs[BURST_SIZE];

                        //收包,接收到nb_tx个包
                        //端口,队列,缓冲区,队列大小
                        const uint16_t nb_rx = rte_eth_rx_burst(port, 0,
                                        bufs, BURST_SIZE);

                        if (unlikely(nb_rx == 0))
                                continue; // 没有读到包就继续下一个 port

                        /* Send burst of TX packets, to second port of pair. */
                        //发包,发送nb_rx个包
                        //端口,队列,发送缓冲区,发包个数
                        const uint16_t nb_tx = rte_eth_tx_burst(port ^ 1, 0,
                                        bufs, nb_rx);

                        //*****注意:以上流程为:从x收到的包,发送到x^1口
                        //其中,0^1 = 1, 1^1 = 0
                        //此运算可以达到测试要求的收、发包逻辑

                        /* Free any unsent packets. */
                        //释放不发送的数据包
                        //1、收到nb_rx个包,转发了nb_tx个,剩余nb_rx-nb_tx个
                        //2、把剩余的包释放掉
                        if (unlikely(nb_tx < nb_rx)) {
                                uint16_t buf;
                                for (buf = nb_tx; buf < nb_rx; buf++)
                                        rte_pktmbuf_free(bufs[buf]);    //释放包
                        }
                }
        }
}

注意:

数据包的获取是指驱动把数据包放入了内存中,上层应用从队列中去取出这些数据包;发送是指把要发送的数据包放入到发送队列中,为实际发送做准备。

业务层面获取数据包是从rte_eth_rx_burst()开始的

static inline uint16_t  
rte_eth_rx_burst(uint8_t port_id, uint16_t queue_id,  
         struct rte_mbuf **rx_pkts, uint16_t nb_pkts)  
{  
    struct rte_eth_dev *dev;  

    dev = &rte_eth_devices[port_id];  
    return (*dev->rx_pkt_burst)(dev->data->rx_queues[queue_id], rx_pkts, nb_pkts);  
}  

rte_eth_tx_burst() 发送成功的话会自动释放 mbuf,对于没成功的,需要手动进行释放

完整代码:

#include <stdint.h>
#include <inttypes.h>
#include <rte_eal.h>
#include <rte_ethdev.h>
#include <rte_cycles.h>
#include <rte_lcore.h>
#include <rte_mbuf.h>

#define RX_RING_SIZE 128        //接收环大小
#define TX_RING_SIZE 512        //发送环大小

#define NUM_MBUFS 8191
#define MBUF_CACHE_SIZE 250
#define BURST_SIZE 32

static const struct rte_eth_conf port_conf_default = {
        .rxmode = { .max_rx_pkt_len = ETHER_MAX_LEN }
};

/* basicfwd.c: Basic DPDK skeleton forwarding example. */

/*
 * Initializes a given port using global settings and with the RX buffers
 * coming from the mbuf_pool passed as a parameter.
 */

/*
        指定网口的队列数,本列中指定的是但队列
        在tx、rx两个方向上,设置缓冲区
*/
static inline int
port_init(uint8_t port, struct rte_mempool *mbuf_pool)
{
        struct rte_eth_conf port_conf = port_conf_default;      //网口配置=默认的网口配置
        const uint16_t rx_rings = 1, tx_rings = 1;              //网口tx、rx队列的个数
        int retval;                     //临时变量,返回值
        uint16_t q;                     //临时变量,队列号

        if (port >= rte_eth_dev_count())
                return -1;

        /* Configure the Ethernet device. */
        //配置以太网口设备
        //网口号、发送队列个数、接收队列个数、网口的配置
        retval = rte_eth_dev_configure(port, rx_rings, tx_rings, &port_conf);   //设置网卡设备
        if (retval != 0)
                return retval;

        /* Allocate and set up 1 RX queue per Ethernet port. */
        //RX队列初始化
        for (q = 0; q < rx_rings; q++) {        //遍历指定网口的所有rx队列

                //申请并设置一个收包队列
                //指定网口,指定队列,指定队列RING的大小,指定SOCKET_ID号,指定队列选项(默认NULL),指定内存池
                //其中rte_eth_dev_socket_id(port)不理解,通过port号来获取dev_socket_id??
                //dev_socket_id作用未知,有待研究
                retval = rte_eth_rx_queue_setup(port, q, RX_RING_SIZE,
                                rte_eth_dev_socket_id(port), NULL, mbuf_pool);
                if (retval < 0)
                        return retval;
        }

        //TX队列初始化
        /* Allocate and set up 1 TX queue per Ethernet port. */
        for (q = 0; q < tx_rings; q++) {        //遍历指定网口的所有tx队列

                //申请并设置一个发包队列
                //指定网口,指定队列,指定队列RING大小,指定SOCKET_ID号,指定选项(NULL为默认)
                //??TX为何没指定内存池,此特征有待研究
                retval = rte_eth_tx_queue_setup(port, q, TX_RING_SIZE,  //申请并设置一个发包队列
                                rte_eth_dev_socket_id(port), NULL);
                if (retval < 0)
                        return retval;
        }

        /* Start the Ethernet port. */
        retval = rte_eth_dev_start(port);       //启动网卡
        if (retval < 0)
                return retval;

        /* Display the port MAC address. */
        struct ether_addr addr;
        rte_eth_macaddr_get(port, &addr);       //获取网卡的MAC地址,并打印
        printf("Port %u MAC: %02" PRIx8 " %02" PRIx8 " %02" PRIx8
                           " %02" PRIx8 " %02" PRIx8 " %02" PRIx8 "\n",
                        (unsigned)port,
                        addr.addr_bytes[0], addr.addr_bytes[1],
                        addr.addr_bytes[2], addr.addr_bytes[3],
                        addr.addr_bytes[4], addr.addr_bytes[5]);

        /* Enable RX in promiscuous mode for the Ethernet device. */
        rte_eth_promiscuous_enable(port);       //设置网卡为混杂模式

        return 0;
}

/*
 * The lcore main. This is the main thread that does the work, reading from
 * an input port and writing to an output port.
 */
/*
//业务函数入口点
//__attribute__((noreturn))用法
//标明函数无返回值
//用来修饰lcore_main函数,标明lcore_main无返回值
*/
/*
//1、检测CPU与网卡是否匹配
//2、建议使用本地CPU就近网卡???,不理解
//3、数据接收、发送的while(1)
*/
static __attribute__((noreturn)) void
lcore_main(void)
{
        const uint8_t nb_ports = rte_eth_dev_count();   //网口总数
        uint8_t port;                                   //临时变量,网口号

        /*
         * Check that the port is on the same NUMA node as the polling thread
         * for best performance.
         * 检测CPU和网口是否匹配
         * ????,不理解,姑且看作是一个检测机制
         */
        for (port = 0; port < nb_ports; port++)                 //遍历所有网口
                if (rte_eth_dev_socket_id(port) > 0 &&          //检测的IF语句
                                rte_eth_dev_socket_id(port) !=
                                                (int)rte_socket_id())
                        printf("WARNING, port %u is on remote NUMA node to "
                                        "polling thread.\n\tPerformance will "
                                        "not be optimal.\n", port);

        printf("\nCore %u forwarding packets. [Ctrl+C to quit]\n",
                        rte_lcore_id());

        /* Run until the application is quit or killed. */
        /*运行 直到 应用程序 推出 或 被kill*/
        for (;;) {
                /*
                 * Receive packets on a port and forward them on the paired
                 * port. The mapping is 0 -> 1, 1 -> 0, 2 -> 3, 3 -> 2, etc.
                 * 从Eth接收数据包 ,并发送到 ETH上。
                 * 发送顺序为:0 的接收 到 1的 发送,
                 *              1 的接收 到 0的 发送
                 * 每两个端口为一对
                 */
                for (port = 0; port < nb_ports; port++) {       //遍历所有网口

                        /* Get burst of RX packets, from first port of pair. */
                        struct rte_mbuf *bufs[BURST_SIZE];

                        //收包,接收到nb_tx个包
                        //端口,队列,收包队列,队列大小
                        const uint16_t nb_rx = rte_eth_rx_burst(port, 0,
                                        bufs, BURST_SIZE);

                        if (unlikely(nb_rx == 0))
                                continue;

                        /* Send burst of TX packets, to second port of pair. */
                        //发包,发送nb_rx个包
                        //端口,队列,发送缓冲区,发包个数
                        const uint16_t nb_tx = rte_eth_tx_burst(port ^ 1, 0,
                                        bufs, nb_rx);

                        //*****注意:以上流程为:从x收到的包,发送到x^1口
                        //其中,0^1 = 1, 1^1 = 0
                        //此运算可以达到测试要求的收、发包逻辑

                        /* Free any unsent packets. */
                        //释放不发送的数据包
                        //1、收到nb_rx个包,转发了nb_tx个,剩余nb_rx-nb_tx个
                        //2、把剩余的包释放掉
                        if (unlikely(nb_tx < nb_rx)) {
                                uint16_t buf;
                                for (buf = nb_tx; buf < nb_rx; buf++)
                                        rte_pktmbuf_free(bufs[buf]);    //释放包
                        }
                }
        }
}

/*
 * The main function, which does initialization and calls the per-lcore
 * functions.
 */
int
main(int argc, char *argv[])
{
        struct rte_mempool *mbuf_pool;  //指向内存池结构的指针变量
        unsigned nb_ports;              //网口个数
        uint8_t portid;                 //网口号,临时的标记变量

        /* Initialize the Environment Abstraction Layer (EAL). */
        int ret = rte_eal_init(argc, argv);     //初始化
        if (ret < 0)
                rte_exit(EXIT_FAILURE, "Error with EAL initialization\n");

        argc -= ret;                    //??本操作莫名其妙,似乎一点用处也没有
        argv += ret;                    //??本操作莫名其妙,似乎一点用处也没有

        /* Check that there is an even number of ports to send/receive on. */
        nb_ports = rte_eth_dev_count(); //获取当前有效网口的个数
        if (nb_ports < 2 || (nb_ports & 1))     //如果有效网口数小于2或有效网口数为奇数0,则出错
                rte_exit(EXIT_FAILURE, "Error: number of ports must be even\n");

        /* Creates a new mempool in memory to hold the mbufs. */
        /*创建一个新的内存池*/
        //"MBUF_POOL"内存池名, NUM_MBUFS * nb_ports网口数,
        //此函数为rte_mempoll_create()的封装
        mbuf_pool = rte_pktmbuf_pool_create("MBUF_POOL", NUM_MBUFS * nb_ports,
                MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
        if (mbuf_pool == NULL)
                rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n");

        //初始化所有的网口
        /* Initialize all ports. */
        for (portid = 0; portid < nb_ports; portid++)   //遍历所有网口
                if (port_init(portid, mbuf_pool) != 0)  //初始化指定网口,需要网口号和内存池,此函数为自定义函数,看前面定义
                        rte_exit(EXIT_FAILURE, "Cannot init port %"PRIu8 "\n",
                                        portid);

        //如果逻辑核心总数>1 ,打印警告信息,此程序用不上多个逻辑核心
        //逻辑核心可以通过传递参数 -c 逻辑核掩码来设置
        if (rte_lcore_count() > 1)
                printf("\nWARNING: Too many lcores enabled. Only 1 used.\n");

        /* Call lcore_main on the master core only. */
        //执行主函数
        lcore_main();

        return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_15437629/article/details/79633720