DPDK内存管理-mempool、mbuf

来源:互联网 发布:泰拉瑞亚mac存档位置 编辑:程序博客网 时间:2024/06/05 07:59

1. init

DPDK通过使用hugetlbfs,减少CPU TLB表的Miss次数,提高性能。

DPDK的内存初始化工作,主要是将hugetlbfs的配置的大内存页,根据其映射的物理地址是否连续、属于哪个Socket等,有效的组织起来,为后续管理提供便利。

1. eal_hugepage_info_init

eal_hugepage_info_init()主要是获取配置好的Hugetlbfs的相关信息,并将其保存在struct internal_config数据结构中。主要工作如下:

  1. 读取/sys/kernel/mm/hugepages目录下的各个子目录,通过判断目录名称中包含”hugepages-“字符串,获取hugetlbfs的相关子目录,并获取hugetlbfs配置的内存页大小。
  2. 通过读取/proc/mounts信息,找到hugetlbfs的挂载点。
  3. 通过读取/sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages,获取配置的hugepages个数。
  4. 以打开文件的方式,打开挂载点目录,为其FD设置互斥锁。上述所有获取的信息,都保存在internal_config.hugepage_info[MAX_HUGEPAGES_SIZE]中,hugepage_info数据结构如下:
struct hugepage_info {  size_t hugepage_sz; /**< size of a hugepage */  const char *hugedir; /**< dir wherehugetlbfs is mounted */  uint32_t num_pages[RTE_MAX_NUMA_NODES];   /**< number of hugepages of that size oneach socket */  int lock_descriptor; /**< file descriptorfor hugepage dir */};赋值如下:hpi->hugepage_sz = 2M;hpi->hugedir = /mnt/huge;hpi->num_pages[0] = 64; // 由于此时还不知道哪些内存页分处在哪个socket上,故都先放在socket-0上。hpi->lock_descriptor = open(hpi->hugedir, O_RONLY); // 在读取hugetlbfs配置的时候,需要锁住整个目录。当所有hugepage都mmap(将一个文件或者其它对象映射进内存)完成后,会解锁。

2. rte_eal_config_create

rte_eal_config_create()主要是初始化rte_config.mem_config。

如果是以root用户运行dpdk程序的话,rte_config.mem_config = /var/run/.rte_config文件mmap的首地址,指向/var/run/.rte_config文件mmap的一段 sizeof(struct rte_mem_config) 大小的内存。

3. rte_eal_hugepage_init

rte_eal_hugepage_init()主要是在/mnt/huge目录下创建hugetlbfs配置的内存页数(在本文中就是64)的rtemap_xx文件,并为每个rtemap_xx文件做mmap映射,保证mmap后的虚拟地址与实际的物理地址是一样的。

4. rte_eal_memdevice_init

rte_eal_memdevice_init()初始化rte_config.mem_config->nchannel和rte_config.mem_config->nrank。

rte_config.mem_config->nchannel = 启动参数中“-n”指定的值,不能为0,不能大于4。
rte_config.mem_config->nrank = 启动参数中“-r”指定的值。不能为0,不能大于16。

5. rte_eal_memzone_init

rte_eal_memzone_init()主要负责初始化rte_config.mem_config->free_memseg[]及rte_config.mem_config->memzone[]。
其中,rte_config.mem_config->free_memseg[]记录空闲的rte_config.mem_config->memseg[]。

这里写图片描述

2. rte_mempool

DPDK 以两种方式对外提供内存管理方法:

  • rte_mempool,主要用于网卡数据包的收发;
  • rte_malloc,主要为应用程序提供内存使用接口。

rte_mempool 由函数 rte_mempool_create() 负责创建,从 rte_config.mem_config->free_memseg[] 中取出合适大小的内存,放到 rte_config.mem_config->memzone[] 中。

以 l2fwd 为例,说明 rte_mempool 的创建及使用:

