dpdk-packet_ordering例程分析

该博客详细分析了dpdk-packet_ordering例程,功能是通过DPDK的reorder库对乱序接收的报文进行排序并重新发送,确保数据一致性。程序需要3个核心和偶数个网卡,涉及多个线程处理接收、排序和发送任务。文中还讨论了编译运行过程中的问题及解决方案,并深入解析了main.c源码中的关键函数和数据结构。
摘要由CSDN通过智能技术生成

packet_ordering功能

调用DPDK的reorder库,将乱序接收到的报文变成顺序再发送出去,保证和进入负载均衡前的数据一致。
该例程需要三个线程(至少三个核心)且网卡数量应为1/偶数才能完成这一功能。功能介绍参照官方文档
三个线程为rx_thread,worker_thread,tx_thread/send_thread
用到了两个环形队列rx_to_workers,workers_to_tx

线程 功能
rx_thread 遍历所有的port,将用户要求的port接收到的数据包进行排序并设置其seq,放入rx_to_workers环形队列。
worker_thread rx_to_workers环形队列中的数据包出队,放入workers_to_tx中。
send_thread 调用reorder库,将workers_to_tx出队的数据包进行顺序排列并将port^=1由配对的另外一个网卡发送出去。
tx_thread send_thread不同的是,它不对数据包进行重新排列,直接发送

packet_ordering编译运行及结果

编译须使用命令:

cd examples/packet_ordering
make

编译成功后,运行该REORDERAPP:

cd build
./packet_ordering -c 7 -- -p 3

其中c参数的二进制位为0111,代表三个核运行该程序。
在程序自定义的参数与系统EAL层参数间用两个杠分隔开。
p参数表示程序自定义的PORTMASK,二进制位为0011,也就是将网卡0和网卡1的接收和发送互换,即将网卡0接收到的数据包经过顺序调整后由网卡1发送,将网卡1接收到的数据包经过顺序调整后由网卡0发送。
在运行过程中曾遇到以下问题:

1.Cannot allocate memory

EAL: Error - exiting with code: 1
  Cause: Cannot allocate memory

Solution:
重新设置hugepage的大小为512,具体的设置方法可以参照这里

2.由于终止时间过早,TX只接收不发

解决上个问题后,终于看到了程序中的:

Initializing port 0... done
Port 0 MAC: 08 00 27 60 56 7d
Initializing port 1... done
Port 1 MAC: 08 00 27 ea 05 4f
REORDERAPP: worker_thread() started on lcore 1
REORDERAPP: send_thread() started on lcore 2
REORDERAPP: rx_thread() started on lcore 0

由于运行时间较短,在ctrl + c终止程序时,程序的输出为:

^CExiting on signal 2

RX thread stats:
 - Pkts rxd:				27
 - Pkts enqd to workers ring:		27

Worker thread stats:
 - Pkts deqd from workers ring:		27
 - Pkts enqd to tx ring:		27
 - Pkts enq to tx failed:		0

TX stats:
 - Pkts deqd from tx ring:		27
 - Ro Pkts transmitted:			0
 - Ro Pkts tx failed:			0
 - Pkts transmitted w/o reorder:	0
 - Pkts tx failed w/o reorder:		0

Port 0 stats:
 - Pkts in:   0
 - Pkts out:  0
 - In Errs:   0
 - Out Errs:  0
 - Mbuf Errs: 0

Port 1 stats:
 - Pkts in:   27
 - Pkts out:  0
 - In Errs:   0
 - Out Errs:  0
 - Mbuf Errs: 0

可以看到对于Port 1,Pkts in为27,Pkts out为0,是不对等的。后来查看源码,可知对于正常流程中的数据包,其达到一定数量MAX_PKTS_BURST(在例程中设置的是32),才能将对应的数据包发送。

^CExiting on signal 2

RX thread stats:
 - Pkts rxd:				66
 - Pkts enqd to workers ring:		66

Worker thread stats:
 - Pkts deqd from workers ring:		66
 - Pkts enqd to tx ring:		66
 - Pkts enq to tx failed:		0

TX stats:
 - Pkts deqd from tx ring:		66
 - Ro Pkts transmitted:			64
 - Ro Pkts tx failed:			0
 - Pkts transmitted w/o reorder:	0
 - Pkts tx failed w/o reorder:		0

Port 0 stats:
 - Pkts in:   0
 - Pkts out:  64
 - In Errs:   0
 - Out Errs:  0
 - Mbuf Errs: 0

Port 1 stats:
 - Pkts in:   66
 - Pkts out:  0
 - In Errs:   0
 - Out Errs:  0
 - Mbuf Errs: 0

main.c源码分析

1. 头文件引用及宏定义

#include <signal.h>
#include <getopt.h>

#include <rte_eal.h>
#include <rte_common.h>
#include <rte_errno.h>
#include <rte_ethdev.h>
#include <rte_lcore.h>
#include <rte_malloc.h>
#include <rte_mbuf.h>
#include <rte_mempool.h>
#include <rte_ring.h>
#include <rte_reorder.h>

#define RX_DESC_PER_QUEUE 1024 //每个接收队列的元素个数
#define TX_DESC_PER_QUEUE 1024 //每个发送队列的元素个数

#define MAX_PKTS_BURST 32 //mbuf数组存放的最多数据包个数
#define REORDER_BUFFER_SIZE 8192 //顺序重组的最多数据包个数
#define MBUF_PER_POOL 65535 //?
#define MBUF_POOL_CACHE_SIZE 250 //?

