DPDK L2fwd 源码阅读

rte_prefetch(报文处理中的指令预取) cache的基本操作单位是cache line,如果一个cache line中包含两个不同的数据,那么如果一个线程对其中一个数据进行写操作,那么这个cache line就需要更新,意味着另一个线程需要重新加载另一个数据。所以两个不同的数据不要放在同一个cache line中,这就需要数据定义的时候声明cache对齐,例如cache line的大小是128位(16字节),定义一个8字节的long,剩下8字节使用无用的数据来填充。但是我们在处理本节点时,可以通过CPU指令将下一个节点推送到Cache里。 阅读详情
/* SPDX-License-Identifier: BSD-3-Clause
 * Copyright(c) 2010-2016 Intel Corporation
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/queue.h>
#include <netinet/in.h>
#include <setjmp.h>
#include <stdarg.h>
#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <signal.h>
#include <stdbool.h>

#include <rte_common.h>
#include <rte_log.h>
#include <rte_malloc.h>
#include <rte_memory.h>
#include <rte_memcpy.h>
#include <rte_eal.h>
#include <rte_launch.h>
#include <rte_atomic.h>
#include <rte_cycles.h>
#include <rte_prefetch.h>
#include <rte_lcore.h>
#include <rte_per_lcore.h>
#include <rte_branch_prediction.h>
#include <rte_interrupts.h>
#include <rte_random.h>
#include <rte_debug.h>
#include <rte_ether.h>
#include <rte_ethdev.h>
#include <rte_mempool.h>
#include <rte_mbuf.h>

static volatile bool force_quit;

/* MAC updating enabled by default */
static int mac_updating = 1;
/* MAC updating,默认开启。若不开启,则是和basicfw一样的模式。开启后,会有如下影响:

The source MAC address is replaced by the TX_PORT MAC address
源MAC地址会改写成发送端口的MAC地址

The destination MAC address is replaced by 02:00:00:00:00:TX_PORT_ID
改写目的MAC地址,改写为 02:00:00:00:00:<发送端口的port id>
*/

#define RTE_LOGTYPE_L2FWD RTE_LOGTYPE_USER1

#define MAX_PKT_BURST 32
#define BURST_TX_DRAIN_US 100 /* TX drain every ~100us */
#define MEMPOOL_CACHE_SIZE 256

/*
 * Configurable number of RX/TX ring descriptors
 */
#define RTE_TEST_RX_DESC_DEFAULT 1024
#define RTE_TEST_TX_DESC_DEFAULT 1024
static uint16_t nb_rxd = RTE_TEST_RX_DESC_DEFAULT;
static uint16_t nb_txd = RTE_TEST_TX_DESC_DEFAULT;

/* ethernet addresses of ports */
static struct ether_addr l2fwd_ports_eth_addr[RTE_MAX_ETHPORTS];

/* mask of enabled ports */
static uint32_t l2fwd_enabled_port_mask = 0;

/* list of enabled ports */
static uint32_t l2fwd_dst_ports[RTE_MAX_ETHPORTS];

static unsigned int l2fwd_rx_queue_per_lcore = 1; // 每个逻辑核最多可以用来处理几个端口/队列(L2fwd 一个端口分配各一个收发队列)


#define MAX_RX_QUEUE_PER_LCORE 16
#define MAX_TX_QUEUE_PER_PORT 16
struct lcore_queue_conf { // 逻辑核上的队列配置
	unsigned n_rx_port; // 该 lcore 上绑定多少个端口,也作为下一个数组的下标(0 ~ n-1)。
	unsigned rx_port_list[MAX_RX_QUEUE_PER_LCORE]; // 存放一系列端口号,绑定哪些端口。
} __rte_cache_aligned;

// 这也就是 poll module driver 思想。绑定 lcore 和 port,特定的 lcore 轮询对应的一个或多个 port

struct lcore_queue_conf lcore_queue_conf[RTE_MAX_LCORE]; // 数组下标是 lcore id

static struct rte_eth_dev_tx_buffer *tx_buffer[RTE_MAX_ETHPORTS]; // 用于缓冲未来要发送的数据包的结构,API rte_eth_tx_buffer 和 rte_eth_tx_buffer_flush使用的结构

static struct rte_eth_conf port_conf = {
	.rxmode = { // RX feature 见 flow_filtering
		.split_hdr_size = 0,
		.ignore_offload_bitfield = 1,
		.offloads = DEV_RX_OFFLOAD_CRC_STRIP,
	},
	.txmode = { // TX feature 
		.mq_mode = ETH_MQ_TX_NONE, // mq_多队列选项,有一些宏来定义用多队列发包的方法
	},
};

struct rte_mempool * l2fwd_pktmbuf_pool = NULL;

/* Per-port statistics struct */
struct l2fwd_port_statistics {
	uint64_t tx; // 发包的数量
	uint64_t rx; // 收包的数量
	uint64_t dropped; // 丢包的数量
} __rte_cache_aligned;
struct l2fwd_port_statistics port_statistics[RTE_MAX_ETHPORTS];

#define MAX_TIMER_PERIOD 86400 /* 1 day max */
/* A tsc-based timer responsible for triggering statistics printout */
// timer 负责每隔一段时间触发打印数据
static uint64_t timer_period = 10; /* default period is 10 seconds */

