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


    rte_mem_config 结构

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

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

    此结构共享 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];
    };
    
    • 1
    • 2
    • 3
    • 4

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    核心过程如下:

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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);
    	}
    	.............................................................
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    主要逻辑如下:

    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 维持的链表中,在内存充足时可以持续分配特定类型的数据结构,适用于需要大量分配某种特定类型共享数据结构的场景。

  • 相关阅读:
    java计算机毕业设计企业公开招聘系统源程序+mysql+系统+lw文档+远程调试
    vue中v-model应用于表单元素
    通讯网关软件022——利用CommGate X2MQTT实现MQTT访问MSSQL服务器
    sqlServer 检测慢 sql
    在项目中应用设计模式的实践指南
    自动微分原理
    ES6 从入门到精通 # 08:扩展的对象的功能
    Mysql中数据表的约束
    Apache的安装与目录结构详细解说
    Java概述
  • 原文地址:https://blog.csdn.net/Longyu_wlz/article/details/127622090