dpdk 基于 rte_tailq_head 在多进程间共享内存的原理

rte_mem_config 结构

dpdk 多进程共享内存机制依赖 rte_mem_config 文件共享内存描述信息,其中有如下结构定义:

   struct rte_tailq_head tailq_head[RTE_MAX_TAILQ]; /**< Tailqs for objects */

此结构共享 tailq_head,用以支持在多进程之间查找特定类型的数据结构地址,本文以 rte_ring 为例描述下其中的原理。

rte_tailq_head 结构

rte_tailq_head 是 dpdk 中双向链表的表头,dpdk 中为多个功能不同的内存结构注册不同的 tailq 链表用于多进程间共享。此结构定义如下:

struct rte_tailq_head {
	struct rte_tailq_entry_head tailq_head; /**< NOTE: must be first element */
	char name[RTE_TAILQ_NAMESIZE];
};

rte_mem_config 中保存所有的 rte_tailq_head 信息,每一个 rte_tailq_head 的 name 唯一标识一个 tailq。 dpdk 通过共享 rte_tailq_head 头并在大页上分配 rte_tailq_head 链表中链入的每一个 rte_tailq_entry 来实现多进程间基于 tailq 获取共享内存信息功能。

核心数据结构为 rte_tailq_elem 与 rte_tailq_entry,其定义如下:

struct rte_tailq_elem {
	/**
	 * Reference to head in shared mem, updated at init time by
	 * rte_eal_tailqs_init()
	 */
	struct rte_tailq_head *head;
	TAILQ_ENTRY(rte_tailq_elem) next;
	const char name[RTE_TAILQ_NAMESIZE];
};

struct rte_tailq_entry {
	TAILQ_ENTRY(rte_tailq_entry) next; /**< Pointer entries for a tailq list */
	void *data; /**< Pointer to the data referenced by this tailq entry */
};

核心过程如下:

  1. 不同模块通过调用 EAL_REGISTER_TAILQ 宏来注册不同的本地 rte_tailq_elem 结构到本地 rte_tailq_elem_head 表头中,此宏会定义一个构造函数在 main 函数之前执行,完成注册过程,注册时 rte_tailq_elem 的 head 字段为 NULL。

  2. 在内存初始化完成后,dpdk 为每一个注册的 rte_tailq_elem 结构在 rte_mem_config 分配一个 rte_tailq_head 并更新相关数据结构
    primary 进程执行的操作如下:

    在 rte_mem_config 的 tailq_head 数组中为每一个注册的 rte_tailq_elem 结构分配一个 rte_tailq_head 链表头,并将 rte_tailq_elem 中的 name 字段拷贝到分配出的 rte_tailq_head 结构的 name 字段中,然后更新 rte_tailq_elem 的 head 字段为分配的 rte_tailq_head 地址。

    secondary 进程执行的操作如下:

    遍历每个注册的 rte_tailq_elem 结构,使用此结构的 name 字段在 rte_mem_config 共享内存的 tailq_head 数组中匹配,找到匹配到的 tailq_head 结构并更新 rte_tailq_elem 的 head 字段.

  3. 每种类型共享数据在创建时,在大页内存上分配一个 rte_tailq_entry 结构,此结构以 name 字符串标识每个分配的结构,将此结构的 data 字段指向创建的共享数据起始地址,然后链入到链表中。

  4. 需要在其它进程中获取基于 tailq 描述的内存结构时,获取到本地 rte_tailq_elem 中的 rte_tailq_head 地址并强转为一个 rte_tailq_entry_head 结构,然后使用名称遍历此链表的每个结构进行匹配,匹配到后返回 rte_tailq_entry 的 data 字段中存储的数据地址即可。

以 rte_ring 结构为例描述实现细节

rte_ring.c 中定义了如下 tailq 相关信息:

TAILQ_HEAD(rte_ring_list, rte_tailq_entry);

static struct rte_tailq_elem rte_ring_tailq = {
	.name = RTE_TAILQ_RING_NAME,
};
EAL_REGISTER_TAILQ(rte_ring_tailq)