/* Print out statistics on packets dropped */
static void
print_stats(void)
{
	uint64_t total_packets_dropped, total_packets_tx, total_packets_rx;
	unsigned portid;

	total_packets_dropped = 0;
	total_packets_tx = 0;
	total_packets_rx = 0;

	const char clr[] = { 27, '[', '2', 'J', '\0' };
	const char topLeft[] = { 27, '[', '1', ';', '1', 'H','\0' };

		/* Clear screen and move to top left */
	printf("%s%s", clr, topLeft);

	printf("\nPort statistics ====================================");

	for (portid = 0; portid < RTE_MAX_ETHPORTS; portid++) {
		/* skip disabled ports */
		if ((l2fwd_enabled_port_mask & (1 << portid)) == 0)
			continue;
		printf("\nStatistics for port %u ------------------------------"
			   "\nPackets sent: %24"PRIu64
			   "\nPackets received: %20"PRIu64
			   "\nPackets dropped: %21"PRIu64,
			   portid,
			   port_statistics[portid].tx,
			   port_statistics[portid].rx,
			   port_statistics[portid].dropped);

		total_packets_dropped += port_statistics[portid].dropped;
		total_packets_tx += port_statistics[portid].tx;
		total_packets_rx += port_statistics[portid].rx;
	}
	printf("\nAggregate statistics ==============================="
		   "\nTotal packets sent: %18"PRIu64
		   "\nTotal packets received: %14"PRIu64
		   "\nTotal packets dropped: %15"PRIu64,
		   total_packets_tx,
		   total_packets_rx,
		   total_packets_dropped);
	printf("\n====================================================\n");
}

static void
l2fwd_mac_updating(struct rte_mbuf *m, unsigned dest_portid) // 改写包的 MAC 层信息
{
	struct ether_hdr *eth;
	void *tmp;

	eth = rte_pktmbuf_mtod(m, struct ether_hdr *);

	/* 02:00:00:00:00:xx */
	tmp = &eth->d_addr.addr_bytes[0]; // 改写目的 MAC 地址为 02:00:00:00:00:<发送端口的port id>
	*((uint64_t *)tmp) = 0x000000000002 + ((uint64_t)dest_portid << 40);

	/* src addr */
	// 改写 源 MAC 地址 改写成发送端口的MAC地址
	ether_addr_copy(&l2fwd_ports_eth_addr[dest_portid], &eth->s_addr);
}

static void
l2fwd_simple_forward(struct rte_mbuf *m, unsigned portid)
{
	unsigned dst_port;
	int sent;
	struct rte_eth_dev_tx_buffer *buffer;

	dst_port = l2fwd_dst_ports[portid]; // 与之配对的端口

	if (mac_updating) // 如果开启了 mac updating 模式
		l2fwd_mac_updating(m, dst_port); // 调整 MAC 地址

	buffer = tx_buffer[dst_port]; // 该端口的 tx_buffer
	sent = rte_eth_tx_buffer(dst_port, 0, buffer, m); // 将收到的包缓存在 tx_buffer 里,用于未来的发送。
	// 返回值 如果为0,表示 pkt 已经被缓存
	// 返回值 N>0,表示由于缓冲区被flush导致N个pkt被发送。
	
	if (sent)
		port_statistics[dst_port].tx += sent;
}

/* main processing loop */
static void
l2fwd_main_loop(void)
{
	struct rte_mbuf *pkts_burst[MAX_PKT_BURST];
	struct rte_mbuf *m;
	int sent;
	unsigned lcore_id;
	uint64_t prev_tsc, diff_tsc, cur_tsc, timer_tsc;
	unsigned i, j, portid, nb_rx;
	struct lcore_queue_conf *qconf;
	const uint64_t drain_tsc = (rte_get_tsc_hz() + US_PER_S - 1) / US_PER_S *
			BURST_TX_DRAIN_US;
	struct rte_eth_dev_tx_buffer *buffer;

	prev_tsc = 0;
	timer_tsc = 0;

	lcore_id = rte_lcore_id(); // 获取自己的 lcore id
	qconf = &lcore_queue_conf[lcore_id];

	if (qconf->n_rx_port == 0) { // 因为对每一个 lcore 都执行 main 线程,如果该 lcore 上没有绑定端口,就无事可做。
		RTE_LOG(INFO, L2FWD, "lcore %u has nothing to do\n", lcore_id);
		return;
	}

	RTE_LOG(INFO, L2FWD, "entering main loop on lcore %u\n", lcore_id);

	for (i = 0; i < qconf->n_rx_port; i++) {

		portid = qconf->rx_port_list[i];
		RTE_LOG(INFO, L2FWD, " -- lcoreid=%u portid=%u\n", lcore_id,
			portid); // 显示一下 lcore 和 port 的对应关系

	}

	while (!force_quit) {

		cur_tsc = rte_rdtsc(); // 获取从开机起至当前的时间戳

		/*
		 * TX burst queue drain
		 * 发送逻辑
		 */
		diff_tsc = cur_tsc - prev_tsc;
		if (unlikely(diff_tsc > drain_tsc)) { // 时间到了
			// 如果tx_buffer满,会发送一批 pkt 出去。如果没满,为了保证没有没被发出的 pkt,所以每个一小段时间,也会发送队列中的包
			for (i = 0; i < qconf->n_rx_port; i++) { // 对 lcore 负责的每个端口

				portid = l2fwd_dst_ports[qconf->rx_port_list[i]]; // 与之配对的端口
				buffer = tx_buffer[portid];

				sent = rte_eth_tx_buffer_flush(portid, 0, buffer); // 将 buffer 里的 pkt 全部从 port id 的 0号 Tx queue 发出去
				if (sent) // 返回值是成功发出的 pkt 数量
					port_statistics[portid].tx += sent;

			}

			/* if timer is enabled */
			if (timer_period > 0) {

				/* advance the timer */
				timer_tsc += diff_tsc;

				/* if timer has reached its timeout */
				if (unlikely(timer_tsc >= timer_period)) {

					/* do this only on master core */
					if (lcore_id == rte_get_master_lcore()) { // 如果计时器到了,就打印一下信息。只在主核心打印信息
						print_stats();
						/* reset the timer */
						timer_tsc = 0;
					}
				}
			}

			prev_tsc = cur_tsc;
		}

		/*
		 * Read packet from RX queues
		 * 接收逻辑
		 */
		for (i = 0; i < qconf->n_rx_port; i++) { // 对 lcore 负责的每个端口

			portid = qconf->rx_port_list[i]; // 获取端口号
			nb_rx = rte_eth_rx_burst(portid, 0,
						 pkts_burst, MAX_PKT_BURST); // 收包,收到该端口的 0 号 rx queue

			port_statistics[portid].rx += nb_rx; // 更新端口上的收包计数器

			for (j = 0; j < nb_rx; j++) { // 对每一个包
				m = pkts_burst[j]; // 包的 mbuf 指针
				// Prefetch: 预取一个 cache 行。参数是要取的地址,类型 void *
				// rte_pktmbuf_mtod:返回 mbuf 中 data 的起始地址
				rte_prefetch0(rte_pktmbuf_mtod(m, void *)); 
				l2fwd_simple_forward(m, portid); // 收包后进行 L2fwd !!
			}
		}
	}
}

