DPDK ring库:环形缓冲区的解剖

目录

ring库

FreeBSD *中的Ring实施参考

Linux *中的无锁环形缓冲区

附加功能

名称

用例

环形缓冲区的解剖

单一生产者入队

单一消费者出队

多个生产者入队

模数32位索引

生产者/消费者同步模式

MP / MC(默认一项)

SP / SC

MP_RTS / MC_RTS

MP_HTS / MC_HTS

环窥API

参考资料


ring库

环允许管理队列。除了具有无限大小的链表之外,rte_ring具有以下属性:

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

与链接列表队列相比,此数据结构的优点如下:

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

缺点:

  • 大小是固定的
  • 就内存而言,拥有多个环比链接列表队列要花费更多。一个空环至少包含N个对象。

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

rte_ring结构体


/**
 * An RTE ring structure.
 *
 * The producer and the consumer have a head and a tail index. The particularity
 * of these index is that they are not between 0 and size(ring). These indexes
 * are between 0 and 2^32, and we mask their value when we access the ring[]
 * field. Thanks to this assumption, we can do subtractions between 2 index
 * values in a modulo-32bit base: that's why the overflow of the indexes is not
 * a problem.
 */
struct rte_ring {
	/*
	 * Note: this field kept the RTE_MEMZONE_NAMESIZE size due to ABI
	 * compatibility requirements, it could be changed to RTE_RING_NAMESIZE
	 * next time the ABI changes
	 */
	char name[RTE_MEMZONE_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. */
	struct rte_ring_headtail prod __rte_cache_aligned;
	char pad1 __rte_cache_aligned; /**< empty cache line */

	/** Ring consumer status. */
	struct rte_ring_headtail cons __rte_cache_aligned;
	char pad2 __rte_cache_aligned; /**< empty cache line */
};

FreeBSD *中的Ring实施参考

以下代码已添加到FreeBSD 8.0中,并在某些网络设备驱动程序中使用(至少在Intel驱动程序中使用):

Linux *中的无锁环形缓冲区

以下是描述Linux无锁环形缓冲区设计的链接。

附加功能

名称

环由唯一名称标识。无法创建两个具有相同名称的环(如果尝试这样做,rte_ring_create()将返回NULL)。

用例

Ring库的用例包括:

  • DPDK中的应用程序之间的通信
  • 由内存池分配器使用

环形缓冲区的解剖

本节说明环形缓冲区的工作方式。环形结构由两个头对和尾对组成。一种由生产者使用,另一种由消费者使用。以下各节的图将它们称为prod_head,prod_tail,cons_head和cons_tail。

每个图代表环的简化状态,它是一个圆形缓冲区。函数局部变量的内容显示在图的顶部,而环结构的内容显示在图的底部。

单一生产者入队

本节说明生产者将对象添加到环时会发生什么。在此示例中,仅修改了生产者的头和尾(prod_head和prod_tail),并且只有一个生产者。

初始状态是使prod_head和prod_tail指向同一位置。

入队第一步

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

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

入队第二步

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

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

入队最后一步

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

单一消费者出队

本节说明了当使用者将对象从环中出队时会发生什么。在此示例中,仅修改了消费者的头和尾(cons_head和cons_tail),并且只有一个消费者。

初始状态是使cons_head和cons_tail指向同一位置。

出队第一步

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

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

出队第二步

第二步是修改ring结构中的ring-> cons_head以指向与cons_next相同的位置。

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

出队最后一步

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

多个生产者入队

本节说明当两个生产者同时将对象添加到环时会发生什么。在此示例中,仅修改了生产者的头和尾(prod_head和prod_tail)。

初始状态是使prod_head和prod_tail指向同一位置。

多个生产者进入第一步

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

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

多个生产者加入第二步

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

