如何快速定位 dpdk memzone 内存泄露问题?

问题现象

dpdk-19.11 secondary 进程无法启动,报错信息中有如下内容:

memzone_reserve_aligned_thread_unsafe(): No more room in config

确认问题为创建 memzone 失败,却不清楚为什么失败,需要定位原因。

阅读代码与调试

使用关键字 No more room in config 在 dpdk 源码中搜索,找到 memzone_reserve_aligned_thread_unsafe 函数的如下代码:

	struct rte_memzone *mz;
	struct rte_mem_config *mcfg;
	struct rte_fbarray *arr;

	/* get pointer to global configuration */
	mcfg = rte_eal_get_configuration()->mem_config;
	arr = &mcfg->memzones;

	/* no more room in config */
	if (arr->count >= arr->len) {
		RTE_LOG(ERR, EAL, "%s(): No more room in config\n", __func__);
		rte_errno = ENOSPC;
		return NULL;
	}

此代码首先获取到 rte_mem_config 的地址,然后访问其中的 memzones 字段,此字段是一个 rte_fbarray 结构。此后判断 rte_fbarray 结构的 count 值大于等于 len 时打印 No more room in config 的错误信息后返回。

这个 memzones 的 rte_fbarray 的默认大小定义如下:

common_base:98:CONFIG_RTE_MAX_MEMZONE=2560

gdb 调试发现,rte_mem_config memzones 字段中的 count 值与 len 值相等,均为 2560,表明此时 rte_fbarray 的所有表项都已经耗尽,大概率是存在 memzone 内存泄露。

继续阅读代码确定 memzone_reserve_aligned_thread_unsafe 函数将会被 rte_memzone_reserve 函数调用,rte_memzone_reserve 函数会传递一个唯一的 name 字符串以标识每一个分配出来的 memzone。

复现过程

多次复现发现,当 primary 进程正常运行时,secondary 进程启动后退出达到一定的次数就会报 No more room in config 的问题,表明 secondary 进程每次启动后退出都会泄露一部分 memzone,当泄露完成后就无法创建新的 memzone

如何找到泄露的位置?

可能有如下方法:

  1. dump rte_mem_config 中的 memzones rte_fbarray 结构,查看到哪个 memzone 的名称前缀出现的次数最多,使用这个名称在代码中搜索即可。
  2. 修改 rte_memzone_reserve 函数代码,在其中添加打印 name 参数,重启 primary 进程,运行两次 secondary 进程找到名称最多的 name 前缀,搜索代码。
  3. 重启 primary 进程,运行一次 dpdk secondary 进程后退出,然后执行 od -c /var/run/dpdk/rte/fbarray_memzone 命令并将输出信息保存为 1.txt,再次运行一次 dpdk secondary 进程,然后继续执行 od -c /var/run/dpdk/rte/fbarray_memzone 命令并将输出信息保存为 2.txt,使用文本对比工具对比差异,找到新增的 memzone 的名称,搜索代码。
    od -c fbarray_memzone 文件内容示例如下:
    $ od -c ./fbarray_memzone
    0000000   r   t   e   _   e   t   h   _   d   e   v   _   d   a   t   a
    0000020  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
    0000040 200   l 226 233 003  \0  \0  \0 200   l   6   X 375   ?  \0  \0
    0000060   @ 320 006  \0  \0  \0  \0  \0  \0  \0      \0  \0  \0  \0  \0
    0000100  \0  \0  \0  \0  \0  \0  \0  \0   M   P   _   v   p   p       p
    0000120   o   o   l       0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
    0000140  \0  \0  \0  \0  \0  \0  \0  \0 200   < 225 233 003  \0  \0  \0
    0000160 200   <   5   X 375   ?  \0  \0  \0 001  \0  \0  \0  \0  \0  \0
    0000200  \0  \0      \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
    0000220   M   P   _   v   p   p       p   o   o   l       0       (   n
    0000240   o       c   a   c   h   e   )  \0  \0  \0  \0  \0  \0  \0  \0
    0000260   @   : 225 233 003  \0  \0  \0   @   :   5   X 375   ?  \0  \0
    0000300  \0 001  \0  \0  \0  \0  \0  \0  \0  \0      \0  \0  \0  \0  \0
    
  4. 编译 dpdk_proc_info 程序,重启 primary 进程,添加 – -m 参数运行两次 dpdk_proc_info 程序并分别保存日志信息为 1.txt 与 2.txt,使用文本对比工具对比差异,找到新增的 memzone 名称,搜索代码。
    dpdk_proc_info -m 选项输出的 memzone 信息示例如下:
    ------------ MEMORY_ZONES -------------
    Zone 0: name:<rte_pdump_stats>, len:0x400040, virt:0x1005fffc0, socket_id:0, flags:0
    physical segments used:
      addr: 0x100400000 iova: 0x100400000 len: 0x200000 pagesz: 0x200000
      addr: 0x100600000 iova: 0x100600000 len: 0x200000 pagesz: 0x200000
      addr: 0x100800000 iova: 0x100800000 len: 0x200000 pagesz: 0x200000
    Zone 1: name:<MP_mb_pool_0>, len:0x182100, virt:0x10047de40, socket_id:0, flags:0
    physical segments used:
      addr: 0x100400000 iova: 0x100400000 len: 0x200000 pagesz: 0x200000
    Zone 2: name:<RG_MP_mb_pool_0>, len:0x20180, virt:0x10045db80, socket_id:0, flags:0
    physical segments used:
      addr: 0x100400000 iova: 0x100400000 len: 0x200000 pagesz: 0x200000
    Zone 3: name:<MP_mb_pool_0_0>, len:0x1724880, virt:0x1010db780, socket_id:0, flags:0
    