static int
l2fwd_launch_one_lcore(__attribute__((unused)) void *dummy)
{
	l2fwd_main_loop();
	return 0;
}

/* display usage */
static void
l2fwd_usage(const char *prgname)
{
	printf("%s [EAL options] -- -p PORTMASK [-q NQ]\n"
	       "  -p PORTMASK: hexadecimal bitmask of ports to configure\n"
	       "  -q NQ: number of queue (=ports) per lcore (default is 1)\n"
		   "  -T PERIOD: statistics will be refreshed each PERIOD seconds (0 to disable, 10 default, 86400 maximum)\n"
		   "  --[no-]mac-updating: Enable or disable MAC addresses updating (enabled by default)\n"
		   "      When enabled:\n"
		   "       - The source MAC address is replaced by the TX port MAC address\n"
		   "       - The destination MAC address is replaced by 02:00:00:00:00:TX_PORT_ID\n",
	       prgname);
}

static int
l2fwd_parse_portmask(const char *portmask)
{
	char *end = NULL;
	unsigned long pm;

	/* 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;
}

static unsigned int
l2fwd_parse_nqueue(const char *q_arg)
{
	char *end = NULL;
	unsigned long n;

	/* parse hexadecimal string */
	n = strtoul(q_arg, &end, 10);
	if ((q_arg[0] == '\0') || (end == NULL) || (*end != '\0'))
		return 0;
	if (n == 0)
		return 0;
	if (n >= MAX_RX_QUEUE_PER_LCORE)
		return 0;

	return n;
}

static int
l2fwd_parse_timer_period(const char *q_arg)
{
	char *end = NULL;
	int n;

	/* parse number string */
	n = strtol(q_arg, &end, 10);
	if ((q_arg[0] == '\0') || (end == NULL) || (*end != '\0'))
		return -1;
	if (n >= MAX_TIMER_PERIOD)
		return -1;

	return n;
}

static const char short_options[] =
	"p:"  /* portmask */
	"q:"  /* number of queues */
	"T:"  /* timer period */
	;

#define CMD_LINE_OPT_MAC_UPDATING "mac-updating"
#define CMD_LINE_OPT_NO_MAC_UPDATING "no-mac-updating"

enum {
	/* long options mapped to a short option */

	/* first long only option value must be >= 256, so that we won't
	 * conflict with short options */
	CMD_LINE_OPT_MIN_NUM = 256,
};

static const struct option lgopts[] = {
	{ CMD_LINE_OPT_MAC_UPDATING, no_argument, &mac_updating, 1},
	{ CMD_LINE_OPT_NO_MAC_UPDATING, no_argument, &mac_updating, 0},
	{NULL, 0, 0, 0}
};

/* Parse the argument given in the command line of the application */
static int
l2fwd_parse_args(int argc, char **argv)
{
	int opt, ret, timer_secs;
	char **argvopt;
	int option_index;
	char *prgname = argv[0]; // l2fwd

	argvopt = argv;

	while ((opt = getopt_long(argc, argvopt, short_options,
				  lgopts, &option_index)) != EOF) { // linux 下解析命令行参数的函数。支持由两个横杠开头的长选项。
		// 关于这个函数可以 man getopt_long
		switch (opt) { // 解析成功时返回字符
		/* portmask */
		case 'p': // 端口掩码
			l2fwd_enabled_port_mask = l2fwd_parse_portmask(optarg); // 解析成功时,将字符后面的参数放到 optarg 里
			if (l2fwd_enabled_port_mask == 0) {
				printf("invalid portmask\n");
				l2fwd_usage(prgname);
				return -1;
			}
			break;

		/* nqueue */
		case 'q': // A number of queues (=ports) per lcore (default is 1)
				  // q 后面跟着的数字是每个逻辑核心上要绑定多少个队列(端口)
				  // 例如 -q 4 意味着该应用使用一个 lcore 轮询 4个端口。如果共有16个端口,则只需要4个lcore
			l2fwd_rx_queue_per_lcore = l2fwd_parse_nqueue(optarg);
			if (l2fwd_rx_queue_per_lcore == 0) {
				printf("invalid queue number\n");
				l2fwd_usage(prgname);
				return -1;
			}
			break;

		/* timer period */
		case 'T':
			timer_secs = l2fwd_parse_timer_period(optarg);
			if (timer_secs < 0) {
				printf("invalid timer period\n");
				l2fwd_usage(prgname);
				return -1;
			}
			timer_period = timer_secs;
			break;

		/* long options */
		case 0: // 解析到了长选项 会返回0,长选项形如 --arg=param or --arg param.
			break;

		default:
			l2fwd_usage(prgname);
			return -1;
		}
	}

	if (optind >= 0) // optind 是 argv 中下一个要被处理的参数的 index
		argv[optind-1] = prgname;

	ret = optind-1; 
	optind = 1; /* reset getopt lib */ // 解析完所有的参数要让 optind 重新指向 1
	return ret;
}