l2fwd_pktmbuf_pool =rte_mempool_create("mbuf_pool", NB_MBUF,MBUF_SIZE, 32,sizeof(structrte_pktmbuf_pool_private),rte_pktmbuf_pool_init, NULL,rte_pktmbuf_init, NULL,rte_socket_id(), 0);// “mbuf_pool”:创建的rte_mempool的名称。// NB_MBUF:rte_mempool包含的rte_mbuf元素的个数。// MBUF_SIZE:每个rte_mbuf元素的大小。#defineRTE_PKTMBUF_HEADROOM    128#defineMBUF_SIZE (2048 + sizeof(struct rte_mbuf) + RTE_PKTMBUF_HEADROOM)#defineNB_MBUF   8192struct rte_pktmbuf_pool_private {  uint16_t mbuf_data_room_size; /**< Size of data space in each mbuf.*/};

rte_mempool 由函数 rte_mempool_create() 负责创建。首先创建 rte_ring,再创建 rte_mempool,并建立两者之间的关联。

这里写图片描述

1.rte_ring_create()创建rte_ring无锁队列

r = rte_ring_create(rg_name, rte_align32pow2(n+1), socket_id, rg_flags);

具体步骤如下:

a、需要保证创建的队列数可以被2整除,即,count = rte_align32pow2(n + 1);

b、计算需要为count个队列分配的内存空间,即,ring_size = count * sizeof(void *) + sizeof(struct rte_ring);

c、调用 rte_memzone_reserve(),在 rte_config.mem_config->free_memseg[] 中查找一个合适的 free_memseg(查找规则是 free_memseg 中剩余内存大于等于需要分配的内存,但是多余的部分是最小的),从该 free_memseg 中分配指定大小的内存,然后将分配的内存记录在 rte_config.mem_config->memzone[] 中。

d、初始化新分配的rte_ring。

2、创建并初始化rte_mempool

a、计算需要为rte_mempool申请的内存空间。包含:sizeof(struct rte_mempool)、private_data_size,以及n * objsz.total_size。

b、调用rte_memzone_reserve(),在rte_config.mem_config->free_memseg[]中查找一个合适的free_memseg,在该free_memseg中分配mempool_size大小的内存,然后将新分配的内存记录到rte_config.mem_config->memzone[]中。

c、初始化新创建的rte_mempool,并调用rte_pktmbuf_pool_init()初始化rte_mempool的私有数据结构。

d、调用mempool_populate(),以及rte_pktmbuf_init()初始化rte_mempool的每个rte_mbuf元素。

3. rte_mbuf

1. rte_mbuf、rte_mempool及网卡收到的数据包在内存中的组织结构 :

这里写图片描述

调用rte_mempool_create()函数创建rte_mempool的时候,指定申请多少个rte_mbuff及每个rte_mbuf中elt_size的大小。elt_size是为网卡接收的数据包预先分配的内存的大小,该内存块就是rte_mbuf->pkt.data的实际存储区域。具体如上图所示。

在申请的rte_mempool内存块中,最前面存储struct rte_mempool数据结构,后面紧接着是rte_pktmbuf_pool_private数据,再后面就是N个rte_mbuf内存块。

每个rte_mbuf内存中,最前面同样存储的是struct rte_mbuf数据结果,后面是RTE_PKTMBUF_HEADROOM,最后面就是实际网卡接收到的数据,如下:

    struct rte_mbuf *m = _m;    uint32_t buf_len = mp->elt_size - sizeof(struct rte_mbuf);    RTE_MBUF_ASSERT(mp->elt_size >= sizeof(struct rte_mbuf));    memset(m, 0, mp->elt_size);    /* start of buffer is just after mbuf structure */    m->buf_addr = (char *)m + sizeof(struct rte_mbuf);    m->buf_physaddr = rte_mempool_virt2phy(mp, m) +            sizeof(struct rte_mbuf);    m->buf_len = (uint16_t)buf_len;    /* keep some headroom between start of buffer and data */    m->pkt.data = (char*) m->buf_addr + RTE_MIN(RTE_PKTMBUF_HEADROOM, m->buf_len);    /* init some constant fields */    m->type = RTE_MBUF_PKT;    m->pool = mp;    m->pkt.nb_segs = 1;    m->pkt.in_port = 0xff;

2.网卡接收的数据是如何存储到rte_mbuf中的?

