【DPDK学习路径】六、申请缓冲区内存池

发布于:2024-06-17 ⋅ 阅读:(19) ⋅ 点赞:(0)

        节5中展示了如何使用DPDK提供的运行时接口创建线程并绑定核心,创建线程是为了执行确定的任务,对于DPDK而言,最重要的任务就是处理网卡接收到的数据包报文。

        Linux 内核协议栈与网卡之间的工作是非常经典的生产者-消费者模型,在接收报文的情况下,网卡总是生产者,而协议栈总是消费者,发送的时候则是相反的。DPDK 想要取代内核协议栈,就必须要完成对这一生产-消费关系的处理,具体来讲,DPDK 使用了无锁循环队列来作为数据包缓冲区。

        队列的组织依赖于存储数据报文的结构体,在内核协议栈中,这一结构体是sk_buff,而在DPDK中,这一结构体是 mbuf。简单来说,内核使用 sk_buff 保存接收的报文,通过多个 sk_buff 组合成为接收队列,而在 DPDK 中,使用 mbuf替代 sk_buff。

        DPDK 提供了一次性创建多个 mbuf的接口 rte_pktmbuf_pool_create,这样一次性创建出来的多个 m_buff 称为一个缓冲区内存池。对上层而言,直接调用此接口创建内存池,使用内存池创建一个接收队列,将队列绑定网卡,即可轮询从队列中取得数据包并保存于内存池之中。

        rte_pktmbuf_pool_create 含有六个参数,分别是内存池名称、mbuf数量、缓存大小、私有空间大小、单个 mbuf 大小、socket id。

        这里的 socket id 是对 NUMA 的支持,与 TCP/IP 的 socket 无关,在后面的章节将详细介绍 NUMA 支持。

        下面给出 rte_pktmbuf_pool_create 的具体实例:

#include <string.h>

#include <rte_eal.h>
#include <rte_mbuf.h>

int main(int argc, char *argv[]) {
    char buf_name[64] = {0};
    struct rte_mempool *mbuf_pool;

    int socketid = 0;
    

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

    snprintf(buf_name, sizeof(buf_name)-1, "buf_1");
    // 创建 mbuf pool
    mbuf_pool = rte_pktmbuf_pool_create(buf_name, 10, 250, 0, 2048+128, socketid);
    if (mbuf_pool == NULL){
        rte_exit(EXIT_FAILURE, "Cannot init mbuf pool on socket %d\n", socketid);
    }
    else{
        printf("Allocated mbuf pool on socket %d\n", socketid);
    }
    printf("alloc mempool name : [%s]\n", mbuf_pool->name);

    // 创建及修改mbuf
    struct rte_mbuf *mbuf;
    char *pkt_start;
    char data[64] = "hello world!";
    int data_len = strlen(data);
    
    mbuf = rte_pktmbuf_alloc(mbuf_pool);
    if (mbuf == NULL) {
        rte_exit(EXIT_FAILURE, "Cannot allocate mbuf\n");
    }
    // mbuf->buf_addr指向了实际的内存缓冲区,mbuf->data_off则表示缓冲区的偏移量
    // 因此,缓冲区指针+偏移量,指向了数据包真实的起点。
    pkt_start = (char *)(mbuf->buf_addr) + mbuf->data_off;
    rte_memcpy(pkt_start, data, data_len);
    printf("%s\n", pkt_start);

    // free
    rte_pktmbuf_free(mbuf);
    rte_mempool_free(mbuf_pool);
    rte_eal_cleanup();
    
    return 0;
}

        在上述实例中我们默认 socket id 是 0,这种假设在 non-NUMA 架构的 CPU 中是合适的,但是,在 NUMA 架构中,实际会导致多核性能下降,这是因为 DPDK 支持 NUMA 架构中的核心优先访问本地内存,而如果默认 socket id 为 0,那么,使用其他核心执行任务时也只能访问 core 0 的内存,也就是访问了远程内存,这会导致访存时间边长。

        因此,在 NUMA 架构下有必要充分使用 DPDK 对 NUMA 的支持创建内存池,下面给出具体的实例:

#define NB_SOCKETS 10
#define MEMPOOL_CACHE_SIZE 250
static int numa_on = 0;
static struct rte_mempool * pktmbuf_pool[NB_SOCKETS];

static int
init_mem(unsigned nb_mbuf){
    unsigned lcore_id;
    int socketid;
    char mbuf_pool_name[64];

    for(lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++){
        if (rte_lcore_is_enabled(lcore_id) == 0){
            continue;
        }

        if(numa_on){
            socketid = rte_lcore_to_socket_id(lcore_id);
        }
        else{
            socketid = 0;
        }

        if(socketid >= NB_SOCKETS){
            rte_exit(EXIT_FAILURE, "Socket %d of lcore %u is out of range %d\n",
                socketid, lcore_id, NB_SOCKETS);
        }

        if(pktmbuf_pool[socketid] == NULL){
            snprintf(mbuf_pool_name, sizeof(mbuf_pool_name), "mbuf_pool_%d", socketid);
            pktmbuf_pool[socketid] = rte_pktmbuf_pool_create(mbuf_pool_name, nb_mbuf,
                MEMPOOL_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, socketid);
            if(pktmbuf_pool[socketid] == NULL){
                rte_exit(EXIT_FAILURE, "Cannot init mbuf pool on socket %d\n", socketid);
            }
            else{
                printf("Allocated mbuf pool on socket %d\n", socketid);
            }
        }
    }
    return 0;
}

        尝试将此函数添加到你的 DPDK 程序中,为各个 socket 创建相应的内存池吧。(对于 non-NUMA 架构,只能为 socket 0 创建内存池)

        下一节我们将讲述如何使用内存池缓冲区创建网卡的 接收/发送队列。