/* Check the link status of all ports in up to 9s, and print them finally */
static void
check_all_ports_link_status(uint32_t port_mask)
{
#define CHECK_INTERVAL 100 /* 100ms */
#define MAX_CHECK_TIME 90 /* 9s (90 * 100ms) in total */
	uint16_t portid;
	uint8_t count, all_ports_up, print_flag = 0;
	struct rte_eth_link link;

	printf("\nChecking link status");
	fflush(stdout);
	for (count = 0; count <= MAX_CHECK_TIME; count++) {
		if (force_quit)
			return;
		all_ports_up = 1;
		RTE_ETH_FOREACH_DEV(portid) {
			if (force_quit)
				return;
			if ((port_mask & (1 << portid)) == 0)
				continue;
			memset(&link, 0, sizeof(link));
			rte_eth_link_get_nowait(portid, &link);
			/* print link status if flag set */
			if (print_flag == 1) {
				if (link.link_status)
					printf(
					"Port%d Link Up. Speed %u Mbps - %s\n",
						portid, link.link_speed,
				(link.link_duplex == ETH_LINK_FULL_DUPLEX) ?
					("full-duplex") : ("half-duplex\n"));
				else
					printf("Port %d Link Down\n", portid);
				continue;
			}
			/* clear all_ports_up flag if any link down */
			if (link.link_status == ETH_LINK_DOWN) {
				all_ports_up = 0;
				break;
			}
		}
		/* after finally printing all link status, get out */
		if (print_flag == 1)
			break;

		if (all_ports_up == 0) {
			printf(".");
			fflush(stdout);
			rte_delay_ms(CHECK_INTERVAL);
		}

		/* set the print_flag if all ports up or timeout */
		if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
			print_flag = 1;
			printf("done\n");
		}
	}
}

static void
signal_handler(int signum)
{
	if (signum == SIGINT || signum == SIGTERM) {
		printf("\n\nSignal %d received, preparing to exit...\n",
				signum);
		force_quit = true; //当我们退出是ctrl+c不是直接将进程杀死,而是会将force_quit置为true,让程序自然退出,这样程序就来得及完成最后退出之前的操作。
	}
}

