首先浏览了下官网的介绍
1.运行程序
应用层参数:
-p 系统将要使用的端口的十六进制的掩码,如0x3(二进制0011)表示只是用端口0和端口1
-n 开启的客户端的进程数,处理从服务器接收到的数据包
mp_server运行参数:
./mp_server/build/mp_server -l 1-2 -n 4 – -p 3 -n 2
-l 1-2参数:服务器程序运行在core 1上,打印统计信息是在core 2上
mp_client运行参数:
./mp_client/build/mp_client -l 3 -n 4 –proc-type=auto – -n 0
./mp_client/build/mp_client -l 4 -n 4 –proc-type=auto – -n 1
-l参数:两个客户端进程运行在core 3和core 4上。
2.流程分析和走读源代码
int
main(int argc, char *argv[])
{
/* initialise the system 环境的初始化*/
if (init(argc, argv) < 0 )
return -1;
RTE_LOG(INFO, APP, "Finished Process Init.\n");
cl_rx_buf = calloc(num_clients, sizeof(cl_rx_buf[0]));
/* clear statistics */
clear_stats();
/* put all other cores to sleep bar master */
rte_eal_mp_remote_launch(sleep_lcore, NULL, SKIP_MASTER);
//数据转发
do_packet_forwarding();
return 0;
}
初始化函数init
内部函数调用依次为:
1.rte_eal_init函数->eal环境初始化
2.rte_memzone_reserve->申请端口信息预留内存
3.parse_app_args->解析应用层参数
4.init_mbuf_pools->初始化内存池
5.init_port->针对每个端口去初始化端口
6.check_all_ports_link_status->检测端口的链路状态
7.init_shm_rings->初始化用于共享内存的ring
挑选init_port和init_shm_rings函数来讲解下流程,剩下的都是常规流程
init_port(uint8_t port_num)
{
/* for port configuration all features are off by default */
const struct rte_eth_conf port_conf = {
.rxmode = {
.mq_mode = ETH_MQ_RX_RSS
}
};
const uint16_t rx_rings = 1, tx_rings = num_clients;
const uint16_t rx_ring_size = RTE_MP_RX_DESC_DEFAULT;
const uint16_t tx_ring_size = RTE_MP_TX_DESC_DEFAULT;
uint16_t q;
int retval;
printf("Port %u init ... ", (unsigned)port_num);
fflush(stdout);
/* Standard DPDK port initialisation - config port, then set up
* rx and tx rings */
//配置端口属性,比如发送队列和接收队列
if ((retval = rte_eth_dev_configure(port_num, rx_rings, tx_rings,
&port_conf)) != 0)
return retval;
//创建接收队列
for (q = 0; q < rx_rings; q++) {
retval = rte_eth_rx_queue_setup(port_num, q, rx_ring_size,
rte_eth_dev_socket_id(port_num),
NULL, pktmbuf_pool);
if (retval < 0)
return retval;
}
//创建发送队列
for ( q = 0; q < tx_rings; q ++ ) {
retval = rte_eth_tx_queue_setup(port_num, q, tx_ring_size,
rte_eth_dev_socket_id(port_num),
NULL);
if (retval < 0) return retval;
}
//设置端口混杂模式
rte_eth_promiscuous_enable(port_num);
//启动网卡设备
retval = rte_eth_dev_start(port_num);
if (retval < 0) return retval;
printf( "done: \n");
return 0;
}
init_shm_rings(void)
{
unsigned i;
unsigned socket_id;
const char * q_name;
const unsigned ringsize = CLIENT_QUEUE_RINGSIZE;
//用于保存连接的client信息
clients = rte_malloc("client details",
sizeof(*clients) * num_clients, 0);
if (clients == NULL)
rte_exit(EXIT_FAILURE, "Cannot allocate memory for client program details\n");
for (i = 0; i < num_clients; i++) {
/* Create an RX queue for each client */
socket_id = rte_socket_id();
q_name = get_rx_queue_name(i);
clients[i].rx_q = rte_ring_create(q_name,
ringsize, socket_id,
RING_F_SP_ENQ | RING_F_SC_DEQ ); /* single prod, single cons */
if (clients[i].rx_q == NULL)
rte_exit(EXIT_FAILURE, "Cannot create rx ring queue for client %u\n", i);
}
return 0;
}
clients = rte_malloc("client details",sizeof(*clients) * num_clients, 0);
首先申请了clients数组,用于保存每个客户端的信息,如客户端用于通信的rte_ring,客户端标识client_id,收包数,以及丢包数。
q_name = get_rx_queue_name(i);
使用get_rx_queue_name()函数构造“接收队列”的字符串标识。
clients[i].rx_q = rte_ring_create(q_name,
ringsize, socket_id,
RING_F_SP_ENQ | RING_F_SC_DEQ );
创建用于client和server通信的ring,ring的类型是单生产者单消费者。
此时init函数,初始化过程先告一段落。
数据转发函数do_packet_forwarding
do_packet_forwarding(void)
{
unsigned port_num = 0; /* indexes the port[] array */
for (;;) {
struct rte_mbuf *buf[PACKET_READ_SIZE];
uint16_t rx_count;
/* read a port 读取端口上的数据*/
rx_count = rte_eth_rx_burst(ports->id[port_num], 0, \
buf, PACKET_READ_SIZE);
/*统计数据报数量*/
ports->rx_stats.rx[port_num] += rx_count;
/* Now process the NIC packets read */
if (likely(rx_count > 0))
//处理网卡上读取到的数据包
process_packets(port_num, buf, rx_count);
/* 移动到下一个端口发送数据,如果端口等于num_ports,则从0开始*/
if (++port_num == ports->num_ports)
port_num = 0;
}
}
流程是rte_eth_rx_burst函数从端口接收数据包,调用process_packets处理读取的数据包,而且是采用轮询端口的方式来处理数据包的哦。
进入process_packets函数中
process_packets(uint32_t port_num __rte_unused,
struct rte_mbuf *pkts[], uint16_t rx_count)
{
uint16_t i;
uint8_t client = 0;
for (i = 0; i < rx_count; i++) {
enqueue_rx_packet(client, pkts[i]);
//将数据包分依次发给不同的客户端
if (++client == num_clients)
client = 0;
}
for (i = 0; i < num_clients; i++)
flush_rx_queue(i);
}
第一步:将数据包通过enqueue_rx_packet函数发送给不同的客户端,采用的方式是轮询。
enqueue_rx_packet(uint8_t client, struct rte_mbuf *buf)
{
cl_rx_buf[client].buffer[cl_rx_buf[client].count++] = buf;
}
转定义看下cl_rx_buf变量的类型
/* One buffer per client rx queue - dynamically allocate array */
static struct client_rx_buf *cl_rx_buf;
struct client_rx_buf {
struct rte_mbuf *buffer[PACKET_READ_SIZE];
uint16_t count;
};
client_rx_buf的buffer是个数组,数组的长度是PACKET_READ_SIZE(32),每个元素的类型是rte_mbuf*.
现在好好分析下enqueue_rx_packet函数的这条语句,
cl_rx_buf是个结构体数组,数组的下标是client的序号。
cl_rx_buf[client]表示指定client的结构体信息
cl_rx_buf[client].count++ 表示指定客户端使用buffer[PACKET_READ_SIZE]数组中的哪一个元素。
第二步:调用rte_ring_enqueue_bulk函数将数据入队客户端的队列(cl->rx_q标识的队列)
flush_rx_queue(uint16_t client)
{
uint16_t j;
struct client *cl;
if (cl_rx_buf[client].count == 0)
return;
cl = &clients[client];
if (rte_ring_enqueue_bulk(cl->rx_q, (void **)cl_rx_buf[client].buffer,
cl_rx_buf[client].count) != 0)
{
for (j = 0; j < cl_rx_buf[client].count; j++)
rte_pktmbuf_free(cl_rx_buf[client].buffer[j]);
cl->stats.rx_drop += cl_rx_buf[client].count;
}
else
cl->stats.rx += cl_rx_buf[client].count;
cl_rx_buf[client].count = 0;
}
发送成功则统计每个客户端的发包数,发送失败则统计丢包数。
客户端代码分析
1、rte_eal_init函数,eal环境初始化
2、parse_app_args函数,解析应用层参数,解析-n 客户端个数
3、使用rte_ring_lookup函数去查询服务器创建的ring,函数参数是get_rx_queue_name获取的。
4、使用rte_mempool_lookup函数去查询服务器创建的mempool内存池。名称是”MProc_pktmbuf_pool”
5、使用rte_memzone_lookup函数去查询服务器创建的memzone,名称是”MProc_port_info”,通过ports = mz->addr;获取到端口信息。
6、configure_output_ports函数
configure_output_ports(const struct port_info *ports)
{
int i;
if (ports->num_ports > RTE_MAX_ETHPORTS)
rte_exit(EXIT_FAILURE, "Too many ethernet ports. RTE_MAX_ETHPORTS = %u\n",
(unsigned)RTE_MAX_ETHPORTS);
//遍历端口,比如p1=0 p2 = 1,映射输出端口和输入端口的关系
for (i = 0; i < ports->num_ports - 1; i+=2){
uint8_t p1 = ports->id[i];
uint8_t p2 = ports->id[i+1];
output_ports[p1] = p2;
output_ports[p2] = p1;
//配置发送缓冲区
configure_tx_buffer(p1, MBQ_CAPACITY);
configure_tx_buffer(p2, MBQ_CAPACITY);
}
}
进入configure_tx_buffer函数中
static void
configure_tx_buffer(uint8_t port_id, uint16_t size)
{
int ret;
/* Initialize TX buffers */
tx_buffer[port_id] = rte_zmalloc_socket("tx_buffer",
RTE_ETH_TX_BUFFER_SIZE(size), 0,
rte_eth_dev_socket_id(port_id));
if (tx_buffer[port_id] == NULL)
rte_exit(EXIT_FAILURE, "Cannot allocate buffer for tx on port %u\n",
(unsigned) port_id);
rte_eth_tx_buffer_init(tx_buffer[port_id], size);
ret = rte_eth_tx_buffer_set_err_callback(tx_buffer[port_id],
flush_tx_error_callback, (void *)(intptr_t)port_id);
if (ret < 0)
rte_exit(EXIT_FAILURE, "Cannot set error callback for "
"tx buffer on port %u\n", (unsigned) port_id);
}
解析下
rte_zmalloc_socket函数
void* rte_zmalloc_socket ( const char * type, size_t size, unsigned
align, int socket ) type:一个字符串,去标识申请的对象类型,方便日后调试,如“tx_buffer”
size:申请的长度(以字节为单位) align:内存对齐方式,此处填写0
socket:在哪个socket-id上申请内存,socket-id理论上和物理cpu一一对应。rte_eth_dev_socket_id(uint8_t
port_id)函数返回port_id标识的以太网设备所对应的socketId。rte_eth_tx_buffer_init函数 int rte_eth_tx_buffer_init ( struct
rte_eth_dev_tx_buffer * buffer, uint16_t size ) 功能:初始化发送缓冲区
buffer:被初始化的发送缓冲区 size:缓冲区大小返回值:成功则返回指向申请对象的指针。失败则返回NULL值
rte_eth_tx_buffer_set_err_callback函数 设置发送缓冲区的失败回调函数
继续分析代码
下面是个while循环一直从ring无锁队列中弹出数据rte_ring_dequeue_bulk
rx_pkts = (uint16_t)RTE_MIN(rte_ring_count(rx_ring), PKT_READ_SIZE);
rte_ring_count(rx_ring)表示ring中元素的个数,将ring中元素的个数和PKT_READ_SIZE取最小值。
接收到数据包后调用handle_packet函数
static void
handle_packet(struct rte_mbuf *buf)
{
int sent;
const uint8_t in_port = buf->port;
const uint8_t out_port = output_ports[in_port];
struct rte_eth_dev_tx_buffer *buffer = tx_buffer[out_port];
sent = rte_eth_tx_buffer(out_port, client_id, buffer, buf);
if (sent)
tx_stats->tx[out_port] += sent;
}
将数据包从发送端口发送出去。
分析完毕,如有不清晰的地方,请留言或者加qq1078285863