上面列举了四种方法,每种方法都能使用,但是实施过程需要花费的时间以及依赖的基础知识与工具有所差别。

高版本定位过程

dpdk 20 年的如下 commit 引入 trace 子系统来 trace dpdk 内部函数调用:

commit 27db82c709dc466537b8437b0dec0619880d59c9
Author: Jerin Jacob <jerinj@marvell.com>
Date:   Thu Apr 23 00:33:19 2020 +0530

    trace: introduce new subsystem

在支持 trace 的 dpdk 高版本可以使能 memzone 相关函数的 trace point,运行一次 secondary 进程,查看 trace 信息中 memzone 函数调用信息,找到没有 free 的 memzone 名称搜索代码来定位报本文描述的问题。

babeltrace 解析的 trace 日志内容示例如下:

# babeltrace /root/dpdk-traces/rte-2022-11-12-PM-06-20-08/
[18:20:09.265384247] (+?.?????????) lib.eal.memzone.reserve: { cpu_id = 0x0, name = "dpdk-testpmd" }, { name = "rte_pdump_stats", len = 0x400008, socket_id = 0, flags = 0x0, align = 0x40, bound = 0x0, mz = 0x100007000 }
[18:20:09.282972301] (+0.017588054) lib.eal.memzone.lookup: { cpu_id = 0x0, name = "dpdk-testpmd" }, { name = "mbuf_user_pool_ops", memzone = 0x0 }
[18:20:09.282972613] (+0.000000312) lib.eal.memzone.lookup: { cpu_id = 0x0, name = "dpdk-testpmd" }, { name = "mbuf_platform_pool_ops", memzone = 0x0 }
[18:20:09.282985000] (+0.000012387) lib.eal.memzone.reserve: { cpu_id = 0x0, name = "dpdk-testpmd" }, { name = "MP_mb_pool_0", len = 0x182100, socket_id = 0, flags = 0x6, align = 0x40, bound = 0x0, mz = 0x100007048 }
[18:20:09.283240882] (+0.000255882) lib.eal.memzone.lookup: { cpu_id = 0x0, name = "dpdk-testpmd" }, { name = "mbuf_user_pool_ops", memzone = 0x0 }
[18:20:09.283241032] (+0.000000150) lib.eal.memzone.lookup: { cpu_id = 0x0, name = "dpdk-testpmd" }, { name = "mbuf_platform_pool_ops", memzone = 0x0 }
[18:20:09.283244566] (+0.000003534) lib.eal.memzone.reserve: { cpu_id = 0x0, name = "dpdk-testpmd" }, { name = "RG_MP_mb_pool_0", len = 0x20180, socket_id = 0, flags = 0x0, align = 0x40, bound = 0x0, mz = 0x100007090 }
[18:20:09.285245820] (+0.002001254) lib.eal.memzone.reserve: { cpu_id = 0x0, name = "dpdk-testpmd" }, { name = "MP_mb_pool_0_0", len = 0x172487F, socket_id = 0, flags = 0x6, align = 0x40, bound = 0x0, mz = 0x1000070D8 }
......................................................................................
[18:20:10.436267496] (+1.150217836) lib.eal.memzone.free: { cpu_id = 0x0, name = "dpdk-testpmd" }, { name = "MP_mb_pool_0_0", addr = 0x101EDB780, rc = 0 }
[18:20:10.436276496] (+0.000009000) lib.eal.memzone.free: { cpu_id = 0x0, name = "dpdk-testpmd" }, { name = "RG_MP_mb_pool_0", addr = 0x10125DB80, rc = 0 }