int
main(int argc, char **argv)
{
	struct lcore_queue_conf *qconf;
	int ret;
	uint16_t nb_ports;
	uint16_t nb_ports_available = 0;
	uint16_t portid, last_port;
	unsigned lcore_id, rx_lcore_id;
	unsigned nb_ports_in_mask = 0;
	unsigned int nb_lcores = 0;
	unsigned int nb_mbufs;

	/* init EAL */
	// 解析 EAL 的参数
	ret = rte_eal_init(argc, argv);
	if (ret < 0)
		rte_exit(EXIT_FAILURE, "Invalid EAL arguments\n");
	argc -= ret;
	argv += ret;

	force_quit = false;
	signal(SIGINT, signal_handler);
	signal(SIGTERM, signal_handler);

	/* parse application arguments (after the EAL ones) */
	// 解析 l2fwd 的运行参数
	ret = l2fwd_parse_args(argc, argv);
	if (ret < 0)
		rte_exit(EXIT_FAILURE, "Invalid L2FWD arguments\n");

	printf("MAC updating %s\n", mac_updating ? "enabled" : "disabled"); // 默认开启 mac updating 这一功能。

	/* convert to number of cycles */
	timer_period *= rte_get_timer_hz(); // 获得CPU主频,单位hz (1s多少个cycle),位于rte_cycles.h

	nb_ports = rte_eth_dev_count(); // 网口数量
	if (nb_ports == 0)
		rte_exit(EXIT_FAILURE, "No Ethernet ports - bye\n");

	/* check port mask to possible port mask */
	// 检查掩码和可用网口数量是否有冲突
	if (l2fwd_enabled_port_mask & ~((1 << nb_ports) - 1))
		rte_exit(EXIT_FAILURE, "Invalid portmask; possible (0x%x)\n",
			(1 << nb_ports) - 1);

	/* reset l2fwd_dst_ports */

	for (portid = 0; portid < RTE_MAX_ETHPORTS; portid++)
		l2fwd_dst_ports[portid] = 0; // 先重置这个数组
	last_port = 0; 

	/*
	 * Each logical core is assigned a dedicated TX queue on each port.
	 */
	RTE_ETH_FOREACH_DEV(portid) { // 使用RTE_ETH_FOREACH_DEV()宏来访问所有的 ethdev
		/* skip ports that are not enabled */
		if ((l2fwd_enabled_port_mask & (1 << portid)) == 0)
			continue;

		if (nb_ports_in_mask % 2) {
			l2fwd_dst_ports[portid] = last_port;
			l2fwd_dst_ports[last_port] = portid;
		}
		else
			last_port = portid;

		nb_ports_in_mask++; // 这些逻辑可以实现 basicfwd 那样的一对对端口互相转发。
	}
	if (nb_ports_in_mask % 2) {
		printf("Notice: odd number of ports in portmask.\n");
		l2fwd_dst_ports[last_port] = last_port; // 如果是奇数个端口,会有最后一个端口的 dst_port 是自己
	}

	rx_lcore_id = 0; // 从逻辑核心id 0开始
	qconf = NULL;

	/* Initialize the port/queue configuration of each logical core */
	// 在每一个端口上,配置逻辑核、配置队列。
	RTE_ETH_FOREACH_DEV(portid) {
		/* skip ports that are not enabled */
		if ((l2fwd_enabled_port_mask & (1 << portid)) == 0)
			continue;

		/* get the lcore_id for this port */
		// 为该端口配置一个逻辑核。
		while (rte_lcore_is_enabled(rx_lcore_id) == 0 || 
		       lcore_queue_conf[rx_lcore_id].n_rx_port ==
		       l2fwd_rx_queue_per_lcore) {
			/*从lcore id = 0 开始循环:
			如果:如果该 lcore id 有效(已经被占用),则检查下一个逻辑核。
			如果该 lcore 是空闲的,要检查该 lcore 上绑定了多少个端口,如果到达了最大端口数量限制也会循环。*/
			rx_lcore_id++;

			if (rx_lcore_id >= RTE_MAX_LCORE) // RTE_MAX_LCORE 宏 64
				rte_exit(EXIT_FAILURE, "Not enough cores\n"); // 逻辑核心不足
		}
		// 跳出循环时,rx_lcore_id 变量存储了一个可用的 lcore id,绑定该端口到这个 lcore

		if (qconf != &lcore_queue_conf[rx_lcore_id]) {
			/* Assigned a new logical core in the loop above. */
			qconf = &lcore_queue_conf[rx_lcore_id]; 
			nb_lcores++;
			// qconf 是一个指针,指向当前进行配置的 lcore 的,用于存放配置信息的结构体		
		}
		qconf->rx_port_list[qconf->n_rx_port] = portid;
		qconf->n_rx_port++;
		// 绑定就是在这个核处理的端口列表中加上当前这个端口,然后该核绑定的端口数加 1。
		printf("Lcore %u: RX port %u\n", rx_lcore_id, portid);
	}

	nb_mbufs = RTE_MAX(nb_ports * (nb_rxd + nb_txd + MAX_PKT_BURST +
		nb_lcores * MEMPOOL_CACHE_SIZE), 8192U); 
		// mbuf中的元素个数,取 8192 和 (端口数 * (队列长度 * 2 + 一个 Burst 的 pkt 数量 + 逻辑核数 * cache size)) 两者中较大的一个。

	/* create the mbuf pool */
	// 初始化内存池,用于 rx 队列接收 pkt 用
	l2fwd_pktmbuf_pool = rte_pktmbuf_pool_create("mbuf_pool", nb_mbufs,
		MEMPOOL_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE,
		rte_socket_id());
	if (l2fwd_pktmbuf_pool == NULL)
		rte_exit(EXIT_FAILURE, "Cannot init mbuf pool\n");

	/* Initialise each port */
	// 端口初始化
	RTE_ETH_FOREACH_DEV(portid) {
		struct rte_eth_rxconf rxq_conf; // rx queue 的配置信息
		struct rte_eth_txconf txq_conf; // tx queue 的配置信息
		struct rte_eth_conf local_port_conf = port_conf; // 配置端口时使用的配置信息
		struct rte_eth_dev_info dev_info; // 以太网设备的信息

		/* skip ports that are not enabled */
		if ((l2fwd_enabled_port_mask & (1 << portid)) == 0) {
			printf("Skipping disabled port %u\n", portid);
			continue;
		}
		nb_ports_available++;

		/* init port */
		printf("Initializing port %u... ", portid);
		fflush(stdout); // 清除写缓冲区,强迫未写入磁盘的内容立即写入
		rte_eth_dev_info_get(portid, &dev_info); // 获取以太网设备信息
		if (dev_info.tx_offload_capa & DEV_TX_OFFLOAD_MBUF_FAST_FREE)
			local_port_conf.txmode.offloads |=
				DEV_TX_OFFLOAD_MBUF_FAST_FREE; // mbuf fast free,支持快速发包
		ret = rte_eth_dev_configure(portid, 1, 1, &local_port_conf); // 配置收发队列各 1 条
		/*本程序中,Rx队列只能有一条,确保一个 lcore 负责轮询一个 port
		 Tx 队列则可以根据可用的 lcore 数目更改。*/

		if (ret < 0)
			rte_exit(EXIT_FAILURE, "Cannot configure device: err=%d, port=%u\n",
				  ret, portid);

		ret = rte_eth_dev_adjust_nb_rx_tx_desc(portid, &nb_rxd,
						       &nb_txd);
		if (ret < 0)
			rte_exit(EXIT_FAILURE,
				 "Cannot adjust number of descriptors: err=%d, port=%u\n",
				 ret, portid);

		rte_eth_macaddr_get(portid,&l2fwd_ports_eth_addr[portid]); // 获取设备的MAC地址,写在后一个结构体里

		/* init one RX queue */
		// 配置 rx 队列
		fflush(stdout);
		rxq_conf = dev_info.default_rxconf;
		rxq_conf.offloads = local_port_conf.rxmode.offloads;
		ret = rte_eth_rx_queue_setup(portid, 0, nb_rxd,
					     rte_eth_dev_socket_id(portid),
					     &rxq_conf,
					     l2fwd_pktmbuf_pool);
		if (ret < 0)
			rte_exit(EXIT_FAILURE, "rte_eth_rx_queue_setup:err=%d, port=%u\n",
				  ret, portid);

		/* init one TX queue on each port */
		// 每个 port 配置一条 tx 队列
		fflush(stdout);
		txq_conf = dev_info.default_txconf;
		txq_conf.txq_flags = ETH_TXQ_FLAGS_IGNORE;
		txq_conf.offloads = local_port_conf.txmode.offloads;
		ret = rte_eth_tx_queue_setup(portid, 0, nb_txd,
				rte_eth_dev_socket_id(portid),
				&txq_conf);
		if (ret < 0)
			rte_exit(EXIT_FAILURE, "rte_eth_tx_queue_setup:err=%d, port=%u\n",
				ret, portid);

		/* Initialize TX buffers */
		// 为每个端口的 Tx 分配发送缓冲区
		tx_buffer[portid] = rte_zmalloc_socket("tx_buffer",
				RTE_ETH_TX_BUFFER_SIZE(MAX_PKT_BURST), 0,
				rte_eth_dev_socket_id(portid)); // 为 tx buffer 分配空间。
												// 宏RTE_ETH_TX_BUFFER_SIZE(x) :计算 tx buffer 的 size,参数x是包的个数

		if (tx_buffer[portid] == NULL) 
			rte_exit(EXIT_FAILURE, "Cannot allocate buffer for tx on port %u\n",
					portid);

		rte_eth_tx_buffer_init(tx_buffer[portid], MAX_PKT_BURST); // 初始化 Tx buffer,参数是 buffer 指针和 buffer size。

		/* rte_eth_tx_buffer_set_err_callback() 对于不能被发送的 pkt 配置回调函数。
		在尝试发送一个 tx buffer 的所有 pkt,遇到问题不能全部成功发送,就会触发设置好的回调函数。
		默认行为是丢包。如果要其他的行为(例如重传,计数)则需要额外的代码。也有设置好的API例如rte_eth_count_unsent_packet_callback()等,和本函数中用的也是。
		参数 1. tx_buffer 指针,2.回调函数的指针。3. 回调函数的参数
		*/
		ret = rte_eth_tx_buffer_set_err_callback(tx_buffer[portid], 
				rte_eth_tx_buffer_count_callback, // 丢包,并更新计数器
				&port_statistics[portid].dropped); // 计数器的指针放到第三个参数
		if (ret < 0)
			rte_exit(EXIT_FAILURE,
			"Cannot set error callback for tx buffer on port %u\n",
				 portid);

		/* Start device */
		// 启用设备
		ret = rte_eth_dev_start(portid);
		if (ret < 0)
			rte_exit(EXIT_FAILURE, "rte_eth_dev_start:err=%d, port=%u\n",
				  ret, portid);

		printf("done: \n");

		rte_eth_promiscuous_enable(portid); // 混杂模式

		printf("Port %u, MAC address: %02X:%02X:%02X:%02X:%02X:%02X\n\n",
				portid,
				l2fwd_ports_eth_addr[portid].addr_bytes[0],
				l2fwd_ports_eth_addr[portid].addr_bytes[1],
				l2fwd_ports_eth_addr[portid].addr_bytes[2],
				l2fwd_ports_eth_addr[portid].addr_bytes[3],
				l2fwd_ports_eth_addr[portid].addr_bytes[4],
				l2fwd_ports_eth_addr[portid].addr_bytes[5]);

		/* initialize port stats */
		memset(&port_statistics, 0, sizeof(port_statistics));
	}

	if (!nb_ports_available) {
		rte_exit(EXIT_FAILURE,
			"All available ports are disabled. Please set portmask.\n");
	}

	check_all_ports_link_status(l2fwd_enabled_port_mask); // 检查所有链路的状态,可以参考 flow_filtering

	ret = 0;
	/* launch per-lcore init on every lcore */
	// 这里就是DPDK的典型执行方法,分配所有 lcore 执行函数
	rte_eal_mp_remote_launch(l2fwd_launch_one_lcore, NULL, CALL_MASTER);
	RTE_LCORE_FOREACH_SLAVE(lcore_id) {
		if (rte_eal_wait_lcore(lcore_id) < 0) {
			ret = -1;
			break;
		}
	}

	RTE_ETH_FOREACH_DEV(portid) {
		if ((l2fwd_enabled_port_mask & (1 << portid)) == 0)
			continue;
		printf("Closing port %d...", portid);
		rte_eth_dev_stop(portid);
		rte_eth_dev_close(portid);
		printf(" Done\n");
	}
	printf("Bye...\n");

	return ret;
}

