
本文已同步至:
个人博客:itwakeup.com
微信公众号:vpp与dpdk研习社(vpp_dpdk_lab)
在高性能网络数据平面编程中,频繁的内存分配和释放操作往往会成为性能瓶颈。传统的malloc/free不仅性能开销大,还容易造成内存碎片。VPP基于向量(vector)和位图(bitmap)实现了一套高效的内存池(Pool)机制来解决这些问题。
VPP的内存池实际上是一个特殊的向量,内存布局如下:
+------------------+------------------+------------------+-----+
| pool_header_t | Element 0 | Element 1 | ... |
+------------------+------------------+------------------+-----+
^ ^
| |
header pool pointer (用户可见)用户操作的pool指针指向实际的元素数组,而pool_header_t通过pool_header()函数获取,它复用了向量的header机制。
typedef struct
{
/** Bitmap of indices of free objects. */
uword *free_bitmap;
/** Vector of free indices. One element for each set bit in bitmap. */
u32 *free_indices;
/* The following fields are set for fixed-size, preallocated pools */
/** Maximum size of the pool, in elements */
u32 max_elts;
} pool_header_t;这个结构体是内存池的元数据头,每个pool都有一个对应的header。关键字段说明:
池数组: [obj0] [obj1] [obj2] [obj3] [obj4]
索引: 0 1 2 3 4
free_bitmap: 0 1 0 1 0 (1=空闲, 0=使用中)
free_indices: [1, 3] (只存储空闲的索引)扩容发生在pool_get()时,当同时满足:
free_indices 为空)max_elts == 0)VPP采用 1.5倍增长策略,即新容量 = 当前需求 + 当前需求 / 2
步骤:
扩容后地址可能发生改变,之前保存的指针会失效!例如:
entry_t *old_ptr = &pool[5]; // 保存指针
...
pool_get(pool, new_entry); // 触发扩容,pool地址改变,此时old_ptr是野指针!安全做法:保存索引而不是指针
u32 index = 5; // 保存索引
...
pool_get(pool, new_entry); // 触发扩容,
entry_t *ptr = &pool[index]; // 通过索引访问新地址#define pool_init_fixed(P, E)P: 池指针E: 最大元素数量#define pool_alloc(P, N)
#define pool_alloc_aligned(P, N, A)
#define pool_alloc_aligned_heap(P, N, A, H)P: 池指针(输入/输出,可能会被修改)N: 要预分配的元素数量(uword类型)A: 内存对齐要求(可选)H: 指定堆(可选,0表示默认堆)#define pool_dup(P)
#define pool_dup_aligned(P, A)P: 源池指针A: 内存对齐要求#define pool_free(p)p: 池指针(输入/输出,释放后会被设为NULL)#define pool_get(P, E)P: 池指针(输入/输出,可能会被修改)E: 输出参数,指向分配的对象(对象指针类型)关键实现 (_pool_get函数):
static_always_inline void
_pool_get (void **pp, void **ep, uword align, int zero, uword elt_sz)
{
// 优先从空闲列表分配
if (n_free)
{
uword index = ph->free_indices[n_free - 1];
e = p + index * elt_sz;
ph->free_bitmap = clib_bitmap_andnoti_notrim (ph->free_bitmap, index);
vec_set_len (ph->free_indices, n_free - 1);
clib_mem_unpoison (e, elt_sz);
goto done;
}
// 如果是固定池且没有空闲对象,报错
if (ph->max_elts)
{
clib_warning ("can't expand fixed-size pool");
os_out_of_memory ();
}
// 否则扩展池
p = _vec_realloc_internal (p, len + 1, &va);
e = p + len * elt_sz;
}#define pool_get_zero(P, E)P: 池指针(输入/输出,可能会被修改)E: 输出参数,指向分配的对象pool_get相同,但会将分配的对象内存清零#define pool_get_aligned(P, E, A)P: 池指针(输入/输出,可能会被修改)E: 输出参数,指向分配的对象A: 内存对齐要求(字节数,通常是2的幂次)#define pool_put(P, E)P: 池指针(输入)E: 要释放的对象指针(必须是通过pool_get从该池分配的对象)index = E - P关键实现 (_pool_put_index函数):
static_always_inline void
_pool_put_index (void *p, uword index, uword elt_sz)
{
pool_header_t *ph = pool_header (p);
ASSERT (index < ph->max_elts ? ph->max_elts : vec_len (p));
ASSERT (!pool_is_free_index (p, index));
// 在位图中标记为空闲
ph->free_bitmap = clib_bitmap_ori_notrim (ph->free_bitmap, index);
// 添加到空闲索引列表
if (ph->max_elts)
{
u32 len = _vec_len (ph->free_indices);
vec_set_len (ph->free_indices, len + 1);
ph->free_indices[len] = index;
}
else
vec_add1 (ph->free_indices, index);
clib_mem_poison (p + index * elt_sz, elt_sz);
}#define pool_put_index(P, I)P: 池指针(输入,不会被修改)I: 要释放的对象索引(u32或uword类型)pool_put(P, &P[I])always_inline uword pool_elts (void *v)v: 池指针(void *类型,可以传入任何类型的池)pool_len(p) - vec_len(free_indices)#define pool_len(p)p: 池指针#define pool_free_elts(P)P: 池指针#define pool_is_free(P, E)
int pool_is_free_index (void *p, uword index)P: 池指针E: 对象指针(pool_is_free)或 索引(pool_is_free_index)index: 要检查的索引(uword类型)use-after-free错误#define pool_max_len(P) vec_max_len(P)P: 池指针#define pool_bytes(P)P: 池指针always_inline void pool_validate (void *v)v: 池指针#define pool_foreach(VAR, POOL)VAR: 迭代器变量,类型与池元素类型相同的指针POOL: 要遍历的池使用示例:
proc_t *procs; // 进程池
proc_t *proc;
pool_foreach (proc, procs)
{
if (proc->state != PROC_STATE_RUNNING)
continue;
// 处理运行中的进程
process_running_proc(proc);
}注意事项:
pool_flush#define pool_foreach_index(i, v)i: 迭代器变量,uword类型,存储当前对象的索引v: 要遍历的池#define pool_elt_at_index(p, i)p: 池指针i: 元素索引(uword类型)i处对象的指针&p[i] 但带有安全检查#define pool_foreach_pointer(e, p)e: 迭代器变量,存储当前指针指向的值p: 指针类型的池(池元素是指针)session_t **pool)时使用。指针池使用该接口和通用接口的区别:
1)使用通用接口遍历,需要手动解引用:
object_t **object_pool = 0; // 指针池
object_t **obj_ptr; // 迭代器是指针的指针
pool_foreach(obj_ptr, object_pool) {
object_t *obj = *obj_ptr; // 需要手动解引用
process(obj);
}2)使用该接口,自动解引用:
object_t **object_pool = 0; // 指针池
object_t *obj; // 迭代器直接是对象指针
pool_foreach_pointer(obj, object_pool) {
// 自动解引用,直接使用!
process(obj);
}#define pool_flush(VAR, POOL, BODY)VAR: 迭代器变量,类型与池元素类型相同的指针POOL: 要清空的池BODY: 对每个元素执行的操作(代码块)使用示例:
buffer_entry_t *entry;
pool_flush(entry, buffer_pool, {
// 在删除前释放资源
if (entry->data)
free_buffer_data(entry->data);
});
// 执行完后,buffer_pool为空但仍可用以下示例均以独立文件编译,假设你已编译好vpp,gcc参数根据你的环境修改。
gcc -o test_pool_simple test_pool_simple.c \
-I./build-root/install-vpp_debug-native/vpp/include \
-L./build-root/install-vpp_debug-native/vpp/lib64 \
-Wl,-rpath,./build-root/install-vpp_debug-native/vpp/lib64 \
-lvppinfra该示例为可动态扩容的内存池:
pool_get的时候扩容。#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vppinfra/pool.h>
#include <vppinfra/mem.h>
typedef struct test_entry
{
u32 id;
u8 data[32];
} test_entry_t;
int
main (int argc, char **argv)
{
clib_mem_init (0, 64 << 20); // 64MB heap
test_entry_t *pool = 0;
test_entry_t *entry = 0;
printf ("=== VPP Pool Test Program ===\n");
printf ("Initial pool pointer: %p\n\n", pool);
// 1. Add elements
printf ("--- Adding Elements ---\n");
for (int i = 0; i < 4; i++)
{
pool_get (pool, entry);
entry->id = i + 100;
memset (entry->data, i + 'A', sizeof (entry->data));
printf ("Added element: id=%d, index=%ld, pool_addr=%p\n", entry->id,
entry - pool, pool);
}
// 2. Display pool status
printf ("\n--- Pool Status ---\n");
printf ("Total elements (pool_len): %u\n", pool_len (pool));
printf ("Active elements (pool_elts): %lu\n", pool_elts (pool));
printf ("Free elements (pool_free_elts): %lu\n", pool_free_elts (pool));
// 3. Iterate all elements
printf ("\n--- Iterate All Elements (pool_foreach) ---\n");
pool_foreach (entry, pool)
{
printf ("Element[%ld]: id=%d, data[0]=%c\n", entry - pool, entry->id,
entry->data[0]);
}
// 4. Delete some elements
printf ("\n--- Deleting Elements ---\n");
entry = pool_elt_at_index (pool, 1);
printf ("Delete element: index=1, id=%d\n", entry->id);
pool_put (pool, entry);
entry = pool_elt_at_index (pool, 3);
printf ("Delete element: index=3, id=%d\n", entry->id);
pool_put (pool, entry);
// 5. Display status again
printf ("\n--- Pool Status After Deletion ---\n");
printf ("Total elements: %u\n", pool_len (pool));
printf ("Active elements: %lu\n", pool_elts (pool));
printf ("Free elements: %lu\n", pool_free_elts (pool));
// 6. Iterate by index
printf ("\n--- Iterate by Index (pool_foreach_index) ---\n");
u32 index;
pool_foreach_index (index, pool)
{
entry = pool_elt_at_index (pool, index);
printf ("Element[%d]: id=%d, data[0]=%c\n", index, entry->id,
entry->data[0]);
}
// 7. Add new elements (reuse freed slots)
printf ("\n--- Adding New Elements (Reuse Free Slots) ---\n");
pool_get (pool, entry);
entry->id = 200;
memset (entry->data, 'X', sizeof (entry->data));
printf ("Added new element: id=%d, index=%ld (Notice: index reused!)\n",
entry->id, entry - pool);
pool_get (pool, entry);
entry->id = 201;
memset (entry->data, 'Y', sizeof (entry->data));
printf ("Added new element: id=%d, index=%ld\n", entry->id, entry - pool);
// 8. Final iteration
printf ("\n--- Final Iteration ---\n");
pool_foreach (entry, pool)
{
printf ("Element[%ld]: id=%d, data[0]=%c\n", entry - pool, entry->id,
entry->data[0]);
}
// 9. Display final status
printf ("\n--- Final Pool Status ---\n");
printf ("Total elements: %u\n", pool_len (pool));
printf ("Active elements: %lu\n", pool_elts (pool));
printf ("Free elements: %lu\n", pool_free_elts (pool));
// 10. Cleanup pool
printf ("\n--- Freeing Pool ---\n");
pool_free (pool);
printf ("Pool freed, pointer=%p\n", pool);
printf ("\n=== Test Completed ===\n");
return 0;
}运行结果如下,需要注意的是,Free elements包含了已释放的和预留未被使用的元素之和:
Initial pool pointer: (nil)
--- Adding 4 Elements ---
Added element: id=100, index=0, pool_addr=0x7faf8f1e6420
Added element: id=101, index=1, pool_addr=0x7faf8f1e6420
Added element: id=102, index=2, pool_addr=0x7faf8f1e6420
Added element: id=103, index=3, pool_addr=0x7faf8f1e6420
--- Pool Status ---
Total elements (pool_len): 4
Active elements (pool_elts): 4
Free elements (pool_free_elts): 2
--- Iterate All Elements (pool_foreach) ---
Element[0]: id=100, data[0]=A
Element[1]: id=101, data[0]=B
Element[2]: id=102, data[0]=C
Element[3]: id=103, data[0]=D
--- Deleting Elements ---
Delete element: index=1, id=101
Delete element: index=3, id=103
--- Pool Status After Deletion ---
Total elements: 4
Active elements: 2
Free elements: 4
--- Iterate by Index (pool_foreach_index) ---
Element[0]: id=100, data[0]=A
Element[2]: id=102, data[0]=C
--- Adding New Elements (Reuse Free Slots) ---
Added new element: id=200, index=3 (Notice: index reused!)
Added new element: id=201, index=1
--- Final Iteration ---
Element[0]: id=100, data[0]=A
Element[1]: id=201, data[0]=Y
Element[2]: id=102, data[0]=C
Element[3]: id=200, data[0]=X
--- Final Pool Status ---
Total elements: 4
Active elements: 4
Free elements: 2
--- Freeing Pool ---
Pool freed, pointer=(nil)该示例为可固定大小的内存池:
Initial pool pointer: 0x7f81aeaf1420
--- Adding 4 Elements ---
Added element: id=100, index=0, pool_addr=0x7f81aeaf1420
Added element: id=101, index=1, pool_addr=0x7f81aeaf1420
Added element: id=102, index=2, pool_addr=0x7f81aeaf1420
pool is empty, add element failed
--- Pool Status ---
Total elements (pool_len): 3
Active elements (pool_elts): 3
Free elements (pool_free_elts): 0
--- Iterate All Elements (pool_foreach) ---
Element[0]: id=100, data[0]=A
Element[1]: id=101, data[0]=B
Element[2]: id=102, data[0]=C
--- Deleting Elements ---
Delete element: index=1, id=101
--- Pool Status After Deletion ---
Total elements: 3
Active elements: 2
Free elements: 1
--- flush pool ---
Element[4198528]: id=100, data[0]=A
free something
Element[4198528]: id=102, data[0]=C
free something
--- Freeing Pool ---
Pool freed, pointer=(nil)运行结果:
Initial pool pointer: 0x7f02953cc420
--- Adding 4 Elements ---
Added element: id=100, index=0, pool_addr=0x7f02953cc420
Added element: id=101, index=1, pool_addr=0x7f02953cc420
Added element: id=102, index=2, pool_addr=0x7f02953cc420
pool is empty, add element failed
--- Pool Status ---
Total elements (pool_len): 3
Active elements (pool_elts): 3
Free elements (pool_free_elts): 0
--- Iterate All Elements (pool_foreach) ---
Element[0]: id=100, data[0]=A
Element[1]: id=101, data[0]=B
Element[2]: id=102, data[0]=C
--- Deleting Elements ---
Delete element: index=1, id=101
--- Pool Status After Deletion ---
Total elements: 3
Active elements: 2
Free elements: 1
--- flush pool ---
Element[0]: id=100, data[0]=A
free something
Element[2]: id=102, data[0]=C
free something
--- Freeing Pool ---
Pool freed, pointer=(nil)原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。