包含 lib.eal.memzone.reserve 的项目为 memzone 创建的 trace 日志,包含 lib.eal.memzone.free 的项目为 memzone free 的日志,找到只有创建没有 free 的 memzone 就相当于找到了泄露的位置。

这种方式也是一种通用的定位此种 dpdk 内部数据结构泄露的方法,当相关数据结构的申请与释放接口支持 trace point 时可以直接使用,不支持时可以手动添加 trace point 来使用。

总结

许多问题的复杂性不在于问题本身,而是难以获取到足够根因闭环的信息。这些信息的缺失一部分来源于框架自身实现与信息收集工具的缺失,另一部分来自于维护者对框架的理解的不足。

对这两点不足进行反思并持续改进,当维护者对框架实现了然于胸,可用的工具也一抓一大把时,许多看似困难的问题将会迎刃而解。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
除了 rte_malloc_heap_dump() 函数之外,DPDK 还提供了其他一些 API 来统计内存使用情况,包括: 1. rte_malloc_stats():该函数可以获取 DPDK 内存池的统计信息,包括内存池中的对象数量、空闲对象数量、已分配对象数量等等,可以用于监视内存池的使用情况。 2. rte_malloc_validate():该函数可以验证指定地址是否在 DPDK 内存堆中,如果地址无效,函数返回值为负数。 3. rte_malloc_dump_heaps():该函数可以打印所有非空 DPDK 内存堆的使用情况,包括内存堆的名称、总大小、已分配大小、剩余大小等等。 下面是一个简单的例子,展示了如何使用这些函数来统计 DPDK 内存使用情况: ```c #include <stdio.h> #include <rte_malloc.h> int main(int argc, char **argv) { // 初始化 DPDK 环境 rte_eal_init(argc, argv); // 分配一块内存 void *mem = rte_malloc(NULL, 1024, 0); if (mem == NULL) { printf("Failed to allocate memory!\n"); return -1; } // 输出内存使用情况 struct rte_malloc_stats stats; rte_malloc_stats(&stats); printf("DPDK memory stats:\n"); printf("Total heap size: %lu bytes\n", stats.total_heap_size); printf("Free heap size: %lu bytes\n", stats.free_heap_size); printf("Allocated heap size: %lu bytes\n", stats.allocd_heap_size); printf("Total allocated objects: %lu\n", stats.alloc_count); printf("Total freed objects: %lu\n", stats.free_count); // 验证指定地址是否在内存堆中 if (rte_malloc_validate(mem) < 0) { printf("Invalid memory address!\n"); } // 输出所有内存堆的使用情况 rte_malloc_dump_heaps(stdout); // 释放内存 rte_free(mem); return 0; } ``` 在上述代码中,我们首先使用 rte_eal_init() 函数初始化 DPDK 环境,然后使用 rte_malloc() 函数分配了一块 1024 字节的内存。接着,我们使用 rte_malloc_stats() 函数获取 DPDK 内存池的统计信息,并将其打印到标准输出中。然后,我们使用 rte_malloc_validate() 函数验证分配的内存地址是否有效。最后,我们使用 rte_malloc_dump_heaps() 函数打印所有非空 DPDK 内存堆的使用情况,将结果输出到标准输出中。最后,我们使用 rte_free() 函数释放了分配的内存。 当运行该程序时,屏幕上将输出类似以下的信息: ``` DPDK memory stats: Total heap size: 268435456 bytes Free heap size: 267435392 bytes Allocated heap size: 1021064 bytes Total allocated objects: 1 Total freed objects: 0 DPDK memory heap Heap name: rte_malloc_heap Total size: 268435456 bytes Free size: 267435392 bytes Free blocks: 2 Allocated blocks: 3 Minimum alloc size: 64 bytes Maximum alloc size: 268435392 bytes Total allocations: 1024 bytes Total frees: 0 bytes ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值