一、整体结构
1.结构关系
##2. mmap文件映射关系
结构 | 变量 | 路径 | size |
---|---|---|---|
rte_mem_config | rte_config.mem_config | /var/run/dpdk/config | sizeof(*rte_config.mem_config) |
rte_memseg_list | rte_config.mem_config->memsegs[0].memseg_arr.data | /var/run/dpdk/fbarray_memseg-32768k-0-0 | need_len * sizeof(struct rte_memseg) + msk_needsz |
rte_memzone | rte_config.mem_config->memzones.data | /var/run/dpdk/fbarray_memzone | need_len * sizeof(struct rte_memzone) + msk_needsz |
hugepage_info | internal_config.hugepage_info[0] | /var/run/dpdk/hugepage_info | sizeof(internal_config.hugepage_info) |
rte_memseg | ( (struct rte_memseg*) (rte_config.mem_config->memsegs[0].memseg_arr.data + elt_size*idx) )->addr | /mnt/huge/fbarray_rtemap_0 | hugepage_sz (2M\32M\1G…) |
上面的rte_memseg_list中保存着的rte_memseg, 以及rte_config.mem_config->memzones,
这两个数据都是用rte_fbarray维护,他所需要的大小计算方法是
mmap_len = calc_data_size(page_sz, elt_sz, len);
//其中,
//page_sz是页大小,la架构是16K,
//elt_sz是数据结构的大小【 sizeof(struct rte_memseg) 、 sizeof(struct rte_memzone) 】
//len 是所需的长度
//fbarray保存一个个结构后,最后的位置有msk用于维护各个的使用情况
static size_t
calc_data_size(size_t page_sz, unsigned int elt_sz, unsigned int len)
{
size_t data_sz = elt_sz * len;
size_t msk_sz = calc_mask_size(len);
return RTE_ALIGN_CEIL(data_sz + msk_sz, page_sz);
}
3.锁的应用
读写函数锁的种类调用位置rte_rwlock_t rte_mem_config::mlock;rte_rwlock_read_lockrte_rwlock_read_unlockrte_rwlock_write_lockrte_rwlock_write_unlock自旋锁rte_memzone_reserve_thread_saferte_memzone_freerte_memzone_lookuprte_eal_memzone_initrte_memzone_walk
二、数据结构解析
1.rte_config
所有的数据都存在全局变量rte_config中,其中内存管理的都在struct rte_mem_config *mem_config结构中。
struct rte_config {
uint32_t master_lcore; /**< Id of the master lcore */
uint32_t lcore_count; /**< Number of available logical cores. */
uint32_t numa_node_count; /**< Number of detected NUMA nodes. */
uint32_t numa_nodes[RTE_MAX_NUMA_NODES]; /**< List of detected NUMA nodes. */
uint32_t service_lcore_count;/**< Number of available service cores. */
enum rte_lcore_role_t lcore_role[RTE_MAX_LCORE]; /**< State of cores. */
/** Primary or secondary configuration */
enum rte_proc_type_t process_type;
/** PA or VA mapping mode */
enum rte_iova_mode iova_mode;
/**
* Pointer to memory configuration, which may be shared across multiple
* DPDK instances
*/
struct rte_mem_config *mem_config;
} __attribute__((__packed__));
2.rte_mem_config
2.1 创建过程
- mem_config会保存在“/var/run/dpdk/config”文件中
2)虚拟地址会先通过eal_get_virtual_area获得进程的匿名映射的基地址,再通过这个基地址映射config文件,得到最后的rte_config.mem_config的地址
2.2 主要结构
mem_config总共有三个重要的结构,其余是一些大小、标志、锁等,这三个重要的结构分别是memzones、memseg_list、malloc_heap
memzone 和memseg都封装在了rte_fbarray的data中
memzone = rte_fbarray->data + idx * rte_fbarray->elt_sz;
memseg = rte_fbarray->data + idx * rte_fbarray->elt_sz;
3. memzones
3.1初始化流程
主进程创建"memzone"的fbarray,配置文件存储在"/var/run/dpdk/fbarray_memzone"。
3.2 创建流程
1)先通过malloc_heap_alloc找到一块空闲heap 的 elem,
2)然后把memzone的配置 arr = &mcfg->memzones 管理set为使用。
4. memseg
4.1 初始化流程
struct rte_memseg_list {
RTE_STD_C11
union {
void *base_va;
/**< Base virtual address for this memseg list. */
uint64_t addr_64;
/**< Makes sure addr is always 64-bits */
};
uint64_t page_sz; /**< Page size for all memsegs in this list. */
int socket_id; /**< Socket ID for all memsegs in this list. */
volatile uint32_t version; /**< version number for multiprocess sync. */
size_t len; /**< Length of memory area covered by this memseg list. */
unsigned int external; /**< 1 if this list points to external memory */
unsigned int heap; /**< 1 if this list points to a heap */
struct rte_fbarray memseg_arr;
};
1)这里是对管理memseg_list的结构进行初始化,
2)memseg是用 struct rte_fbarray数据结构进行维护的,
他的配置信息保存在rte_config.mem_config->memseg[id]->memseg_arr里面,映射在配置文件保存在"/var/run/dpdk/fbarray_memseg-32768k-%d-%d".
3)具体流程是
先对rte_memseg_list结构中的memseg_arr进行初始化,创建配置文件,并映射(流程和rte_fbarray的一致)。
再对rte_config.mem_config->memseg[id]进行初始化,通过eal_get_virtual_area得到后面memseg映射的基地址base_va。后面的映射大页内存的虚拟地址的基地址就是这个地址。
4.2 创建流程
4.2.1 申请多个内存段
int
eal_memalloc_alloc_seg_bulk(struct rte_memseg **ms, int n_segs, size_t page_sz,
int socket, bool exact)
- 这个函数先进行参数配置,把参数保存在了wa结构中,其中n_segs是此次要申请的段数,socket是申请所在的numa_id
struct alloc_walk_param {
struct hugepage_info *hi;
struct rte_memseg **ms;
size_t page_sz;
unsigned int segs_allocated;
unsigned int n_segs;
int socket;
bool exact;
};
- 最后调用alloc_seg_walk函数来申请大页内存
4.2.2 内存分段申请
1)上面eal_memalloc_alloc_seg_bulk函数中把申请的参数全部保存在了wa结构中,这里提取出来,need就是需要申请映射的次数
2)分need次数申请,最后调用alloc_seg来映射大页内存(下一节说明)。
3)映射的基地址就是 4.1节中说的rte_config.mem_config->memseg[id]->base_va.
4.2.3 申请内存段
这是巨页内存申请最小单元的函数alloc_seg
- 得到巨页的路径 “/mnt/huge/rtemap_%d”
2)扩大巨页文件大小至alloc_sz,就是巨页大小,loongarch架构是32M
3)下面是将32M大小的文件,mmap到用户进程空间中,得到虚拟addr
4)通过rte_mem_virt2iova,读取"/proc/self/pagemap"进程文件,获取addr虚拟地址对应的物理地址
整个过程完成,得到这个内存段的虚拟地址和物理地址。
5. rte_fbarray
这是一个中间数据管理结构,用于维护memzone和memseg这两个数据结构
struct rte_fbarray {
char name[RTE_FBARRAY_NAME_LEN]; /**< name associated with an array */
unsigned int count; /**< number of entries stored */
unsigned int len; /**< current length of the array */
unsigned int elt_sz; /**< size of each element */
void *data; /**< data pointer */
rte_rwlock_t rwlock; /**< multiprocess lock */
};
- rte_fbarray结构中的len是最大个数,其中memzone是RTE_MAX_MEMZONE(2560)个,memseg是下面流程计算所得。
cur_max_mem = max_type_mem - total_type_mem;
cur_mem = get_mem_amount(hugepage_sz,cur_max_mem);
n_segs = cur_mem / hugepage_sz;
if (eal_memseg_list_init(msl, hugepage_sz, n_segs,
0, type_msl_idx, false)) - elt_sz是memzone和memseg结构体的sizeof。data 是具体的数据,他的映射长度是 calc_data_size计算出来的 elt_sz * len + msk_sz, 每一块都保存一个数据(struct rte_memzone 或者 struct rte_memseg).
- data 是具体的数据,他的映射长度是 calc_data_size计算出来的 elt_sz * len + msk_sz, 每一块都保存一个数据(struct rte_memzone 或者 struct rte_memseg)
- data的数据是memzone或者memseg,他是通过resize_and_map映射配置文件得到,
memzone的数据映射文件是 “/var/run/dpdk/fbarray_memzone”
memseg的数据映射文件是"/var/run/dpdk/fbarray_memseg-32768k-%d-%d"
6.malloc_heap
6.1 封装函数
dpdk中的malloc最终调用malloc_heap_alloc函数,从heap中获取数据,其中heap是
struct malloc_heap *heap = &rte_config.mem_config->malloc_heaps[heap_id];
6.2 数据结构
malloc_heap中的数据是用malloc_elem结构储存数据的。调用rte_malloc最终返回的地址是malloc_elem结构的尾地址
(return elem == NULL ? NULL : (void *)(&elem[1]))。
struct malloc_heap {
rte_spinlock_t lock;
LIST_HEAD(, malloc_elem) free_head[RTE_HEAP_NUM_FREELISTS];
struct malloc_elem *volatile first;
struct malloc_elem *volatile last;
unsigned int alloc_count;
unsigned int socket_id;
size_t total_size;
char name[RTE_HEAP_NAME_MAX_LEN];
} __rte_cache_aligned;
申请的内存,都是以malloc_elem结构存在,前面是malloc_elem结构的维护数据,后面的内存大小供用户使用。
struct malloc_elem {
struct malloc_heap *heap;
struct malloc_elem *volatile prev;
/**< points to prev elem in memseg */
struct malloc_elem *volatile next;
/**< points to next elem in memseg */
LIST_ENTRY(malloc_elem) free_list;
/**< list of free elements in heap */
struct rte_memseg_list *msl;
volatile enum elem_state state;
uint32_t pad;
size_t size;
struct malloc_elem *orig_elem;
size_t orig_size;
#ifdef RTE_MALLOC_DEBUG
uint64_t header_cookie; /* Cookie marking start of data */
/* trailer cookie at start + size */
#endif
} __rte_cache_aligned;
6.3 申请流程
6.3.1 申请流程总述
malloc_heap_alloc是从malloc heaps中申请内存,
如果当前heaps (rte_config.mem_config->malloc_heaps[heap_id])中没有找到,调用malloc_heap_alloc_on_heap_id,会尝试从hugepage中获取more memseg,然后add到heap memory中。
当heaps中有足够的空间时,调用heap_alloc可以找到适当的malloc element,从该malloc element中得到一定大小的内存,剩余部分重新插入heaps中的free 链。
6.3.2 从heap中获取
1)当heap中有足够的空间,就会调用heap_alloc函数,先找到find_suitable_element合适的elem,
2) 然后调用malloc_elem_alloc函数,在找到的elem中,
3) 先调用elem_start_pt找到新的elem的起始和终止地址,原则是地址从下向上查找(具体查看new_data_start = RTE_ALIGN_FLOOR((end_pt - size),align);),新的起始地址是原来的elem的结束地址减去申请的大小。
4) 拆分(split_elem)elem,将新申请的插入elem链表中。
6.3.3 从大页中映射新的memseg
当heap中没有充足空间,就会调用alloc_more_mem_on_socket,最终调用try_expand_heap_primary,来确定需要申请空间的大小alloc_sz,进而得到需要申请大页的个数 n_segs = alloc_sz / pg_sz(即hugepage_sz);
然后调用alloc_pages_on_heap,进入后先映射新的hugepage,添加memseg,然后调用malloc_heap_add_memory把elem添加进到heap(rte_config.mem_config->malloc_heaps[heap_id])中。
7.rte_ring
7.1结构关系
大小 | ||
---|---|---|
rxq | sizeof(struct i40e_rx_queue) | |
rxq->mp | 外面传入的mempool | |
rxq->rx_ring | 描述符个数 * sizeof(union i40e_rx_desc) | 描述符总共16字节 |
rxq->sw_ring | 描述符个数 * sizeof(struct i40e_rx_entry) |
struct i40e_rx_entry {
struct rte_mbuf *mbuf;
};
驱动里面的queue,里面有描述符和buffer两部分,需要申请内存的部分总共有rxq、rxq->rx_ring(描述符)、rxq->sw_ring(存储数据地址的地址)
rxq = rte_zmalloc_socket("i40e rx queue",
sizeof(struct i40e_rx_queue),
RTE_CACHE_LINE_SIZE,
socket_id);
rxq->mp = mp;
len = I40E_MAX_RING_DESC;
len += RTE_PMD_I40E_RX_MAX_BURST;
ring_size = RTE_ALIGN(len * sizeof(union i40e_rx_desc),
I40E_DMA_MEM_ALIGN);
rz = rte_eth_dma_zone_reserve(dev, "rx_ring", queue_idx,
ring_size, I40E_RING_BASE_ALIGN, socket_id);
rxq->mz = rz;
/* Zero all the descriptors in the ring. */
memset(rz->addr, 0, ring_size);
rxq->rx_ring_phys_addr = rz->iova;
rxq->rx_ring = (union i40e_rx_desc *)rz->addr;
rxq->sw_ring = rte_zmalloc_socket("i40e rx sw ring",
sizeof(struct i40e_rx_entry) * len,
RTE_CACHE_LINE_SIZE,
socket_id);
7.2 sw_ring 获取流程
运行中,收发函数会不断申请mbuf
开始阶段,会申请描述符个mbuf,并写进硬件。
I40e驱动rxq开始 代码解读
int
i40e_dev_rx_queue_start(struct rte_eth_dev *dev, uint16_t rx_queue_id)
{
struct i40e_rx_queue *rxq;
int err;
struct i40e_hw *hw = I40E_DEV_PRIVATE_TO_HW(dev->data->dev_private);
PMD_INIT_FUNC_TRACE();
rxq = dev->data->rx_queues[rx_queue_id];
if (!rxq || !rxq->q_set) {
PMD_DRV_LOG(ERR, "RX queue %u not available or setup",
rx_queue_id);
return -EINVAL;
}
if (rxq->rx_deferred_start)
PMD_DRV_LOG(WARNING, "RX queue %u is deferred start",
rx_queue_id);
//申请rxq中的sw_ring中的mbuf
err = i40e_alloc_rx_queue_mbufs(rxq);
if (err) {
PMD_DRV_LOG(ERR, "Failed to allocate RX queue mbuf");
return err;
}
/* Init the RX tail register. */
I40E_PCI_REG_WRITE(rxq->qrx_tail, rxq->nb_rx_desc - 1);
err = i40e_switch_rx_queue(hw, rxq->reg_idx, TRUE);
if (err) {
PMD_DRV_LOG(ERR, "Failed to switch RX queue %u on",
rx_queue_id);
i40e_rx_queue_release_mbufs(rxq);
i40e_reset_rx_queue(rxq);
return err;
}
dev->data->rx_queue_state[rx_queue_id] = RTE_ETH_QUEUE_STATE_STARTED;
return 0;
}
int
i40e_alloc_rx_queue_mbufs(struct i40e_rx_queue *rxq)
{
struct i40e_rx_entry *rxe = rxq->sw_ring;
uint64_t dma_addr;
uint16_t i;
for (i = 0; i < rxq->nb_rx_desc; i++) {
volatile union i40e_rx_desc *rxd;
//从mempool中申请一个mbuf
struct rte_mbuf *mbuf = rte_mbuf_raw_alloc(rxq->mp);
if (unlikely(!mbuf)) {
PMD_DRV_LOG(ERR, "Failed to allocate mbuf for RX");
return -ENOMEM;
}
rte_mbuf_refcnt_set(mbuf, 1);
mbuf->next = NULL;
mbuf->data_off = RTE_PKTMBUF_HEADROOM;
mbuf->nb_segs = 1;
mbuf->port = rxq->port_id;
//获取mbuf的物理地址
dma_addr =
rte_cpu_to_le_64(rte_mbuf_data_iova_default(mbuf));
//写描述符
rxd = &rxq->rx_ring[i];
rxd->read.pkt_addr = dma_addr;
rxd->read.hdr_addr = 0;
#ifndef RTE_LIBRTE_I40E_16BYTE_RX_DESC
rxd->read.rsvd1 = 0;
rxd->read.rsvd2 = 0;
#endif /* RTE_LIBRTE_I40E_16BYTE_RX_DESC */
rxe[i].mbuf = mbuf;
}
return 0;
}