二层转发和普通的端口转发有什么区别呢?简单来说有几个:

特点L2fwdbasicfwd
端口数量两者都用端口掩码来指定,L2fwd支持奇数个只能是偶数个
lcore数量多个,每个lcore负责一个port一个lcore,执行类似repeater的程序
转发逻辑转发时会改写MAC地址只能是 0<-->1,2<-->3 这样的 pair 互相转发
Tx_buffer有发包缓存队列,收的包会缓存到发包队列里,一段时间后或者队列满后才会转发没有发包缓存,Rx收到包后直接Tx出去

运行情况

root@ubuntu:/home/chang/dpdk/examples/l2fwd/build# ./l2fwd -l 0-3 -n 4 -- -p 0x3EAL: Detected 8 lcore(s)
EAL: No free hugepages reported in hugepages-1048576kB
EAL: Multi-process socket /var/run/.rte_unix
EAL: Probing VFIO support...
EAL: PCI device 0000:02:01.0 on NUMA socket -1
EAL:   Invalid NUMA socket, default to 0
EAL:   probe driver: 8086:100f net_e1000_em
EAL: PCI device 0000:02:02.0 on NUMA socket -1
EAL:   Invalid NUMA socket, default to 0
EAL:   probe driver: 8086:100f net_e1000_em
EAL: PCI device 0000:02:03.0 on NUMA socket -1
EAL:   Invalid NUMA socket, default to 0
EAL:   probe driver: 8086:100f net_e1000_em
EAL: PCI device 0000:02:04.0 on NUMA socket -1
EAL:   Invalid NUMA socket, default to 0
EAL:   probe driver: 8086:100f net_e1000_em
MAC updating enabled
Lcore 0: RX port 0
Lcore 1: RX port 1
Initializing port 0... done: 
Port 0, MAC address: 00:0C:29:F7:4D:25

Initializing port 1... done: 
Port 1, MAC address: 00:0C:29:F7:4D:2F


Checking link statusdone
Port0 Link Up. Speed 1000 Mbps - full-duplex
Port1 Link Up. Speed 1000 Mbps - full-duplex
L2FWD: entering main loop on lcore 1
L2FWD:  -- lcoreid=1 portid=1
L2FWD: lcore 3 has nothing to do
L2FWD: entering main loop on lcore 0
L2FWD:  -- lcoreid=0 portid=0