  • 如果ring-> prod_head与局部变量prod_head不同,则CAS操作失败,并且代码在第一步重新启动。
  • 否则,将ring-> prod_head设置为本地prod_next,CAS操作成功,然后继续处理。

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

多个生产商加入第三步

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

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

多个生产者进入第四步

每个内核现在都想更新ring-> prod_tail。仅当ring-> prod_tail等于prod_head局部变量时,内核才能更新它。这仅在内核1上成立。操作已在内核1上完成。

多个生产者进入最后一步

内核1更新了ring-> prod_tail之后,内核2也可以对其进行更新。该操作也已在核心2上完成。


模数32位索引

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

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

注意:为了简化说明,使用16位模运算而不是32位模运算。另外,这四个索引被定义为无符号的16位整数,而在更实际的情况下,这是无符号的32位整数。

Modulo 32位索引-示例1

该环包含11000个条目。

模32位索引-示例2

该环包含12536个条目。

注意:为了便于理解,我们在上述示例中使用65536模运算。在实际执行情况下,这对于降低效率是多余的,但是当结果溢出时会自动完成。

该代码始终将生产者和消费者之间的距离保持在0到size(ring)-1之间。由于具有此属性,我们可以在32位模的基础上进行2个索引值之间的减法:这就是为什么索引溢出不是问题。

在任何时候,即使只有第一项减法已溢出,entry和free_entries仍在0到size(ring)-1之间。

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

生产者/消费者同步模式

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

MP / MC(默认一项)

多生产者(/多消费者)模式。这是环网的默认入队(/出队)模式。在此模式下,多个线程可以将对象排入(/出队)到环(/从环)。对于“经典” DPDK部署(每个内核只有一个线程),这通常是最合适,最快的同步模式。作为一个众所周知的局限性,它可以在某些过度使用的情况下执行纯粹的操作。

SP / SC

单生产者(/单消费者)模式。在此模式下,一次仅允许一个线程将对象排队(或从队列中取出)。

MP_RTS / MC_RTS

具有轻松尾部同步(RTS)模式的多生产者(/多消费者)。与原始MP / MC算法的主要区别在于,尾值不会由每个完成入队/出队的线程增加,而只会由最后一个线程增加。这样一来,线程就可以避免旋转环尾值,而将给定实例的实际尾值更改留给最后一个线程。该技术有助于避免在更新尾部时出现“锁定等待抢占”(LWP)问题,并改善了过量使用系统上的平均入队/出队时间。为了实现RTS,每个入队(/出队)操作需要2个64位CAS:一个用于头部更新,第二个用于尾部更新。相比之下,原始的MP / MC算法需要一个32位CAS来进行磁头更新和尾值的等待/旋转。

MP_HTS / MC_HTS

具有头/尾同步(HTS)模式的多生产者(/多消费者)。在这种模式下,入队/出队操作已完全序列化:在任何给定时刻,只能进行一次入队/出队操作。这是通过允许线程head.value 仅在时才进行更改来实现的。头部和尾部的值都自动更新(作为一个64位值)。为了实现这一点,头部更新例程将使用64位CAS。该技术还避免了在尾部更新时发生的“锁定等待抢占”(LWP)问题,并有助于在过量使用的情况下改善环入队/出队行为。完全序列化的生产者/消费者的另一个优点-它提供了为rte_ring实施MT安全查看API的能力。head.value == tail.value

环窥API

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

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

这样,用户就可以检查环中的对象而无需将其从环中移除(又名MT安全偷看),并在实际入队之前为环中的对象保留空间。请注意,此API仅适用于两种同步模式:

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

用适当的同步模式创建/初始化振铃是用户的责任。作为用法示例:

/ *从环读取1个元素:* / 
uint32_t  n  =  rte_ring_dequeue_bulk_start (ring , obj , 1 NULL ); 
if  (n  !=  0 ) { 
    / *检查对象* / 
    if  (object_examine (obj ) ==  KEEP )
        / *决定将其保留在环中。* / 
        rte_ring_dequeue_finish (ring , 0 ); 
    否则
        / *决定将其从环中删除。* / 
        rte_ring_dequeue_finish (铃声, n ); 
}

请注意,在_start_和之间_finish_没有其他线程可以继续进行enqueue(/ dequeue)操作,直到_finish_完成。

参考资料

相关文章

https://rtoax.blog.csdn.net/article/details/107086652

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页