EAL提供了一个malloc API用于申请任意大小内存。
这个API的目的是提供类似malloc的功能,以允许从hugepage中分配内存并方便应用程序移植。
通常,这些类型的分配操作不应该在数据面处理中进行,因为他们比基于池的分配慢,并且在分配和释放路径中使用了锁操作。但是,他们可以在配置代码中使用。
1.Cookies
当CONFIG_RTE_MALLOC_DEBUG开启时,分配的内存包括保护字段,这个字段用于帮助识别缓冲区溢出。
2.对齐和NUMA限制
接口rte_malloc()传入一个对齐参数,该参数用于请求在该值的倍数上对齐的内存区域(这个值必须是2的幂次)。
在支持NUMA的系统上,对rte_malloc()接口调用将返回在调用函数的Core所在的插槽上分配的内存。DPDK还提供了另一组API,以允许在指定NUMA插槽上直接显式分配内存,或者分配另一个NUAM插槽上的内存。
3.用例
这个API旨在由初始化时需要类似malloc功能的应用程序调用。
需要在运行时分配/释放数据,在应用程序的快速路径中,应该使用内存池库。
4.内部实现
4.1数据结构
Malloc库中内部使用两种数据结构类型:
struct malloc_heap:用于在每个插槽上跟踪可用内存空间
struct malloc_elem:库内部分配和释放空间跟踪的基本要素
4.1.1malloc_heap
数据结构malloc_heap用于管理每个插槽上的可用内存空间。在内部,每个NUMA节点有一个堆结构,这允许我们根据此线程运行的NUMA节点为线程分配内存。虽然这并不能保证在NUMA节点上使用内存,但是它并不比内存总是在固定或随机节点上的方案更糟。
堆结构及其关键字段和功能描述如下:
lock:需要锁来同步对堆结构的访问。假定使用链表来跟踪堆中的可用空间,我们需要一个锁
来防止多个线程同时处理该链表。
free_head:指向这个malloc堆的空闲结点链表中的第一个元素。
4.1.2malloc_elem
数据结构malloc_elem用作各种内存块的通用头结构。 它以三种不同的方式使用,如上图所示:
1.作为一个释放/申请内存的头部,正常使用
2.作为内存块内部填充头
3.作为内存结尾标记
结构中重要的字段和使用方法如下所述:
1.heap:这个指针指向了该内存块从哪个堆申请。它被用于正常的内存块,当他们被释放时,将新释放的块添加到堆的空闲列表中。
2.prev:这个指针用于指向紧跟着当前memseg的头元素。当释放一个内存块时,该指针用于引用上一个内存块,检查上一个块是否也是空闲。如果空闲,则将两个空闲块合并成一个大块。
3.next_free:这个指针用于将空闲块列表连接在一起。它用于正常的内存块,在malloc() 接口中用于找到一个合适的空闲块申请出来,在free() 函数中用于将内存块添加到空闲链表。
4.state:该字段可以有三个可能值:FREE, BUSY 或 PAD。前两个是指示正常内存块的分配状态,后者用于指示元素结构是在块开始填充结束时的虚拟结构,即,由于对齐限制,块内的数据开始的地方不在块本身的开始处。在这种情况下,pad头用于定位块的实际malloc元素头。对于结尾的结构,这个字段总是BUSY,它确保没有元素在释放之后搜索超过memseg的结尾以供其它块合并到更大的空闲块。
5.pad:这个字段为块开始处的填充长度。在正常块头部情况下,它被添加到头结构的结尾,以给出数据区的开始地址,即在malloc上传回的地址。在填充虚拟头部时,存储相同的值,并从虚拟头部的地址中减去实际块头部的地址。
6.size:数据块的大小,包括头部本身。对于结尾结构,这个大小需要指定为0,虽然从未使用。对于正在释放的正常内存块,使用此大小值替代 “next” 指针,以标识下一个块的存储位置,在FREE情况下,可以合并两个空闲块。
4.2内存申请
在EAL初始化时,所有memseg都将作为malloc堆的一部分进行设置。这个设置包括在BUSY状态结束时放置一个虚拟结构,如果启用了CONFIG_RTE_MALLOC_DEBUG,它可能包含一个哨兵值,并在开始时为每个memseg指定一个适当的元素头。然后将FREE元素添加到malloc堆的空闲链表中。
当应用程序调用类似malloc功能的函数时,malloc函数将首先为调用线程索引lcore_config结构,并确定该线程的NUMA 节点。NUMA 节点将作为参数传给heap_alloc()函数,用于索引malloc_heap 结构数组。参与索引参数还有大小、类型、对齐方式和边界参数。
函数heap_alloc()将扫描堆的空闲链表,尝试找到一个适用于所请求的大小、对齐方式和边界约束的内存块。
当已经识别出合适的空闲元素时,将计算要返回给用户的指针。紧跟在该指针之前的内存的高速缓存行填充了一个malloc_elem头部。由于对齐和边界约束,在元素的开头和结尾可能会有空闲的空间,这将导致已下行为:
1.检查尾随空间。如果尾部空间足够大,例如 > 128 字节,那么空闲元素将被分割。否则,仅仅忽略它(浪费空间)。
2.检查元素开始处的空间。如果起始处的空间很小, <=128 字节,那么使用填充头,这部分空间被浪费。但是,如果空间很大,那么空闲元素将被分割。
从现有元素的末尾分配内存的优点是不需要调整空闲链表,空闲链表中现有元素仅调整大小指针,并且后面的元素使用 “prev” 指针重定向到新创建的元素位置。
4.3内存释放
要释放内存,将指向数据区开始的指针传递给free函数。从该指针中减去malloc_elem结构的大小,以获得内存块元素头部。如果这个头部类型是PAD,那么进一步减去pad长度,以获得整个块的正确元素头。
从这个元素头中,我们获得指向块所分配的堆的指针及必须被释放的位置,以及指向前一个元素的指针,并且通过size 字段,可以计算下一个元素的指针。 这意味着我们永远不会有两个相邻的FREE 内存块,因为他们总是会被合并成一个大的块。