Port statistics ====================================
Statistics for port 0 ------------------------------
Packets sent:                  2152346
Packets received:              2166674
Packets dropped:                     0
Statistics for port 1 ------------------------------
Packets sent:                  2166674
Packets received:              2152371
Packets dropped:                     0
Aggregate statistics ===============================
Total packets sent:            4319020
Total packets received:        4319045
Total packets dropped:               0
====================================================

用 wireshark 抓下包:

可以看到经过端口转发的包的目的MAC地址被程序改变了。

但是改变了目的MAC地址,自然无法通信。所以这个程序还是设置成测试速率用。如果把修改目的MAC地址的那一行代码注释掉,就可以正常通信。

Sample guide 里有一句话说:The L2 Forwarding application can also be used as a starting point for developing a new application based on the DPDK. 所以这个程序也是非常亲民的=。=

DPDK网卡PMD驱动常用属性字段和API函数汇总 使用方法:rte_eth_dev_configure函数的struct rte_eth_conf的参数指定rxmode.mq_mode = RTE_ETH_MQ_RX_RSS_FLAG,rx_adv_conf.rss_conf指定哈希key。如果网卡支持RTE_ETH_TX_OFFLOAD_MT_LOCKFREE属性,则多个线程可以在没有锁的情况下,在同一个Tx队列上并发地调用rte_eth_tx_burst()发送报文。请注意,扩展统计信息标识符是驱动程序特定的,因此对于不同的端口可能不一样。 阅读详情

相关推荐

DPDK L3fwd 源码阅读

实现精确匹配时,路由表的内容是,实现思路是将五元组信息过 hash 函数,得到一个hash value用作路由表的 index。DPDK 为了性能要求,在 hash library 中对 hash table有特定的要求:hash key 的长度必须固定,hash 表的条目也是有限的。整个L3fwd有三千多行代码,但总体思想就是在L2fwd的基础上,增加网络层的根据 IP 地址进行路由查找的内容。LPM的思路是挺简单的,然而具体的代码实现算是一个大工程,思路也是非常有意思。......

lingshengxiyou的博客 401

dpdk 程序如何配置网卡收发包队列描述符配置?

网卡收发包队列描述符配置看似是一个小的配置,其背后也有诸多细节。如何确定一个最优的配置常常不是一个容易的问题,需要对现有的实现跟底层原理有相对清晰的了解,既知道哪种配置是最优的,也知道为啥它是最优的,这点就相对有些难度了。

龙瑜的博客 2169

DPDK简单example的阅读——l2fwd

DPDK,l2fwd

少阁主的专栏 7094

dpdk l2fwd 应用流程分析

