相关代码:DPDK_Learning/UDP_recv at main · mfdycs/DPDK_Learning (github.com)

思路步骤

1. 进程初始化

固定方法,用于初始化DPDK,检测巨页、CPU是否有设置

if (rte_eal_init(argc, argv) < 0) {
    rte_exit(EXIT_FAILURE, "Error with EAL init\n");
}

2. 初始化内存池

一个进程确定一个内存池, 用于接收数据

struct rte_mempool* rte_pktmbuf_pool_create(const char* name,
                                            unsigned n,
                                            unsigned cache_size,
                                            uint16_t priv_size,
                                            uint16_t data_room_size,
                                            int      socket_id);

参数:

  • name: The name of the mbuf pool.
  • n: The number of elements in the mbuf pool. The optimum size (in terms of memory usage) for a mempool is when n is a power of two minus one: n = (2^q - 1).
  • cache_size: Size of the per-core object cache. See rte_mempool_create() for details.
  • priv_size: Size of application private are between the rte_mbuf structure and the data buffer. This value must be aligned to RTE_MBUF_PRIV_ALIGN.
  • data_room_size: Size of data buffer in each mbuf, including RTE_PKTMBUF_HEADROOM.
  • socket_id: The socket identifier where the memory should be allocated. The value can be SOCKET_ID_ANY if there is no NUMA constraint for the reserved zone.

为了提升性能,我们不设置整4k,而是 4k-1,这样大块可以放在另外的地方

#define NUM_MBUFS (4096-1)
struct rte_mempool *mbuf_pool = rte_pktmbuf_pool_create("mbuf pool", NUM_BUFS, 0, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
if (mbuf_pool == NULL) {
    rte_exit(EXIT_FAILURE, "Could not create mbuf pool\n");
}

3. 初始化网卡

确定传送/接收数据的网卡

在安装DPDK的时候,我们会先绑定相应的网卡,可以选择相对应的功能查看绑定信息

400

43-45插入,选择49-50可以绑定,48查看信息

Option: 43
Option: 44
Option: 45

Option: 48

最前面的为PCI地址

700

选择49,输入 0000:03:00.0 绑定 VMXNET3 多队列网卡

700

此时可能会提示该设备被占用,在使用前需要先 down 掉

ens 为虚拟网卡, 需要先改为 ethx

初始化网卡

#define NUM_BUFS (4096-1)

int DPDK_PortID = 0;

static const struct rte_eth_conf port_conf_default = {
    .rxmode = {
        .max_rx_pkt_len = RTE_ETHER_MAX_LEN,
    },
};

static void ng_init_port(struct rte_mempool *mbuf_pool) {
    uint16_t nb_sys_ports = rte_eth_dev_count_avail();
    if (nb_sys_ports == 0) {
        rte_exit(EXIT_FAILURE, "No Supported eth found\n");
    }

#if 0
    struct rte_eth_dev_info dev_info;
    rte_eth_dev_info_get(DPDK_PortID, &dev_info);
# endif

    // 配置网卡信息
    const int num_rx_queues = 1; 
    const int num_tx_queues = 0;
    struct rte_eth_conf port_conf = port_conf_default;

    int ret = rte_eth_dev_configure(DPDK_PortID, num_rx_queues, num_tx_queues, &port_conf);
    if (ret < 0) {
        rte_exit(EXIT_FAILURE, ":: Cannot configure device: err = %d\n", ret);
    }

    ret = rte_eth_rx_queue_setup(DPDK_PortID, 0, 128, rte_eth_dev_socket_id(DPDK_PortID), NULL, mbuf_pool) < 0);
    if (ret < 0) {
        rte_exit(EXIT_FAILURE, "Could not setup RX queue\n");
    }

    ret = rte_eth_dev_start(DPDK_PortID);
    if (ret < 0) {
        rte_exit(EXIT_FAILURE, "Could not start\n");
    }
}

其中,启动队列的函数为

int rte_eth_rx_queue_setup(port_id, rx_queue_id, nb_rx_desc, socket_id, rx_conf, mb_pool);

nb_rx_desc 为当前指定队列的最长长度,为 rte_eth_rx_burst() 接收的包数量,超过且未设置处理的话会导致内存泄漏

4. 接收数据

接收数据,首先考虑以下几个问题:

  • 从哪个端口接收
  • 从哪个队列接收
  • 接收完放到哪里
  • 长度限制是多少
#define BURST_SIZE 128

struct rte_mbuf *mbufs[BURST_SIZE];  

unsigned num_recvd = rte_eth_rx_burst(DPDK_PortID, 0, mbufs, BURST_SIZE);  
if (num_recvd > BURST_SIZE) {  
    rte_exit(EXIT_FAILURE, "Error receiving from eth\n");  
}

5. 处理数据

unsigned int i = 0;  
for (i = 0; i < num_recvd; i++) {  
    // 处理 mbuf 中的数据  
    struct rte_ether_hdr *ethr = rte_pktmbuf_mtod(mbufs[i], struct rte_ether_hdr*);  

    if (ethr->ether_type != rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) {  
        continue;  
    }  

    struct rte_ipv4_hdr *iphdr = rte_pktmbuf_mtod_offset(mbufs[i], struct rte_ipv4_hdr*, sizeof(struct rte_ether_hdr));  

    // UDP协议数据  
    if (iphdr->next_proto_id == IPPROTO_UDP) {  
        struct rte_udp_hdr *udphdr = (struct rte_udp_hdr*)(iphdr + 1);  

        uint16_t length = ntohs(udphdr->dgram_len);  
        *(char*)(udphdr + length) = '\0';  

        struct in_addr addr;  
        addr.s_addr = iphdr->src_addr;  
        printf("src: %s:%d, ", inet_ntoa(addr), ntohs(udphdr->src_port));  

        addr.s_addr = iphdr->dst_addr;  
        printf("dst: %s:%d, len: %d --> %s\n", inet_ntoa(addr), ntohs(udphdr->dst_port), length, (char*)(udphdr + 1));  

        rte_pktmbuf_free(mbufs[i]);  
    }  

}

运行代码

  1. 初始化环境变量
export RTE_SDK=/home/king/share/dpdk/dpdk-stable-19.08.2/
export RTE_TARGET=x86_64-native-linux-gcc
  1. 将代码放到 ./example/recv 目录下,拷贝其他例子的 makefile,修改编译文件,编译
# binary name
APP = dpdk_recv

# all source are stored in SRCS-y
SRCS-y := recv.c

600

  1. 从系统中解绑网卡,这里解绑 eth0:
ifconfig eth0 down
  1. 绑定网卡

发送端添加ip和网卡绑定: arp -s ip mac

绑定对应网卡的arp:

# 查看网卡
> netsh i i show in
Idx     Met         MTU          状态                名称
---  ----------  ----------  ------------  ---------------------------
  1          75  4294967295  connected     Loopback Pseudo-Interface 1
  6          25        1500  disconnected  WLAN
 23          25        1500  connected     以太网
 26          25        1500  disconnected  本地连接* 1
 12          25        1500  disconnected  本地连接* 2
  9          65        1500  disconnected  蓝牙网络连接 2
 17          55        1400  disconnected  以太网 2
  4          35        1500  connected     VMware Network Adapter VMnet1
 13          35        1500  connected     VMware Network Adapter VMnet8
 52        5000        1500  connected     vEthernet (WSL)

> netsh -c i i add neighbors [Idx] [IP] [MAC]
  1. 运行
./build/dpdk-recv