DPDK通过使用hugetlbfs,减少CPU TLB表的Miss次数,提高性能。
DPDK的内存初始化工作,主要是将hugetlbfs的配置的大内存页,根据其映射的物理地址是否连续、属于哪个Socket等,有效的组织起来,为后续管理提供便利。
1. eal_hugepage_info_init
eal_hugepage_info_init()主要是获取配置好的Hugetlbfs的相关信息,并将其保存在struct internal_config数据结构中。主要工作如下:
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[]。
DPDK 以两种方式对外提供内存管理方法:
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 8192
struct 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元素。
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。
static int
igb_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地址。如下简图所示。