int MAIN(int argc, char **argv) { struct lcore_queue_conf *qconf; struct rte_eth_dev_info dev_info; int ret; uint8_t nb_ports; uint8_t nb_ports_available; uint8_t portid, l...

weixin_34220963的博客 573

NXP DPAA2平台DPDK实战:从环境搭建到应用调优全解析

在嵌入式网络与高性能网关领域,数据包处理性能是核心挑战。传统Linux内核网络协议栈因中断和上下文切换开销,难以满足高吞吐、低延迟需求。DPDK(数据平面开发套件)通过用户态轮询、大页内存和零拷贝等技术,绕开内核瓶颈,实现了数据平面的极致加速。其技术价值在于为网络功能虚拟化、5G核心网和边缘计算等场景提供了确定性的高性能转发能力。NXP的DPAA2架构则进一步将硬件加速引擎集成到SoC中,为队列管理、加密和缓冲区操作提供专用硬件支持。当DPDK与DPAA2结合,开发者需要在资源容器(DPRC)的抽象层上进行

ozzzzzz的专栏 419

手把手教你搞定国民技术N32G031的Keil开发环境(附Pack包和JLink配置)

本文详细指导如何搭建国民技术N32G031的Keil开发环境,包括Keil MDK安装与激活、Pack包导入、JLink调试器配置等关键步骤。特别针对新手常见问题提供解决方案,帮助开发者快速上手N32G031系列MCU的开发工作。

cuangu9341的博客 419

DPDK的examples:l2fwd源码阅读、注释

直接贴源码与注释,后续再详细展开 (flag不立白不立) /*- * BSD LICENSE * * Copyright(c) 2010-2016 Intel Corporation. All rights reserved. * All rights reserved. * * Redistribution and use in source and binary f...

Sword1996的博客 1029

飞牛NAS一键部署小雅Emby全家桶:解锁海量影音资源与智能刮削

本文详细介绍了如何在飞牛NAS上通过Docker一键部署小雅Emby全家桶,完美解决传统WebDAV挂载无法刮削元数据的问题。该方案整合了小雅Alist海量影音资源与Emby媒体服务器,通过预下载超百GB元数据包,自动实现海报、简介等信息的完美匹配,打造出精美的个人影音库。部署过程需准备阿里云盘令牌与充足存储空间,并详解了从环境配置、镜像拉取到最终验证的完整步骤及优化技巧。

weixin_28312391的博客 918

DPDK(二)l2fwd源码阅读

1.解析命令行参数 l2fwd的命令行参数分两部分:EAL和程序本身的参数,中间以–分隔开。例如: ./l2fwd -c 0x3 -n 4 -- -p 3 -q 1 其中-c和-n就是EAL的参数,-p和-q就是程序配置参数。在解析参数的时候也是分两部分解析的。 a.EAL初始化(初始化的同时解析第一部分参数) ret = rte_eal_init(argc, argv);//有时间应该好好看一下...

weixin_43210604的博客 2785

PC微信(3.9.2.23)逆向实战:定位并Hook消息接收函数

本文详细介绍了PC微信(3.9.2.23)逆向分析中定位并Hook消息接收函数的完整实战流程。通过Cheat Engine动态定位未读消息数内存地址,结合OllyDbg分析代码上下文,最终找到稳定的Hook点,并分享了实战中的关键注意事项与踩坑经验。

lll78的博客 472

Android 默认授予所有应用权限以及特殊权限处理

PackageManagerService作为android的主要系统服务之一,包含处理应用权限相关的职责。源码地址如下:在PMS成功启动时并收到回掉systemReady回进行应用权限授予。DefaultPermissionGrantPolicy为权限处理策略管理类。这里并没有赋予所有系统应用权限,所以当进行应用时还是会显示权限检查的弹框。可通过此方法进行所有应用权限赋予:如果不想所有应用都授予权限,可考虑在system/etc下的权限相关xml文件添加应用包名和所需权限。

Jimmy的专栏 1508

DPDK源码分析之l2fwd

2层转发,即对应OSI模型中的数据链路层,该层以Mac帧进行传输,运行在2层的比较有代表性的设备就是交换机了。当交换机收到数据时,它会检查它的目的MAC地址,然后把数据从目的主机所在的接口转发出去。交换机之所以能实现这一功能,是因为交换机内部有一个MAC地址表,MAC地址表记录了网络中所有MAC地址与该交换机各端口的对应信息。

诗酒趁年华 1449

在mdk文件移植太多_XCubeClassB代码 的移植

前言ST在www.st.com官网上提供了针对IEC60730/IEC60335 ClassB认证的软件包。软件包分为三个,分别适用STM8,STM32(基于标准外设库)和STM32(基于HAL库):STM8-SafeCLASSB,STM32-CLASSB-SPL,X-CUBE-CLASSB。本文以X-CUBE-CLASSB为例介绍如何做代码的移植以及经常遇到的问题的解决办法。X-CUB...

weixin_28898707的博客 1832

dpdk-l2fwd

l2fwd 测试_fengfengdiandia的专栏-优快云博客_l2fwd环境如下:Ubuntu 16.04cpu 4 coreIntel Corporation 82545EM Gigabit Ethernet Controller * 2个dpdk-17.02.1 提示: 测试需要root权限一. dpdk 环境搭建1.1. 编译 dpdkcd dpdkhttps://blog.youkuaiyun.com/fengfengdiandia/article/details/78433839 DPDK-l2fw

Do 896

RabbitMQ的使用(JAVA)

Java快速上手的MQ上线!

.............. 1万+

CMOS反相器版图设计全解析:PMOS/NMOS布局与VDD/VSS连接门道

版图设计是芯片从电路原理图迈向物理实现的必经桥梁,而CMOS反相器作为最基础的数字单元,恰好是理解这一过程的最佳切入点。在版图绘制中,PMOS与NMOS因导电类型和迁移率差异,需要在N阱与P衬底上分别规划,并呈现出不同的有源区尺寸。真正决定版图好坏的关键,往往在于VDD/VSS的完整连接——不仅要接通源漏,还要通过阱接触和衬底接触固定阱与衬底电位,防止寄生PN结正偏和闩锁效应。合理布局电源地线轨道、优化接触孔数量,能显著降低寄生电阻与电迁移风险。这些原理不仅适用于反相器,也是所有CMOS数字标准单元版图设计

weixin_29159441的博客 299

【DPDK例程学习】(4) l2fwd

/ === 头文件 ===#include <rte_common.h> // 通用宏#include <rte_random.h> // 随机数#include <rte_ether.h> // ④ 以太网帧结构体 rte_ether_hdr / rte_ether_addr#include <rte_string_fns.h> // 字符串工具// === 全局配置 ===// ⑤ Ctrl+C 标志// ⑥ MAC 重写开关// ⑦ 混杂模式开关32// ⑧ Burst 大小。

qq_54050349的博客 361

一文吃透 SAP HANA SDA:把分散数据当作本地数据来用的那把钥匙

摘要:SAP HANA SDA(Smart Data Access)技术详解:150字精要 SAP HANA SDA是一种数据虚拟化技术,允许在不复制数据的情况下访问远端数据源。核心功能包括: 通过Remote Source定义连接配置 创建Virtual Table作为本地代理 支持谓词下推优化查询性能 与SDI技术协同工作扩展数据集成能力 典型应用场景包括: 跨系统联邦查询 冷热数据分层管理 混合云数据整合 远程表复制(RTR)实现近实时数据同步 实施要点: 需关注适配器能力差异 合理运用查询下推优化

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 318

intel dpdk api l2fwd 实例程序测试

声明:此文档只做学习交流使用,请勿用作其他商业用途 作者:朝阳_tony 邮箱:linzhaolover@gmail.com 2013年5月3日14:24:00 星期五 转载请注明出处:http://blog.youkuaiyun.com/linzhaolove 文章最后程序时间: 2013年5月3日14:24:37 星期五 如果您对dpdk有了解,希望不吝赐教;

编码人生——朝阳_tony 1万+

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

DPDK二层转发示例源码阅读

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <inttypes.h> #include <sys/types.h> #include <sys/queue.h&gt...

C12267的博客 978

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

l3fwd 是什么_DPDK L3fwd 源码阅读

代码部分整个L3fwd有三千多行代码,但总体思想就是在L2fwd的基础上,增加网络层的根据 IP 地址进行路由查找的内容。main.c 文件intmain(int argc, char **argv){/*......*//* init EAL */ret = rte_eal_init(argc, argv);if (ret < 0)rte_exit(EXIT_FAILURE, "Inval...

weixin_29061531的博客 1390

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

上一篇: make modules 和 make modules_install
下一篇: 内核编译版本号带有+问题
mtOS1573
博客等级 码龄15年 305粉丝 188原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值