1. Ring Library
通过操作时将ring中的变量复制到本地的方式来完成无锁操作
1.1 Ring struct
struct rte_ring {
char name[RTE_MEMZONE_NAMESIZE]; // name of the ring\n
int flags; // Flags supplied at creation
const struct rte_memzone *memzone; // memzone, if any, containing the ret_ring
struct prod {
uint32_t watermark; // maximum items before 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_t head; // producer head
volatile uint32_t tail; // producer tail
}
struct cons {
uint32_t sc_dequeue; // true, if single consumer
uint32_t size; // size of the ring
uint32_t mask; // mask(size - 1) of ring
volatile uint32_t head; // consumer head
volatile uint32_t tail; // consumer tail
}
}
1.2 Single Producer Enqueue
单生产者的模式比较简单:
1. 将下标ring->prod_head、ring->cons_tail复制到本地变量m_prod_head和m_cons_tail,然后在本地创建一个m_prod_next变量,指向prod_head + n(n≥1,表示需要入队的数据个数),标记下一次消费的位置。若m_prod_next ≥ m_cons_tail,则表示队列没有足够的空间用来存放这些数据,则返回一个error。
2. 将m_prod_next赋给ring->prod_head,先完成数据存放位置的预定,然后再做入队操作,将需要enqueue的数据加入队列,m_prod_head递增。
3. 数据全部存入队列后,将ring->prod_head赋给ring->prod_tail,结束入队操作。
1.3 Single Consumer Dequeue
与单生产者的入队方式类似,单消费者的出队模式也比较简单:
1. 将下标ring->cons_head和ring_prod_tail复制到本地变量m_cons_head和m_constail,然后在本地创建一个m_cons_next变量,指向m_cons_head + n(n≥1,表示要出队的数据个数),标记下一次消费的位置。若m_cons_next ≥ m_prod_head,则表示没有足够的数据能被取走,则返回一个error。
2. 将m_cons_next赋给ring->cons_head,先完成数据提取的位置预定,然后做数据提取,将数据取走,m_cons_head递增。
3. 数据全部出队后,将ring->cons_head赋给ring->cons_tail,结束出队操作。
1.4 Multiple Producers Enqueue
假设有两位生产者a, b同时进行入队操作:
1. 两位生产者都将下标ring->prod_head和ring->cons_tail拷贝到本的a->prod_head、a->cons_tail和b->prod_head、b->cons_tail,并创建本地变量a->prod_next = a->prod_head + na、b->prod_next = b->prod_head + nb,(na,nb ≥ 1,表示需要入队的数据个数)。若*->prod_next ≥ *->cons_tail,表示没有足够的位置存放数据,则返回一个error。
2. 将a->cons_tail赋给ring->prod_head完成数据存放位置的预取,这里依赖一个原子操作Compare And Swap (CAS):
2.1 若a->prod_head与ring->prod_head不一致,CAS失败,则返回第一步操作。
2.2 否则,将ring->prod_head指向a->prod_next,CAS结束
3. 若a先完成CAS操作,则b第一次CAS操作尝试失败,第二次重新尝试时操作成功(反之亦然)。然后根据本地的a->prod_head和a->prod_next进行数据入队操作,a->prod_head递增。
4. 入队完成后,每个生产者都希望跟新ring->prod_tail,但只有a生产者发现ring->prod_tail与自身的a->prod_head相等时,才能将ring->prod_tail更新为a->prod_head,生产者a的入队操作完成。
5. 同理操作消费者b。
1.5 Modulo 32-bit Indexes
虽然Ring是一个环形队列,但在使用的时候无需关心prod_head等参数的回环问题。该库实现时设立了一个mask参数:mask = size(ring) - 1,然后根据mask自动计算prod_head等参数在ring中的具体下标,自动完成取余操作。