1. DPDK Ring简介
dpdk实现了一个无锁环形队列Ring,可用于在dpdk不同的应用程序之间进行通信。
Ring支持的效果:
- 先进先出
- 最大大小是固定的,指针存储在表中
- 无锁实现
- 多消费者或单消费者出队
- 多生产者或单生产者入队
struct rte_ring {
/* Ring producer status. */
struct prod {
uint32_t watermark; /**< Maximum itemsbefore EDQUOT. */
uint32_t sp_enqueue; /**< True, if single producer. */
uint32_t size; /**< Size of ring.*/
uint32_t mask; /**< Mask (size-1)of ring. */
volatile uint32_thead; /**< Producer head.*/
volatileuint32_t tail; /**< Producer tail.*/
} prod __rte_cache_aligned;
/* Ring consumer status. */
struct cons {
uint32_t sc_dequeue; /**< True, if single consumer. */
uint32_t size; /**< Size of thering. */
uint32_t mask; /**< Mask (size-1)of ring. */
volatileuint32_t head; /**< Consumer head.*/
volatileuint32_t tail; /**< Consumer tail.*/
} cons __rte_cache_aligned;
void*ring[] __rte_cache_aligned;
};
2. Ring实现
2.1 单一生产者入队
首先,将ring-> prod_head和ring-> cons_tail复制到局部变量中。prod_next局部变量指向表的下一个元素,或在批量入队的情况下指向多个元素。
如果环中没有足够的空间(通过检查cons_tail可以检测到),它将返回错误。
第二步是修改环结构中的ring-> prod_head以指向与prod_next相同的位置。
指向添加对象的指针将复制到环(obj4)中。
将对象添加到环中后,将修改环结构中的ring-> prod_tail使其指向与ring-> prod_head相同的位置。入队操作完成。
2.2 单一消费者出队
首先,将ring-> cons_head和ring-> prod_tail复制到局部变量中。cons_next局部变量指向表的下一个元素,或在批量出队的情况下指向多个元素。
如果环中没有足够的对象(通过检查prod_tail可以检测到),它将返回错误。
第二步是修改ring结构中的ring-> cons_head,使其指向与cons_next相同的位置。
指向出队对象(obj1)的指针被复制到用户指定的指针中。
最后,将ring结构中的ring-> cons_tail修改为指向与ring-> cons_head相同的位置。出队操作完成。
2.3 多个生产者入队
当两个生产者同时将对象添加到环时会发生什么?
在两个CPU core上,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上完成。