dpdk学习三

dpdk学习三

1、rte_ring库

rte_ring 没有无限大小的链表,而是具有以下属性:

  • 先进先出
  • 最大尺寸是固定的,对象存储在一个表中
  • 对象可以是 4 字节大小的倍数的指针或元素
  • 无锁实现
  • 多消费者或单消费者出队
  • 多生产者或单生产者入队
  • Bulk dequeue - 如果成功,则将指定数量的对象出列;否则失败
  • Bulk enqueue - 如果成功,则将指定数量的对象入队;否则失败
  • Burst dequeue - 如果无法满足指定的计数,则将最大可用对象出列
  • Burst enqueue - 如果无法满足指定的计数,则将最大可用对象入队

这种数据结构相对于链表队列的优势如下:

  • 快点; 只需要一个 32 位的 Compare-And-Swap 指令,而不是几个指针大小的 Compare-And-Swap 指令。
  • 比完整的无锁队列更简单。
  • 适应批量入队/出队操作。由于对象存储在表中,因此多个对象的出队将不会产生与链接队列中一样多的缓存未命中。此外,许多对象的批量出队不会比简单对象的出队花费更多。

缺点:

  • 大小是固定的
  • 与链表队列相比,拥有许多环在内存方面的成本更高。一个空环至少包含 N 个对象。

环的简化表示显示在消费者和生产者的头尾指针中,指向存储在数据结构中的对象。

在这里插入图片描述

struct rte_memzone {

#define RTE_MEMZONE_NAMESIZE 32       /**< Maximum length of memory zone name.*/
	char name[RTE_MEMZONE_NAMESIZE];  /**< Name of the memory zone. */

	rte_iova_t iova;                  /**< Start IO address. */
	RTE_STD_C11
	union {
		void *addr;                   /**< Start virtual address. */
		uint64_t addr_64;             /**< Makes sure addr is always 64-bits */
	};
	size_t len;                       /**< Length of the memzone. */

	uint64_t hugepage_sz;             /**< The page size of underlying memory */

	int32_t socket_id;                /**< NUMA socket ID. */

	uint32_t flags;                   /**< Characteristics of this memzone. */
} __rte_packed;

struct rte_ring_headtail {
	volatile uint32_t head;      /**< prod/consumer head. */
	volatile uint32_t tail;      /**< prod/consumer tail. */
	RTE_STD_C11
	union {
		/** sync type of prod/cons */
		enum rte_ring_sync_type sync_type;
		/** deprecated -  True if single prod/cons */
		uint32_t single;
	};
};

union __rte_ring_hts_pos {
	/** raw 8B value to read/write *head* and *tail* as one atomic op */
	uint64_t raw __rte_aligned(8);
	struct {
		uint32_t head; /**< head position */
		uint32_t tail; /**< tail position */
	} pos;
};

union __rte_ring_rts_poscnt {
	/** raw 8B value to read/write *cnt* and *pos* as one atomic op */
	uint64_t raw __rte_aligned(8);
	struct {
		uint32_t cnt; /**< head/tail reference counter */
		uint32_t pos; /**< head/tail position */
	} val;
};

struct rte_ring_hts_headtail {
	volatile union __rte_ring_hts_pos ht;
	enum rte_ring_sync_type sync_type;  /**< sync type of prod/cons */
};

struct rte_ring_rts_headtail {
	volatile union __rte_ring_rts_poscnt tail;
	enum rte_ring_sync_type sync_type;  /**< sync type of prod/cons */
	uint32_t htd_max;   /**< max allowed distance between head/tail */
	volatile union __rte_ring_rts_poscnt head;
};

struct rte_ring {
	char name[RTE_RING_NAMESIZE] __rte_cache_aligned;
	/**< Name of the ring. */
	int flags;               /**< Flags supplied at creation. */
	const struct rte_memzone *memzone;
			/**< Memzone, if any, containing the rte_ring */
	uint32_t size;           /**< Size of ring. */
	uint32_t mask;           /**< Mask (size-1) of ring. */
	uint32_t capacity;       /**< Usable size of ring */

	char pad0 __rte_cache_aligned; /**< empty cache line */

	/** Ring producer status. */
	RTE_STD_C11
	union {
		struct rte_ring_headtail prod;
		struct rte_ring_hts_headtail hts_prod;
		struct rte_ring_rts_headtail rts_prod;
	}  __rte_cache_aligned;

