在文件src/vpp/vnet/main.c中,主函数首先临时创建1M大小的堆heap使用。最后,销毁临时heap,根据解析到的VPP所配置heap大小和页面大小,创建主heap。
int
main (int argc, char *argv[])
{
/* temporary heap */
clib_mem_init (0, 1 << 20);
/* destroy temporary heap and create main one */
clib_mem_destroy ();
if ((main_heap = clib_mem_init_with_page_size (main_heap_size,
main_heap_log2_page_sz)))
{
/* Figure out which numa runs the main thread */
__os_numa_index = clib_get_current_numa_node ();
if (default_log2_hugepage_sz != CLIB_MEM_PAGE_SZ_UNKNOWN)
clib_mem_set_log2_default_hugepage_size (default_log2_hugepage_sz);
/* and use the main heap as that numa's numa heap */
clib_mem_set_per_numa_heap (main_heap);
以上调用的两个内存初始化函数,实际上都是调用了clib_mem_init_internal函数,不同之处在于clib_mem_init使用了默认的页面大小。
__clib_export void *
clib_mem_init (void *memory, uword memory_size)
{
return clib_mem_init_internal (memory, memory_size,
CLIB_MEM_PAGE_SZ_DEFAULT);
}
__clib_export void *
clib_mem_init_with_page_size (uword memory_size,
clib_mem_page_sz_t log2_page_sz)
{
return clib_mem_init_internal (0, memory_size, log2_page_sz);
}
函数clib_mem_main_init获取系统的hugepage页面大小,以及NUMA信息。函数clib_mem_create_heap_internal创建主heap。
/* Initialize CLIB heap based on memory/size given by user.
Set memory to 0 and CLIB will try to allocate its own heap. */
static void *
clib_mem_init_internal (void *base, uword size,
clib_mem_page_sz_t log2_page_sz)
{
clib_mem_heap_t *h;
clib_mem_main_init ();
h = clib_mem_create_heap_internal (base, size, log2_page_sz,
1 /*is_locked */ , "main heap");
clib_mem_set_heap (h);
if (mheap_trace_main.lock == 0)
clib_spinlock_init (&mheap_trace_main.lock);
return h;
以上的调用,首个参数base都是空,根据页面大小对齐要分配的内存大小,由函数clib_mem_vm_map_internal执行内存分配,成功的话返回分配的内存地址base。
static clib_mem_heap_t *
clib_mem_create_heap_internal (void *base, uword size,
clib_mem_page_sz_t log2_page_sz, int is_locked,
char *name)
{
clib_mem_heap_t *h;
int sz = sizeof (clib_mem_heap_t);
if (base == 0)
{
log2_page_sz = clib_mem_log2_page_size_validate (log2_page_sz);
size = round_pow2 (size, clib_mem_page_bytes (log2_page_sz));
base = clib_mem_vm_map_internal (0, log2_page_sz, size, -1, 0,
"main heap");
if (base == CLIB_MEM_VM_MAP_FAILED)
return 0;
flags = CLIB_MEM_HEAP_F_UNMAP_ON_DESTROY;
}
else
log2_page_sz = CLIB_MEM_PAGE_SZ_UNKNOWN;
base起始地址开头为clib_mem_heap_t结构,进行初始化,随后跳过此结构以及名称长度的空间,将随后空间生成mspace结构,以供mspace_malloc分配内存使用,例如pool_get的内存分配。
h = base;
h->base = base;
h->size = size;
h->log2_page_sz = log2_page_sz;
h->flags = flags;
sz = strlen (name);
strcpy (h->name, name);
sz = round_pow2 (sz + sizeof (clib_mem_heap_t), 16);
h->mspace = create_mspace_with_base (base + sz, size - sz, is_locked);
mspace_disable_expand (h->mspace);
clib_mem_poison (mspace_least_addr (h->mspace),
mspace_footprint (h->mspace));
return h;
在函数clib_mem_vm_map_internal中,首先设置mmap标志MAP_SHARED,mmap的空间变动在其它进程中可见。
void *
clib_mem_vm_map_internal (void *base, clib_mem_page_sz_t log2_page_sz,
uword size, int fd, uword offset, char *name)
{
clib_mem_main_t *mm = &clib_mem_main;
clib_mem_vm_map_hdr_t *hdr;
uword sys_page_sz = 1ULL << mm->log2_page_sz;
int mmap_flags = MAP_FIXED, is_huge = 0;
if (fd != -1)
{
mmap_flags |= MAP_SHARED;
log2_page_sz = clib_mem_get_fd_log2_page_size (fd);
if (log2_page_sz > mm->log2_page_sz)
is_huge = 1;
}
else
{
mmap_flags |= MAP_PRIVATE | MAP_ANONYMOUS;
根据log2_page_sz的值,决定是否使用巨页hugepage内存,使用巨页,mmap需要标志MAP_HUGETLB。
if (log2_page_sz == mm->log2_page_sz)
log2_page_sz = CLIB_MEM_PAGE_SZ_DEFAULT;
switch (log2_page_sz)
{
case CLIB_MEM_PAGE_SZ_UNKNOWN:
/* will fail later */
break;
case CLIB_MEM_PAGE_SZ_DEFAULT:
log2_page_sz = mm->log2_page_sz;
break;
case CLIB_MEM_PAGE_SZ_DEFAULT_HUGE:
mmap_flags |= MAP_HUGETLB;
log2_page_sz = mm->log2_default_hugepage_sz;
is_huge = 1;
break;
default:
mmap_flags |= MAP_HUGETLB;
mmap_flags |= log2_page_sz << MAP_HUGE_SHIFT;
is_huge = 1;
}
}
if (log2_page_sz == CLIB_MEM_PAGE_SZ_UNKNOWN)
return CLIB_MEM_VM_MAP_FAILED;
根据页面大小log2_page_sz对齐要分配的内存size大小,保留size大小的内存映射,返回起始地址base。再次调用mmap将映射地址修改为读写权限,已经以上指定的mmap_flags标志属性。
对于巨页的情况,mlock将映射的内存锁住,以防移动到swap区。
size = round_pow2 (size, 1ULL << log2_page_sz);
base = (void *) clib_mem_vm_reserve ((uword) base, size, log2_page_sz);
if (base == (void *) ~0)
return CLIB_MEM_VM_MAP_FAILED;
base = mmap (base, size, PROT_READ | PROT_WRITE, mmap_flags, fd, offset);
if (base == MAP_FAILED)
return CLIB_MEM_VM_MAP_FAILED;
if (is_huge && (mlock (base, size) != 0))
{
munmap (base, size);
return CLIB_MEM_VM_MAP_FAILED;
}
将base之前的sys_page_sz大小的内存单独映射在本进程内部,MAP_FIXED标志表明映射地址hdr需要等于指定地址(base - sys_page_sz)。如果last_map存在,将其权限变更为可读写,将以上映射的hdr赋值为其next指针,随后,将last_map的权限恢复为不可访问。
hdr = mmap (base - sys_page_sz, sys_page_sz, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0);
if (hdr != base - sys_page_sz)
{
munmap (base, size);
return CLIB_MEM_VM_MAP_FAILED;
}
map_lock ();
if (mm->last_map)
{
mprotect (mm->last_map, sys_page_sz, PROT_READ | PROT_WRITE);
mm->last_map->next = hdr;
mprotect (mm->last_map, sys_page_sz, PROT_NONE);
}
else
mm->first_map = hdr;
初始化clib_mem_vm_map_hdr_t结构的hdr,将其作为新的内存映射尾部赋值给last_map,初始化hdr的起始地址,页面大小和数量等信息。最后将尾部的sys_page_sz大小的hdr空间的权限设置为不可访问(PROT_NONE)。
clib_mem_unpoison (hdr, sys_page_sz);
hdr->next = 0;
hdr->prev = mm->last_map;
snprintf (hdr->name, CLIB_VM_MAP_HDR_NAME_MAX_LEN - 1, "%s", (char *) name);
mm->last_map = hdr;
hdr->base_addr = (uword) base;
hdr->log2_page_sz = log2_page_sz;
hdr->num_pages = size >> log2_page_sz;
hdr->fd = fd;
hdr->name[CLIB_VM_MAP_HDR_NAME_MAX_LEN - 1] = 0;
mprotect (hdr, sys_page_sz, PROT_NONE);
map_unlock ();
clib_mem_unpoison (base, size);
return base;
以下VPP命令查看主heap的分配信息,以及使用情况。
vpp# show memory main-heap verbose
Thread 0 vpp_main
base 0x7f7f69de9000, size 2g, locked, unmap-on-destroy, name 'main heap'
page stats: page-size 4K, total 524288, mapped 39960, not-mapped 0, unknown 484328
numa 0: 39960 pages, 156.09m bytes
total: 1.99G, used: 93.37M, free: 1.91G, trimmable: 1.91G
free chunks 764 free fastbin blks 0
max total allocated 1.99G