#define RING_SIZE 16384 //环大小
/* Macros for printing using RTE_LOG */
#define RTE_LOGTYPE_REORDERAPP          RTE_LOGTYPE_USER1

2. 全局变量或结构体定义

unsigned int portmask;
unsigned int disable_reorder;
unsigned int insight_worker;
volatile uint8_t quit_signal;
//volatile 易变性,要求编译器不对这个变量进行编译优化
static struct rte_mempool *mbuf_pool;
static struct rte_eth_conf port_conf_default;
struct worker_thread_args {
   //工作线程参数
        struct rte_ring *ring_in;
        struct rte_ring *ring_out;
};

struct send_thread_args {
   
        struct rte_ring *ring_in;
        struct rte_reorder_buffer *buffer;
};
//__rte_cache_aligned 要求cache对齐
volatile struct app_stats {
   
        struct {
   
                uint64_t rx_pkts;
                uint64_t enqueue_pkts;
                uint64_t enqueue_failed_pkts;
        } rx __rte_cache_aligned;
//接收队列
        struct {
   
                uint64_t dequeue_pkts;
                uint64_t enqueue_pkts;
                uint64_t enqueue_failed_pkts;
        } wkr __rte_cache_aligned;

        struct {
   
                uint64_t dequeue_pkts;
                /* Too early pkts transmitted directly w/o reordering */
                uint64_t early_pkts_txtd_woro;
                /* Too early pkts failed from direct transmit */
                uint64_t early_pkts_tx_failed_woro;
                uint64_t ro_tx_pkts;
                uint64_t ro_tx_failed_pkts;
        } tx __rte_cache_aligned;
//传输
} app_stats;
/* per worker lcore stats */
struct wkr_stats_per {
   
                uint64_t deq_pkts;
                uint64_t enq_pkts;
                uint64_t enq_failed_pkts;
} __rte_cache_aligned;

static struct wkr_stats_per wkr_stats[RTE_MAX_LCORE] = {
    {
   0} };

3. get_last_core_id()函数

获取最大的可用核心标识符。

/**
 * Get the last enabled lcore ID
 *
 * @return
 *   The last enabled lcore ID.
 */
static unsigned int
get_last_lcore_id(void)
{
   
        int i;

        for (i = RTE_MAX_LCORE - 1; i >= 0; i--)
                if (rte_lcore_is_enabled(i))
                        return i;
        return 0;
}

4.get_previous_lcore_id(id) 函数

获取核心标识符id之前的一个最近的核心标识符。

/**
 * Get the previous enabled lcore ID
 * @param id
 *  The current lcore ID
 * @return
 *   The previous enabled lcore ID or the current lcore
 *   ID if it is the first available core.
 */
static unsigned int
get_previous_lcore_id(unsigned int id)
{
   
        int i;

        for (i = id - 1; i >= 0; i--)
                if (rte_lcore_is_enabled(i))
                        return i;
        return id;
}

5.pktmbuf_free_bulk()函数

用于将所有的mbuf内存空间都释放。

static inline void
pktmbuf_free_bulk(struct rte_mbuf *mbuf_table[], unsigned n)
{
   
        unsigned int i;

        for (i = 0; i < n; i++)
                rte_pktmbuf_free(mbuf_table[i]);
}

6.print_usage()函数

用于打印各个参数的用途。

/* display usage */
//打印用途
static void
print_usage(const char *prgname)
{
   
        printf("%s [EAL options] -- -p PORTMASK\n"
                        "  -p PORTMASK: hexadecimal bitmask of ports to configure\n",
                        prgname);
}

7.语法分析函数

对传入的portmask字符串(端口掩码?)进行语法分析。其中调用的strtoul函数

strtoul()会将参数nptr字符串根据参数base来转换成无符号的长整型数。参数base范围从2至36,或0。参数base代表采用的进制方式,如base值为10则采用10进制,若base值为16则采用16进制数等。当base值为0时会根据情况选择用哪种进制:如果第一个字符是’0’,就判断第二字符如果是‘x’则用16进制,否则用8进制;第一个字符不是‘0’,则用10进制。一开始strtoul()会扫描参数nptr字符串,跳过前面的空格字符串,直到遇上数字或正负符号才开始做转换,再遇到非数字或字符串结束时(’’)结束转换,并将结果返回。若参数endptr不为NULL,则会将遇到不合条件而终止的nptr中的字符指针由endptr返回。
unsigned long int strtoul(const char *str, char **endptr, int base)
参数:

  • str – 要转换为无符号长整数的字符串。
  • endptr – 对类型为 char* 的对象的引用,其值由函数设置为 str 中数值后的下一个字符。
  • base – 基数,必须介于 2 和 36(包含)之间,或者是特殊值 0。
static int
parse_portmask(const char *portmask)
{
   
        unsigned long pm;
        char *end = NULL;
        /* parse hexadecimal string */
        pm = strtoul(portmask, &end, 16);
        //指从portmask中获取16进制的数
        if ((portmask[0] == '\0') || (end == NULL) || (*end != '\0'))
                return -1;
        if (pm == 0)
                return -1;
        return pm;
}

下面是对传入的命令行参数进行语法处理。其中调用了linux解析命令行选项的函数:
int getopt_long(int argc, char * const argv[],const char *optstring, const struct option *longopts,int *longindex);
其中optarg是指向参数的指针,在DPDK中省略了extern,其定义为:

extern char *optarg;  //选项的参数指针
/* Parse the argument given in the command line of the application */
static int
parse_args(int argc, char **argv)
{
   
        int opt;
        int option_index;
        char **argvopt;
        char *prgname = argv[0]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值