dpdk-packet_ordering例程分析
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]