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;
}
/*
* 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);
发送队列就启动完成了。
/*
* 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
#include
#include
#include
#include
#include
#include
#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;
}