/*-
* BSD LICENSE
*
* Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/#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_memzone.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_pci.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>staticvolatile bool force_quit;/* MAC updating enabled by default */staticint mac_updating =1;#define RTE_LOGTYPE_L2FWD RTE_LOGTYPE_USER1#define NB_MBUF 8192#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 128#define RTE_TEST_TX_DESC_DEFAULT 512static uint16_t nb_rxd = RTE_TEST_RX_DESC_DEFAULT;static uint16_t nb_txd = RTE_TEST_TX_DESC_DEFAULT;/* ethernet addresses of ports */staticstruct 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];staticunsignedint l2fwd_rx_queue_per_lcore =1;#define MAX_RX_QUEUE_PER_LCORE 16#define MAX_TX_QUEUE_PER_PORT 16struct lcore_queue_conf {unsigned n_rx_port;unsigned rx_port_list[MAX_RX_QUEUE_PER_LCORE];} __rte_cache_aligned;struct lcore_queue_conf lcore_queue_conf[RTE_MAX_LCORE];staticstruct rte_eth_dev_tx_buffer *tx_buffer[RTE_MAX_ETHPORTS];staticconststruct rte_eth_conf port_conf ={.rxmode ={.split_hdr_size =0,.header_split =0,/**< Header Split disabled */.hw_ip_checksum =0,/**< IP checksum offload disabled */.hw_vlan_filter =0,/**< VLAN filtering disabled */.jumbo_frame =0,/**< Jumbo Frame Support disabled */.hw_strip_crc =1,/**< CRC stripped by hardware */},.txmode ={.mq_mode = ETH_MQ_TX_NONE,},};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 */static uint64_t timer_period =10;/* default period is 10 seconds *//* Print out statistics on packets dropped */staticvoidprint_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;constchar clr[]={27,'[','2','J','\0'};constchar 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");}//替换源MAC地址和目的MAC地址staticvoidl2fwd_mac_updating(struct rte_mbuf *m,unsigned dest_portid){struct ether_hdr *eth;void*tmp;
eth =rte_pktmbuf_mtod(m,struct ether_hdr *);/* 02:00:00:00:00:xx */
tmp =ð->d_addr.addr_bytes[0];*((uint64_t *)tmp)=0x000000000002+((uint64_t)dest_portid <<40);/* src addr */ether_addr_copy(&l2fwd_ports_eth_addr[dest_portid],ð->s_addr);}//转发staticvoidl2fwd_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)l2fwd_mac_updating(m, dst_port);
buffer = tx_buffer[dst_port];
sent =rte_eth_tx_buffer(dst_port,0, buffer, m);if(sent)
port_statistics[dst_port].tx += sent;}/* main processing loop */staticvoidl2fwd_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;//获取coreid
lcore_id =rte_lcore_id();
qconf =&lcore_queue_conf[lcore_id];if(qconf->n_rx_port ==0){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);}//强制退出,ctrl+cwhile(!force_quit){
cur_tsc =rte_rdtsc();/*
* TX burst queue drain
*///计算时间
diff_tsc = cur_tsc - prev_tsc;if(unlikely(diff_tsc > drain_tsc)){for(i =0; i < qconf->n_rx_port; i++){
portid = l2fwd_dst_ports[qconf->rx_port_list[i]];
buffer = tx_buffer[portid];
sent =rte_eth_tx_buffer_flush(portid,0, buffer);if(sent)
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++){
portid = qconf->rx_port_list[i];//收包,一次最多收取MAX_PKT_BURST个数据包
nb_rx =rte_eth_rx_burst((uint8_t) portid,0,
pkts_burst, MAX_PKT_BURST);//更新统计数据
port_statistics[portid].rx += nb_rx;for(j =0; j < nb_rx; j++){
m = pkts_burst[j];rte_prefetch0(rte_pktmbuf_mtod(m,void*));转发,替换源MAC地址和目的MAC地址l2fwd_simple_forward(m, portid);}}}}staticintl2fwd_launch_one_lcore(__attribute__((unused))void*dummy){l2fwd_main_loop();return0;}/* display usage */staticvoidl2fwd_usage(constchar*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);}staticintl2fwd_parse_portmask(constchar*portmask){char*end =NULL;unsignedlong pm;/* parse hexadecimal string */
pm =strtoul(portmask,&end,16);if((portmask[0]=='\0')||(end ==NULL)||(*end !='\0'))return-1;if(pm ==0)return-1;return pm;}staticunsignedintl2fwd_parse_nqueue(constchar*q_arg){char*end =NULL;unsignedlong n;/* parse hexadecimal string */
n =strtoul(q_arg,&end,10);if((q_arg[0]=='\0')||(end ==NULL)||(*end !='\0'))return0;if(n ==0)return0;if(n >= MAX_RX_QUEUE_PER_LCORE)return0;return n;}staticintl2fwd_parse_timer_period(constchar*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;}staticconstchar 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,};staticconststruct 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 */staticintl2fwd_parse_args(int argc,char**argv){int opt, ret, timer_secs;char**argvopt;int option_index;char*prgname = argv[0];
argvopt = argv;while((opt =getopt_long(argc, argvopt, short_options,
lgopts,&option_index))!=EOF){switch(opt){/* portmask */case'p':
l2fwd_enabled_port_mask =l2fwd_parse_portmask(optarg);if(l2fwd_enabled_port_mask ==0){printf("invalid portmask\n");l2fwd_usage(prgname);return-1;}break;/* nqueue */case'q':
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 */case0:break;default:l2fwd_usage(prgname);return-1;}}if(optind >=0)
argv[optind-1]= prgname;
ret = optind-1;
optind =1;/* reset getopt lib */return ret;}/* Check the link status of all ports in up to 9s, and print them finally */staticvoidcheck_all_ports_link_status(uint8_t port_num, uint32_t port_mask){#define CHECK_INTERVAL 100 /* 100ms */#define MAX_CHECK_TIME 90 /* 9s (90 * 100ms) in total */
uint8_t portid, 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;for(portid =0; portid < port_num; 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",(uint8_t)portid,(unsigned)link.link_speed,(link.link_duplex == ETH_LINK_FULL_DUPLEX)?("full-duplex"):("half-duplex\n"));elseprintf("Port %d Link Down\n",(uint8_t)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");}}}staticvoidsignal_handler(int signum){if(signum == SIGINT || signum == SIGTERM){printf("\n\nSignal %d received, preparing to exit...\n",
signum);
force_quit = true;}}// 命令行解析//参数输入 ./l2fwd -c 0x3 -n 4 -- -p 3 -q 1//其中-c 为十六进制的分配的逻辑内核数量,-n 为十进制的内存通道数量,EAL参数和程序参数用--分开// -q 为分配给每个核心的收发队列数量(端口数量),-p为十六进制的分配的端口数,-t 为可选默认10s打印时间间隔参数intmain(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, last_port;unsigned lcore_id, rx_lcore_id;unsigned nb_ports_in_mask =0;/* init EAL *///初始化EAL参数,并解析参数,系统函数getopt以及getopt_long,这些处理命令行参数的函数,处理到“--”时就会停止,分割参数
ret =rte_eal_init(argc, argv);if(ret <0)rte_exit(EXIT_FAILURE,"Invalid EAL arguments\n");//argc减去EAL参数的同时,argv加上EAL的参数,保证解析程序参数的时候已经跳过了EAL参数
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");/* convert to number of cycles *///-t参数,打印时间间隔
timer_period *=rte_get_timer_hz();/* create the mbuf pool *///DPDK运行时创建的大页内存中,创建报文内存池,其中socket不是套接字,是numa框架中的socket,每个socket都有数个node,每个node右包括数个core。每个socket都有自己的内存,每个socket里的处理器访问自己内存的速度最快,访问其他socket的内存则较慢。
l2fwd_pktmbuf_pool =rte_pktmbuf_pool_create("mbuf_pool", NB_MBUF,
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");
nb_ports =rte_eth_dev_count();if(nb_ports ==0)rte_exit(EXIT_FAILURE,"No Ethernet ports - bye\n");/* reset l2fwd_dst_ports *///设置二层转发目的端口for(portid =0; portid < RTE_MAX_ETHPORTS; portid++)//初始化所有的目的端口为0
l2fwd_dst_ports[portid]=0;
last_port =0;/*
* Each logical core is assigned a dedicated TX queue on each port.
*/for(portid =0; portid < nb_ports; portid++){/* skip ports that are not enabled *///l2fwd_enabled_port_mask 可用端口位掩码 //跳过未分配或是不可用端口。可用端口位掩码表示,左数第n位如果为1,表示端口n可用,如果左数第n位如果为0,表示端口n不可用。要得到第x位为1还是0,我们的方法是将1左移x位,得到一个只在x位为1,其他位都为0的数,再与位掩码相与。结果为1,那么第x位为1,结果位0,那么第x位为0.if((l2fwd_enabled_port_mask &(1<< portid))==0)continue;//此处,当输入端口数,即nb_ports为1时,dst_port[0] = 0;//此处,当输入端口数,即nb_ports为2时,dst_port[0] = 0,dst_port[2] = 1,dst_port[1] = 2;//此处,当输入端口数,即nb_ports为3时,dst_port[0] = 0,dst_port[2] = 1,dst_port[1] = 2;//此处,当输入端口数,即nb_ports为4时,....dst_port[4] = 3,dst_port[3] = 4;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++;rte_eth_dev_info_get(portid,&dev_info);}if(nb_ports_in_mask %2){printf("Notice: odd number of ports in portmask.\n");
l2fwd_dst_ports[last_port]= last_port;}
rx_lcore_id =0;
qconf =NULL;/* Initialize the port/queue configuration of each logical core *///参数q,判断每个逻辑核绑定的端口有没有超出限制(输入的参数q),没超过就继续绑定for(portid =0; portid < nb_ports; portid++){/* skip ports that are not enabled */if((l2fwd_enabled_port_mask &(1<< portid))==0)continue;/* get the lcore_id for this port *///l2fwd_rx_queue_per_lcore即参数-qwhile(rte_lcore_is_enabled(rx_lcore_id)==0||
lcore_queue_conf[rx_lcore_id].n_rx_port ==
l2fwd_rx_queue_per_lcore){
rx_lcore_id++;if(rx_lcore_id >= RTE_MAX_LCORE)rte_exit(EXIT_FAILURE,"Not enough cores\n");}if(qconf !=&lcore_queue_conf[rx_lcore_id])/* Assigned a new logical core in the loop above. */
qconf =&lcore_queue_conf[rx_lcore_id];
qconf->rx_port_list[qconf->n_rx_port]= portid;
qconf->n_rx_port++;printf("Lcore %u: RX port %u\n", rx_lcore_id,(unsigned) portid);}
nb_ports_available = nb_ports;/* Initialise each port *///初始化每个端口for(portid =0; portid < nb_ports; portid++){/* skip ports that are not enabled */if((l2fwd_enabled_port_mask &(1<< portid))==0){printf("Skipping disabled port %u\n",(unsigned) portid);
nb_ports_available--;continue;}/* init port */printf("Initializing port %u... ",(unsigned) portid);//清除读写缓冲区fflush(stdout);///配置端口,将一些配置写进设备dev的一些字段,以及检查设备支持什么类型的中断、支持的包大小
ret =rte_eth_dev_configure(portid,1,1,&port_conf);if(ret <0)rte_exit(EXIT_FAILURE,"Cannot configure device: err=%d, port=%u\n",
ret,(unsigned) 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,(unsigned) portid);//获取设备的MAC地址,存入l2fwd_ports_eth_addr[]数组,后续打印MAC地址rte_eth_macaddr_get(portid,&l2fwd_ports_eth_addr[portid]);/* init one RX queue *///清除读写缓冲区fflush(stdout);//设置接收队列,nb_rxd指收取队列的大小,最大能够存储mbuf的数量
ret =rte_eth_rx_queue_setup(portid,0, nb_rxd,rte_eth_dev_socket_id(portid),NULL,
l2fwd_pktmbuf_pool);if(ret <0)rte_exit(EXIT_FAILURE,"rte_eth_rx_queue_setup:err=%d, port=%u\n",
ret,(unsigned) portid);/* init one TX queue on each port */fflush(stdout);//初始化一个发送队列,nb_txd指发送队列的大小,最大能够存储mbuf的数量
ret =rte_eth_tx_queue_setup(portid,0, nb_txd,rte_eth_dev_socket_id(portid),NULL);if(ret <0)rte_exit(EXIT_FAILURE,"rte_eth_tx_queue_setup:err=%d, port=%u\n",
ret,(unsigned) portid);/* Initialize TX buffers *///为每个端口分配接收缓冲区,根据numa架构的socket就近分配
tx_buffer[portid]=rte_zmalloc_socket("tx_buffer",RTE_ETH_TX_BUFFER_SIZE(MAX_PKT_BURST),0,rte_eth_dev_socket_id(portid));if(tx_buffer[portid]==NULL)rte_exit(EXIT_FAILURE,"Cannot allocate buffer for tx on port %u\n",(unsigned) portid);//初始化发送缓冲区rte_eth_tx_buffer_init(tx_buffer[portid], MAX_PKT_BURST);
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",(unsigned) 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,(unsigned) portid);printf("done: \n");rte_eth_promiscuous_enable(portid);printf("Port %u, MAC address: %02X:%02X:%02X:%02X:%02X:%02X\n\n",(unsigned) 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 *///初始化端口数据,就是后面要打印的,接收、发送、drop的包数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(nb_ports, l2fwd_enabled_port_mask);
ret =0;/* launch per-lcore init on every lcore *///在每个逻辑内核上启动线程,开始转发,l2fwd_launch_one_lcore实际上运行的是l2fwd_main_looprte_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;}}for(portid =0; portid < nb_ports; 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;}