以e1000网卡为例,在网卡初始化的时候,调用eth_igb_rx_init()初始化网卡的收包队列。每个收包队列数据结果如下:

/** * Structure associated with each RX queue. */struct igb_rx_queue {    struct rte_mempool  *mb_pool;   /**< mbuf pool to populate RX ring. */    volatile union e1000_adv_rx_desc *rx_ring; /**< RX ring virtual address. */    uint64_t            rx_ring_phys_addr; /**< RX ring DMA address. */    volatile uint32_t   *rdt_reg_addr; /**< RDT register address. */    volatile uint32_t   *rdh_reg_addr; /**< RDH register address. */    struct igb_rx_entry *sw_ring;   /**< address of RX software ring. */    struct rte_mbuf *pkt_first_seg; /**< First segment of current packet. */    struct rte_mbuf *pkt_last_seg;  /**< Last segment of current packet. */    uint16_t            nb_rx_desc; /**< number of RX descriptors. */    uint16_t            rx_tail;    /**< current value of RDT register. */    uint16_t            nb_rx_hold; /**< number of held free RX desc. */    uint16_t            rx_free_thresh; /**< max free RX desc to hold. */    uint16_t            queue_id;   /**< RX queue index. */    uint16_t            reg_idx;    /**< RX queue register index. */    uint8_t             port_id;    /**< Device port identifier. */    uint8_t             pthresh;    /**< Prefetch threshold register. */    uint8_t             hthresh;    /**< Host threshold register. */    uint8_t             wthresh;    /**< Write-back threshold register. */    uint8_t             crc_len;    /**< 0 if CRC stripped, 4 otherwise. */    uint8_t             drop_en;  /**< If not 0, set SRRCTL.Drop_En. */};

我们只关注其中两个成员变量,rx_ring和sw_ring。

  • rx_ring记录的是union e1000_adv_rx_desc数组,每个union e1000_adv_rx_desc中指定了网卡接收数据的DMA地址,网卡收到数据后,直接往该地址写数据。
  • sw_ring数组记录的是每个具体的rte_mbuf地址,每个rte_mbuf的rte_mbuff->buf_phyaddr + RTE_PKTMBUF_HEADROOM映射后的DMA地址就存储在rx_ring队列的union e1000_adv_rx_desc数据结构中。rte_mbuff->buf_phyaddr + RTE_PKTMBUF_HEADROOM指向的就是rte_mbuf->pkt.data的地址。此时,rte_mbuf、rte_mbuf->pkt.data,网卡的收包队列就关联起来了。具体如下:
static intigb_alloc_rx_queue_mbufs(struct igb_rx_queue *rxq){    struct igb_rx_entry *rxe = rxq->sw_ring;    uint64_t dma_addr;    unsigned i;    /* Initialize software ring entries. */    for (i = 0; i < rxq->nb_rx_desc; i++) {        volatile union e1000_adv_rx_desc *rxd;        struct rte_mbuf *mbuf = rte_rxmbuf_alloc(rxq->mb_pool);        if (mbuf == NULL) {            PMD_INIT_LOG(ERR, "RX mbuf alloc failed "                "queue_id=%hu\n", rxq->queue_id);            return (-ENOMEM);        }        dma_addr =            rte_cpu_to_le_64(RTE_MBUF_DATA_DMA_ADDR_DEFAULT(mbuf));        rxd = &rxq->rx_ring[i];        rxd->read.hdr_addr = dma_addr;        rxd->read.pkt_addr = dma_addr;        rxe[i].mbuf = mbuf;    }    return 0;}

网卡收到数据后,向rx_ring指定的DMA地址上写数据,其实,就是往每个rte_mbuf->pkt.data写数据。应用程序在调用rte_eth_rx_burst()收包时,以e1000网卡为例,最后调用的是eth_igb_recv_pkts(),就是从每个收包队列中,从sw_ring数组中将rte_mbuf取出来,然后重启申请新的rte_mbuf替换到rx_ring中,重新关联rte_mbuf、union e1000_adv_rx_desc、sw_ring以及rte_mbuf->pkt.data的DMA地址。如下简图所示。

这里写图片描述

原创粉丝点击