TAILQ_HEAD 定义了一个 rte_ring_list 类型链表头,链表的每一个成员数据类型为 rte_tailq_entry。rte_ring_tailq 为本地定义的一个 rte_tailq_elem, name 填充 rte_ring 的特定名称——“RTE_RING”,调用 RTE_REGISTER_TAILQ 创建一个构造函数将 rte_tailq_elem 注册到 rte_tailq_elem_head 链表中。

在 rte_eal_init 函数调用完成后,本地 rte_ring_tailq 变量的 head 字段指向 rte_mem_config 的 tailq_head 数组中的一项。

rte_ring_create 函数主要逻辑如下:

struct rte_ring *
rte_ring_create(const char *name, unsigned count, int socket_id,
		unsigned flags)
{
	char mz_name[RTE_MEMZONE_NAMESIZE];
	struct rte_ring *r;
	struct rte_tailq_entry *te;
	const struct rte_memzone *mz;
	ssize_t ring_size;
	int mz_flags = 0;
	struct rte_ring_list* ring_list = NULL;
	
	..............................................................
	ring_list = RTE_TAILQ_CAST(rte_ring_tailq.head, rte_ring_list);
	.............................................................
	ret = snprintf(mz_name, sizeof(mz_name), "%s%s",
		RTE_RING_MZ_PREFIX, name);
  ...............................................................
	te = rte_zmalloc("RING_TAILQ_ENTRY", sizeof(*te), 0);
  ..............................................................
	mz = rte_memzone_reserve_aligned(mz_name, ring_size, socket_id,
					 mz_flags, __alignof__(*r));
	if (mz != NULL) {
		r = mz->addr;
		/* no need to check return value here, we already checked the
		 * arguments above */
		rte_ring_init(r, name, requested_count, flags);

		te->data = (void *) r;
		r->memzone = mz;

		TAILQ_INSERT_TAIL(ring_list, te, next);
	}
	.............................................................
}

name 参数标识此 rte_ring 的名称,rte_ring_create 函数的主要逻辑如下:

  1. 访问 rte_ring_elem 的 head 字段并强转为一个 rte_ring_list head 类型地址,保存为 ring_list
  2. 格式化 name 并保存到 mz_name 中,然后在大页内存中申请一个 rte_tailq_entry 结构,结构地址保存在 te 变量中
  3. 为 rte_ring 预留空间,得到一个 memzone 区域,调用 rte_ring_init 初始化此区域
  4. 将 te 变量的 data 字段赋值为 memzone 区域的地址以保存 rte_ring 的地址
  5. 将 te 插入到 ring_list 指向的链表中

rte_ring_lookup 函数的主要代码如下:

struct rte_ring *
rte_ring_lookup(const char *name)
{
	struct rte_tailq_entry *te;
	struct rte_ring *r = NULL;
	struct rte_ring_list *ring_list;

	ring_list = RTE_TAILQ_CAST(rte_ring_tailq.head, rte_ring_list);
  .............................................................
	TAILQ_FOREACH(te, ring_list, next) {
		r = (struct rte_ring *) te->data;
		if (strncmp(name, r->name, RTE_RING_NAMESIZE) == 0)
			break;
	}
  .............................................................
}

主要逻辑如下:

  1. 获取 rte_ring_list head 地址
  2. 遍历 rte_ring_list 中的所有 rte_tailq_entry,获取每个 rte_tailq_entry 的 data 数据,此数据指向一个 rte_ring 结构,使用传入的 name 参数与 rte_ring 结构的 name 字段进行匹配,命中则返回 rte_ring 结构

为什么需要使用 rte_tailq_head 共享内存?

dpdk 多进程共享内存描述信息的机制 这篇文章中,我描述了 dpdk 中用于多进程间共享内存的方法,主要包含如下两种:

  1. memzone
  2. rte_tailq_head

memzone 这种实现中,在共享内存描述文件 rte_mem_config 中分配 memzone 结构,能够分配的数量在编译时就确定了,不能动态的扩展,当使用这种机制分配大量特定类型的数据结构时,很容易耗尽全局 memzone 结构导致分配失败,这种机制适合分配数量有限特定数据类型的场景。

而 rte_tailq_head 这种机制却没有这种限制,在单个 rte_tailq_head 维持的链表中,在内存充足时可以持续分配特定类型的数据结构,适用于需要大量分配某种特定类型共享数据结构的场景。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值