	char pad1 __rte_cache_aligned; /**< empty cache line */

	/** Ring consumer status. */
	RTE_STD_C11
	union {
		struct rte_ring_headtail cons;
		struct rte_ring_hts_headtail hts_cons;
		struct rte_ring_rts_headtail rts_cons;
	}  __rte_cache_aligned;

	char pad2 __rte_cache_aligned; /**< empty cache line */
};

1.1 rte_ring剖析

环形结构由头尾两对组成;一种供生产者使用,一种供消费者使用。以下部分的数字将它们称为 prod_head、prod_tail、cons_head 和 cons_tail。

1.1.1 单一生产者队列入队

生产者只有一个,初始状态是让 prod_head 和 prod_tail 指向同一位置。

1.1.1.1 入队第一步(单一生产者队列)

首先,将 ring->prod_head和 ring->cons_tail 复制到局部变量中。prod_next 局部变量指向表的下一个元素,或者在批量入队的情况下指向之后的几个元素。

如果环中没有足够的空间(通过检查 cons_tail 检测到),则返回错误。

在这里插入图片描述

1.1.1.2 入队第二步(单一生产者队列)

第二步,修改环结构中的ring->prod_head,使其指向与prod_next相同的位置。

添加的对象被复制到环(obj4)中。

在这里插入图片描述

1.1.1.3 入队第三步(单一生产者队列)

一旦对象被添加到环中,环结构中的 ring->prod_tail 被修改为指向与ring->prod_head相同的位置。入队操作完成。

在这里插入图片描述

1.1.2 单一消费者出队

只有一个消费者, 初始状态是让 cons_head 和 cons_tail 指向同一个位置。

1.1.2.1 出队第一步(单一消费者)

首先,将 ring->cons_head 和 ring->prod_tail 复制到局部变量中。cons_next 局部变量指向表的下一个元素,或者在批量出队的情况下指向之后的几个元素。

如果环中没有足够的对象(通过检查 prod_tail 检测到),则返回错误。

在这里插入图片描述

1.1.2.2 出队第二步(单一消费者)

第二步,修改环结构中的ring->cons_head,使其指向与cons_next相同的位置。

出队的对象 (obj1) 被复制到用户给出的指针中。

在这里插入图片描述

1.1.2.3 出队第三步(单一消费者)

最后修改环结构中的ring->cons_tail,使其指向与ring->cons_head相同的位置。出队操作完成。

在这里插入图片描述

1.1.3 多生产者入队

多生产者,初始状态是让 prod_head 和 prod_tail 指向同一位置。

1.1.3.1 多个生产者入队第一步

在两个内核上,ring->prod_head和 ring->cons_tail 都被复制到局部变量中。prod_next 局部变量指向表的下一个元素,或者在批量入队的情况下指向之后的几个元素。

如果环中没有足够的空间(通过检查 cons_tail 检测到),则返回错误。

在这里插入图片描述

1.1.3.2 多个生产者入队第二步

第二步,修改环结构中的ring->prod_head,使其指向与prod_next相同的位置。此操作使用比较和交换 (CAS) 指令完成,该指令以原子方式执行以下操作:

  • 如果 ring->prod_head 与局部变量 prod_head 不同,则 CAS 操作失败,代码从第一步重新开始。

  • 否则,将 ring->prod_head 设置为本地 prod_next,CAS 操作成功,继续处理。

    图中,操作在核心 1 上成功,第一步在核心 2 上重新启动。

在这里插入图片描述

1.1.3.3 多个生产者入队第三步

CAS 操作在核心 2 上重试成功。

核心 1 更新环的一个元素(obj4),核心 2 更新另一个元素(obj5)。

在这里插入图片描述

1.1.3.4 多个生产者入队第四步

现在每个核心都想更新 ring->prod_tail。只有 ring->prod_tail 等于 prod_head 局部变量,核心才能更新它。这仅适用于核心 1。操作在核心 1 上完成。

在这里插入图片描述

1.1.3.5 多个生产者入队第五步

一旦 ring->prod_tail 被核心 1 更新,核心 2 也被允许更新它。该操作也在核心 2 上完成。

在这里插入图片描述

1.1.4 索引计算

