在 k8s 多个 pod 中运行 dpdk secondary 进程时,启动某个 dpdk secondary 进程时有如下报错信息:
EAL: Cannot initialize local memory map
EAL: FATAL: Cannot init memory
EAL: Cannot init memory
dpdk 版本:dpdk-19.11
修改 dpdk secondary 进程 eal 启动参数,添加 –log-level=lib.eal:debug 选项,重新启动 pod,此时报错信息如下:
EAL: rte_fbarray_init(): couldn't lock /var/run/dpdk/rte/fbarray_memseg-2048k-0-0_1: Resource temporarily unavailable
EAL: Cannot initialize local memory map
EAL: FATAL: Cannot init memory
EAL: Cannot init memory
报错信息表明获取 /var/run/dpdk/rte 目录下特定文件的 lock 失败,需要阅读代码进一步定位。
此问题中,函数调用栈如下所示:
rte_eal_init
rte_eal_memory_init
eal_memalloc_init
rte_memseg_list_walk
secondary_msl_create_walk
rte_fbarray_init
rte_memseg_list_walk 函数遍历 mcfg->memsegs 中的所有 rte_memseg_list,执行 secondary_msl_create_walk 函数,为每一个 secondary 进程以 pid 为后缀在 /var/run/dpdk/rte 下面创建 fbarray_memseg-xxx_pid 文件。
问题出在 secondary_msl_create_walk 函数调用 rte_fbarray_init 初始化一个 fbarray 结构过程中,相关代码如下:
/* create distinct fbarrays for each secondary */
snprintf(name, RTE_FBARRAY_NAME_LEN, "%s_%i",
primary_msl->memseg_arr.name, getpid());
ret = rte_fbarray_init(&local_msl->memseg_arr, name,
primary_msl->memseg_arr.len,
primary_msl->memseg_arr.elt_sz);
if (ret < 0) {
RTE_LOG(ERR, EAL, "Cannot initialize local memory map\n");
return -1;
}
上述代码中,使用 memseg_arr 中的 name 字段与进程的 pid 拼接成 name 字段,对照上文添加了 debug 信息后的输出,fbarray_memseg-2048k-0-0 为 memseg_arr 中 name 字段的值,1 为进程的 pid。
rte_fbarray_init 函数中关键代码如下:
else if (flock(fd, LOCK_EX | LOCK_NB)) {
RTE_LOG(DEBUG, EAL, "%s(): couldn't lock %s: %s\n",
__func__, path, strerror(errno));
rte_errno = EBUSY;
goto fail;
}
/* take out a non-exclusive lock, so that other processes could
* still attach to it, but no other process could reinitialize
* it.
*/
if (flock(fd, LOCK_SH | LOCK_NB)) {
rte_errno = errno;
goto fail;
}
第一次 flock 函数调用以非阻塞方式获取 fbarray_memseg-xxx-pid 文件的互斥锁,第二次 flock 函数调用以非阻塞方式获取 fbarray_memseg-xxx-pid 文件的共享锁。
第一次 flock 函数调用保证每个 secondary 自己的 fbarray_memseg 映射文件只被初始化一次,第二次 flock 调用保证在当前 secondary 进程初始化完成后 fbarray_memseg-xxx-pid 文件能够被其它进程 attach。
在 k8s 多个 pod 中运行的 dpdk secondary 进程有自己独立的 pid namespace 空间,然而却需要在同一个共享目录—— /var/run/dpdk/rte 中以 pid 为后缀区分创建独立的文件名并获取互斥锁。
每个 pod 中运行的 dpdk secondary 进程的 pid 可能会相同,此时只能有一个进程能够获取到互斥锁,其它 pid 相同的进程会启动失败。
方案一:
修改 k8s pod 配置,dpdk 进程所在的 pod 共享同一个 pid namespace。
方案二:
修改 dpdk 代码,使用其它标识保障 fbarray_memseg 映射文件的唯一性。
dpdk mp_channel 框架会为每个 dpdk 进程在 /var/run/dpdk/rte 目录中 bind 一个 unix 本地套接字,dpdk secondary 进程本地套接字名称生成代码如下:
if (rte_eal_process_type() == RTE_PROC_SECONDARY)
snprintf(peer_name, sizeof(peer_name),
"%d_%"PRIx64, getpid(), rte_rdtsc());
上述代码使用 pid + tsc 时间来标识,即就两个进程的 pid 相同,只要在同一个机器上运行,tsc 的值是不同的,这样也不会存在冲突。
既然如此,是否可以修改 fbarray_memseg 后缀生成代码,也加上 tsc 来唯一标识呢?
答案是 yes,上文中的方案二可以使用此种方案实现。
本文描述了 dpdk 多进程程序在 k8s 环境运行的一个问题,从 dpdk 代码实现上来看,dpdk 对这种场景的支持并不好,可能还存在其它问题。上半年我写的 程序启动顺序引发的血案之 dpdk 进程死锁 这篇博客描述了 dpdk 多进程共享用户态锁潜在的死锁问题,在这个场景中也存在。这些问题比较隐晦,缺少对 dpdk 底层原理的理解则很难在方案设计前期识别出来,是一个风险点。