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。
可能有如下方法:
$ 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
------------ 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 来使用。
许多问题的复杂性不在于问题本身,而是难以获取到足够根因闭环的信息。这些信息的缺失一部分来源于框架自身实现与信息收集工具的缺失,另一部分来自于维护者对框架的理解的不足。
对这两点不足进行反思并持续改进,当维护者对框架实现了然于胸,可用的工具也一抓一大把时,许多看似困难的问题将会迎刃而解。