在前面的图中,prod_head、prod_tail、cons_head 和 cons_tail 索引用箭头表示。在实际实现中,这些值不像假设的那样介于 0 和 size(ring)-1 之间。索引在 0 到 2^32 -1 之间,当我们访问对象表(环本身)时,我们会屏蔽它们的值。32 位模还意味着如果结果超出 32 位数字范围,则对索引的操作(例如加/减)将自动进行 2^32 模。

以下是两个示例,有助于解释如何在环中使用索引。

在这里插入图片描述

在这里插入图片描述

代码始终保持生产者和消费者之间的距离在 0 和 size(ring)-1 之间。由于这个属性,我们可以在模 32 位基数中的 2 个索引值之间进行减法:这就是索引溢出不是问题的原因。

在任何时候,entries 和 free_entries 都在 0 和 size(ring)-1 之间。

uint32_t entries = (prod_tail - cons_head);
uint32_t free_entries = (mask + cons_tail - prod_head);

1.2 生产者/消费者同步模式

rte_ring 支持生产者和消费者的不同同步模式。这些模式可以在环创建/初始化时通过flags 参数指定。这应该可以帮助用户以最适合其特定使用场景的方式配置环。目前支持的模式:

1.2.1 MP/MC(默认)

多生产者(/多消费者)模式。这是环的默认入队 (/dequeue) 模式。在这种模式下,多个线程可以将对象排队(/dequeue)到(/from)环。对于“经典”DPDK 部署(每个核心一个线程),这通常是最合适和最快的同步模式。作为一个众所周知的限制 - 它可以在某些过度使用的场景中执行得非常纯粹。

1.2.2. SP/SC

单一生产者(/single-consumer)模式。在这种模式下,一次只允许一个线程将对象入队(/dequeue)到(/from)环。

1.2.3 MP_RTS/MC_RTS

具有Relaxed Tail Sync (RTS) 模式的多生产者 (/multi-consumer)。与原始 MP/MC 算法的主要区别在于,尾值不是由完成入队/出队的每个线程增加,而是仅由最后一个线程增加。这允许线程避免在环尾值上旋转,将实际尾值更改留给给定实例的最后一个线程。该技术有助于避免尾更新时的 Lock-Waiter-Preemption (LWP) 问题,并改善过度使用系统的平均入队/出队时间。为了实现 RTS,每个入队(/出队)操作需要 2 个 64 位 CAS:一个用于头部更新,第二个用于尾部更新。相比之下,原始 MP/MC 算法需要一个 32 位 CAS 用于头部更新和等待/旋转尾部值。

1.2.4 MP_HTS/MC_HTS

具有Head/Tail Sync(HTS) 模式的多生产者 (/multi-consumer)。在这种模式下,入队/出队操作是完全序列化的:在任何给定时刻,只有一个入队/出队操作可以进行。这是通过允许线程head.value 仅在入队/出队操作时进行更改来实现的。头和尾值都以原子方式更新(作为一个 64 位值)。为实现头部更新例程使用 64 位 CAS。该技术还避免了尾更新时的 Lock-Waiter-Preemption (LWP) 问题,并有助于改善过度使用场景中的环入队/出队行为。完全序列化的生产者/消费者的另一个优点 - 它提供了为 rte_ring 实现 MT 安全 peek API 的能力。head.value == tail.value

1.3 环peek

对于带有序列化生产者/消费者(HTS 同步模式)的环,可以将公共入队/出队 API 拆分为两个阶段:

  • 入队/出队开始
  • 入队/出队完成

这允许用户在不将其移除的情况下检查环中的对象(又名 MT 安全peek),并在实际入队之前为环中的对象保留空间。请注意,此 API 仅适用于两种同步模式:

  • 单一生产者/单一消费者 (SP/SC)
  • 具有头/尾同步 (HTS) 的多生产者/多消费者

创建/初始化环并选择适当的同步模式是用户的责任。作为使用示例:

/* read 1 elem from the ring: */
uint32_t n = rte_ring_dequeue_bulk_start(ring, &obj, 1, NULL);
if (n != 0) {
    /* examine object */
    if (object_examine(obj) == KEEP)
        /* decided to keep it in the ring. */
        rte_ring_dequeue_finish(ring, 0);
    else
        /* decided to remove it from the ring. */
        rte_ring_dequeue_finish(ring, n);
}

请注意,在完成之前_start__finish_没有其他线程可以继续进行入队(/出队)操作_finish_

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值