DPDK的内存管理工作主要分布在几个大的部分:大页初始化与管理,内存管理。使用大页可以减少页表开销,是为了尽量减少TBL miss导致的性能损失。基于大页,DPDK又进一步细化管理这部分内存,使得分配,回收更加方便。
大页内存的基本原理在前面已经解释过了,这里就不在继续。
首先熟悉一下DPDK 内存相关的结构体:
- struct rte_mem_config(rte_eal_memconfig.h)
这个数据结构mmap 到文件/var/run /.rte_config中,主/从进程通过这个文件访问实现对这个数据结构的共享。
在每个程序内,使用rte_config .mem_config 访问这个结构。
/**
* the structure for the memory configuration for the RTE.
* Used by the rte_config structure. It is separated out, as for multi-process
* support, the memory details should be shared across instances
*/
struct rte_mem_config {
volatile uint32_t magic; /**< Magic number - Sanity check. */
/* memory topology */
uint32_t nchannel; /**< Number of channels (0 if unknown). */
uint32_t nrank; /**< Number of ranks (0 if unknown). */
/**
* current lock nest order
* - qlock->mlock (ring/hash/lpm)
* - mplock->qlock->mlock (mempool)
* Notice:
* *ALWAYS* obtain qlock first if having to obtain both qlock and mlock
*/
rte_rwlock_t mlock; /**< only used by memzone LIB for thread-safe. */
rte_rwlock_t qlock; /**< used for tailq operation for thread safe. */
rte_rwlock_t mplock; /**< only used by mempool LIB for thread-safe. */
uint32_t memzone_cnt; /**< Number of allocated memzones */
/* memory segments and zones */
struct rte_memseg memseg[RTE_MAX_MEMSEG]; /**< Physmem descriptors. */
struct rte_memzone memzone[RTE_MAX_MEMZONE]; /**< Memzone descriptors. */
struct rte_tailq_head tailq_head[RTE_MAX_TAILQ]; /**< Tailqs for objects */
/* Heaps of Malloc per socket */
struct malloc_heap malloc_heaps[RTE_MAX_NUMA_NODES];
/* address of mem_config in primary process. used to map shared config into
* exact same address the primary process maps it.
*/
uint64_t mem_cfg_addr;
} __attribute__((__packed__));
- struct rte_memseg (rte_memory.h)
rte_mem_config 结构体中的memseg 数组是维护物理地址的,在上面讲到struct hugepage结构对每个hugepage物理页面都存储了它在程序里面的虚存地址。memseg 数组的作用是将物理地址、虚拟地址都连续的hugepage,并且都在同一个socket,pagesize 也相同的hugepage页面集合,把它们都划在一个memseg结构里面,这样做的好处就是优化内存。
这个结构体的信息都是从hugepage页表数组里面获得的。
/**
* Physical memory segment descriptor.
*/
struct rte_memseg {
phys_addr_t phys_addr; /**< Start physical address. 这个memseg的包含的所有的hugepage页面的起始地址*/
union {
void *addr; /**< Start virtual address.这些hugepage页面的起始的虚存地址,由于物理地址和虚拟地址是相同的,这个值应该等于phys_addr。 */
uint64_t addr_64; /**< Makes sure addr is always 64 bits */
};
#ifdef RTE_LIBRTE_IVSHMEM
phys_addr_t ioremap_addr; /**< Real physical address inside the VM */
#endif
size_t len; /**< Length of the segment. memseg的包含的空间size*/
uint64_t hugepage_sz; /**< The pagesize of underlying memory 大页内存的size 2M /1G? */
int32_t socket_id; /**< NUMA socket ID. */
uint32_t nchannel; /**< Number of channels. */
uint32_t nrank; /**< Number of ranks. */
#ifdef RTE_LIBRTE_XEN_DOM0
/**< store segment MFNs */
uint64_t mfn[DOM0_NUM_MEMBLOCK];
#endif
} __rte_packed;
- struct rte_memzone (rte_memzone.h)
这个结构体是DPDK内存管理最终向客户程序提供的基础接口
通过rte_memzone_reverse 可以获取基于dpdk hugepage 的属于同一个物理cpu的,物理内存连续同时虚拟内存也连续的一块地址。在里面找满足socket_id的要求,最小的memseg,从中划出来一块内存,然后把这块内存记录到memzone里面。一般情况下,各个功能分配内存都使用这个函数分配一个memzone
rte_ring/rte_malloc/rte_mempool等组件都是依赖于rte_memzone 组件实现的。
/**
* A structure describing a memzone, which is a contiguous portion of
* physical memory identified by a name.
*/
struct rte_memzone {
#define RTE_MEMZONE_NAMESIZE 32 /**< Maximum length of memory zone name.*/
char name[RTE_MEMZONE_NAMESIZE]; /**< Name of the memory zone. */
phys_addr_t phys_addr; /**< Start physical address. */
union {
void *addr; /**< Start virtual address. */
uint64_t addr_64; /**< Makes sure addr is always 64-bits */
};
#ifdef RTE_LIBRTE_IVSHMEM
phys_addr_t ioremap_addr; /**< Real physical address inside the VM */
#endif
size_t len; /**< Length of the memzone. */
uint64_t hugepage_sz; /**< The page size of underlying memory */
int32_t socket_id; /**< NUMA socket ID. */
uint32_t flags; /**< Characteristics of this memzone. */
uint32_t memseg_id; /**< Memseg it belongs. */
} __attribute__((__packed__));
- rte_memzone_reverse()函数:
一般情况下,各个功能分配内存都使用这个函数分配一个memzone
const structrte_memzone *
rte_memzone_reserve(constchar *name, size_t len, int socket_id,
unsigned flags)
{
1、这里就在free_memseg数组里面找满足socket_id的要求,最小的memseg,从这里面划出来一块内存,当然这时候free_memseg需要更新把这部分内存刨出去。
2、把这块内存记录到memzone里面。
}
上面提到rte_memzone_reserve分配内存后,同时在rte_mem_config.memzone数组里面分配一个元素保存它。对于从memseg中分配内存,以memzone为单位来分配,对于所有的分配情况,都记录在memzone数组里面,当然这个数组是多进程共享,大家都能看到。
在rte_mem_config结构里面memzone_idx 变量记录当前分配的memzone,每申请一次这个变量+1。