DPDK例程分析

本文详细分析了DPDK的几个关键例程,包括Hello World例程,基础转发例程,RX/TX回调例程,数据流分类例程以及定时器例程。介绍了从初始化EAL,创建内存池,端口配置到实现数据包转发,流分类,以及使用定时器的详细过程,深入理解DPDK的应用机制。
摘要由CSDN通过智能技术生成

1、Hello World例程

先搬一段代码,后面再逐步分析。

/* SPDX-License-Identifier: BSD-3-Clause
 * Copyright(c) 2010-2014 Intel Corporation
 */
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <sys/queue.h>
#include <rte_memory.h>
#include <rte_launch.h>
#include <rte_eal.h>
#include <rte_per_lcore.h>
#include <rte_lcore.h>
#include <rte_debug.h>
static int
lcore_hello(__rte_unused void *arg)
{
   
    unsigned lcore_id;
    lcore_id = rte_lcore_id();
    printf("hello from core %u\n", lcore_id);
    return 0;
}
int
main(int argc, char **argv)
{
   
    int ret;
    unsigned lcore_id;
    ret = rte_eal_init(argc, argv);
    if (ret < 0)
        rte_panic("Cannot init EAL\n");
    /* call lcore_hello() on every slave lcore */
    RTE_LCORE_FOREACH_SLAVE(lcore_id) {
   
        rte_eal_remote_launch(lcore_hello, NULL, lcore_id);
    }
    /* call it on master lcore too */
    lcore_hello(NULL);
    rte_eal_mp_wait_lcore();
    return 0;
}

首先,调用rte_eal_init()函数来初始化环境抽象层(EAL),此函数只能在主lcore中调用,并会将从lcore置位等待状态。当EAL层初始化完毕,程序准备好启动lcore上的功能。本例中定义lcore_hello()函数来打印当前icore的ID,其中rte_lcore_id()函数用来获取执行单元的线程ID。
之后,通过RTE_LCORE_FOREACH_SLAVE()宏来遍历除了主lcore以外的所有运行中的lcore。
最后,通过rte_eal_remote_launch()函数在lcore中启动传入的函数。

2、基础转发例程

介绍一下转发程序框架。

首先,初始化环境抽象层(EAL)。

int ret = rte_eal_init(argc, argv);
if (ret < 0)
    rte_exit(EXIT_FAILURE, "Error with EAL initialization\n");

之后,调用rte_mempool_create(…)还申请内存池,以用来存放mbuf供应用使用。

mbuf_pool = rte_mempool_create("MBUF_POOL",
                               NUM_MBUFS * nb_ports,
                               MBUF_SIZE,
                               MBUF_CACHE_SIZE,
                               sizeof(struct rte_pktmbuf_pool_private),
                               rte_pktmbuf_pool_init, NULL,
                               rte_pktmbuf_init,      NULL,
                               rte_socket_id(),
                               0);

rte_mempool_create(…)这个函数看起来实在是让人头大,因为它的参数实在是太多了,其函数原型如下:

struct rte_mempool* rte_mempool_create	(	const char * 	name,
unsigned 	n,
unsigned 	elt_size,
unsigned 	cache_size,
unsigned 	private_data_size,
rte_mempool_ctor_t * 	mp_init,
void * 	mp_init_arg,
rte_mempool_obj_cb_t * 	obj_init,
void * 	obj_init_arg,
int 	socket_id,
unsigned 	flags 
)	

下面来逐一解释各个参数的含义:

  • name:mempool的名字。

  • n:mempool中的元素的个数。n = (2^q - 1)比较适宜。

  • elt_size:代表每个元素的大小。

  • cache_size:如果cache_size非零,rte_mempool库将会通过维护单逻辑核的目标缓存来尝试限制通用无锁池的访问。这个参数必须小于等于CONFIG_RTE_MEMPOOL_CACHE_MAX_SIZE 和 n / 1.5。建议的cache_size为“n取模cache_size == 0”:如果不这样的话,一些元素会一直留在pool中并且永远不会被用到。访问单逻辑核表肯定比访问多生产者/消费者池要快。cache_size设置成0的时候,缓存被关闭;可以用来避免在缓存中丢失对象。

  • private_data_size:添加在mempool结构后面的私有数据的大小。

  • mp_init:在对象初始化之前,用来初始化pool的函数的指针。如果需要,用户可以用来初始化私有数据。不需要的时候,可以将其设置为NULL。

  • mp_init_arg:mempool构造函数的数据的指针。

  • obj_init:每个对象初始化pool的函数指针。用户可以用来设置对象中的元数据。不用的时候可以将其设置为NULL。obj_init()函数用mempool指针、init_arg和对象数作为参数。

  • obj_init_arg:对象构造函数的参数的指针。

  • socket_id:在NUMA模式下的socket的标识符。如果保留区域没有NUMA约束,socket_id可以设置为SOCKET_ID_ANY 。

  • flags:flags可以是如下flag的OR关系:

    • MEMPOOL_F_NO_SPREAD:默认情况下,对象地址被分配在RAM通道之间:pool的申请器将会在对象之间添加填充,实现基于硬件配置。如果设置了这个flag,申请器将会将他们对齐到缓存行。(内存对齐)
    • MEMPOOL_F_NO_CACHE_ALIGN:不内存对齐。
    • MEMPOOL_F_SP_PUT:设置的时候,调用 rte_mempool_put() 或rte_mempool_put_bulk()的时候,是单生产者模式,否则,是多生产者模式。
    • MEMPOOL_F_SC_GET:设置的时候,调用 rte_mempool_get() 或rte_mempool_get_bulk()的时候,是单消费者模式,否则,是多消费者模式。
    • MEMPOOL_F_NO_IOVA_CONTIG:设置的时候,分配的对象在IO内存中不一定是连续的。
      函数返回rte_mempool结构,其定义如下:
struct rte_mempool {
   
     /*
      * Note: this field kept the RTE_MEMZONE_NAMESIZE size due to ABI
      * compatibility requirements, it could be changed to
      * RTE_MEMPOOL_NAMESIZE next time the ABI changes
      */
     char name[RTE_MEMZONE_NAMESIZE]; 
     RTE_STD_C11
     union {
   
         void *pool_data;         
         uint64_t pool_id;        
     };
     void *pool_config;               
     const struct rte_memzone *mz;    
     unsigned int flags;              
     int socket_id;                   
     uint32_t size;                   
     uint32_t cache_size;
     uint32_t elt_size;               
     uint32_t header_size;            
     uint32_t trailer_size;           
     unsigned private_data_size;      
     int32_t ops_index;
 
     struct rte_mempool_cache *local_cache; 
     uint32_t populated_size;         
     struct rte_mempool_objhdr_list elt_list; 
     uint32_t nb_mem_chunks;          
     struct rte_mempool_memhdr_list mem_list; 
 #ifdef RTE_LIBRTE_MEMPOOL_DEBUG
 
     struct rte_mempool_debug_stats stats[RTE_MAX_LCORE];
 #endif
 }  __rte_cache_aligned;

随后,通用用户定义的prot_init()函数来进行端口的初始化:

RTE_ETH_FOREACH_DEV(portid) {
   
    if (port_init(portid, mbuf_pool) != 0) {
   
        rte_exit(EXIT_FAILURE,
                 "Cannot init port %" PRIu8 "\n", portid);
    }
}

初始化完毕之后,lcore_main()就可以在逻辑核上面执行。下面分别对port_init()与lcore_main()进行说明。
port_init()函数如下:

static inline int
port_init(uint16_t port, struct rte_mempool *mbuf_pool)
{
   
    struct rte_eth_conf port_conf =
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值