DPDK ring库:环形缓冲区的解剖
Posted rtoax
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DPDK ring库:环形缓冲区的解剖相关的知识,希望对你有一定的参考价值。
目录
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_
完成。
参考资料
- FreeBSD(版本8)中的bufring.h
- FreeBSD(版本8)中的bufring.c
- Linux无锁环形缓冲区设计
相关文章
https://rtoax.blog.csdn.net/article/details/107086652
以上是关于DPDK ring库:环形缓冲区的解剖的主要内容,如果未能解决你的问题,请参考以下文章
DPDK — RING(librte_ring,Ring Manager,环缓冲区管理组件)