深入浅出dpdk读书笔记--无锁队列

在了解无锁队列之前,需要了解一个知识点:CAS(Compare and Swap,比较并替换)原子指令,用来保障数据的一致性。指令有三个参数,当前内存值V、旧的预期值A、更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。函数原型如下:

rte_atomic32_cmpset(volatile uint32_t *dst, uint32_t exp, uint32_t src)

当dst指向内存的值和exp的值相等时,将src的值更新给dst。对于无锁队列来说,比较复杂的是多生产者和多消费者的这种情况,现在就以多生产者为例,多消费者的情况和多生产者相似,先以图形讲解

1、图解无锁队列

(1)ring的pro_head、pro_tail赋值给本地pro_head,pro_next指向表的下一个元素。

(2)这个操作是一个核心,多个生产者进入到这一步,会有CAS的一个操作,ring.prod_head和本地pro_head进行比较, CAS 操作失败,代码重走第一步。如下图所示,core1比较成功,则将新的位置索引赋值给ring.prod_head,core2由于比较失败,所以core2重新走第一步,prod_next遍历下一个环的位置,prod_head遍历同样遍历到下一个位置。

(3) 在核心 2 上重试 CAS 操作并成功。核心 1 更新环的一个元素 (obj4),核心 2 更新另一个元素 (obj5)。

(4) 现在,每个内核都想要更ring.prod_tail。 只有当ring.prod_tail等于prod_head局部变量时,内核才能更新它。 这仅适用于核心 1。该操作在核心 1 上完成

(5) 一旦核心 1 更新了ring.prod_tail,核心 2 也可以更新它。 该操作也在核心 2 上完成。

2、代码解析无锁队列
/*该函数表示多生产者的情况调用的核心__rte_ring_do_enqueue*/
static __rte_always_inline unsigned int
rte_ring_mp_enqueue_bulk(struct rte_ring *r, void * const *obj_table,
			 unsigned int n, unsigned int *free_space)
{
	return __rte_ring_do_enqueue(r, obj_table, n, RTE_RING_QUEUE_FIXED,
			__IS_MP, free_space);
}

__rte_ring_do_enqueue(struct rte_ring *r, void * const *obj_table,
		 unsigned int n, enum rte_ring_queue_behavior behavior,
		 unsigned int is_sp, unsigned int *free_space)
{
	uint32_t prod_head, prod_next;
	uint32_t free_entries;

	n = __rte_ring_move_prod_head(r, is_sp, n, behavior,
			&prod_head, &prod_next, &free_entries);
	if (n == 0)
		goto end;

	ENQUEUE_PTRS(r, &r[1], prod_head, obj_table, n, void *);

	update_tail(&r->prod, prod_head, prod_next, is_sp, 1);
end:
	if (free_space != NULL)
		*free_space = free_entries - n;
	return n;
}

/*这个函数对应上图讲的第二步,更新ring.prod_head的操作*/
static __rte_always_inline unsigned int
__rte_ring_move_prod_head(struct rte_ring *r, unsigned int is_sp,
		unsigned int n, enum rte_ring_queue_behavior behavior,
		uint32_t *old_head, uint32_t *new_head,
		uint32_t *free_entries)
{
	const uint32_t capacity = r->capacity;
	unsigned int max = n;
	int success;

	do {
		/* Reset n to the initial burst count */
		n = max;

        /*抢占无锁队列*/
		*old_head = r->prod.head;

		/* add rmb barrier to avoid load/load reorder in weak
		 * memory model. It is noop on x86
		 */
		rte_smp_rmb();

		/*
		 *  The subtraction is done between two unsigned 32bits value
		 * (the result is always modulo 32 bits even if we have
		 * *old_head > cons_tail). So 'free_entries' is always between 0
		 * and capacity (which is < size).
		 */
		*free_entries = (capacity + r->cons.tail - *old_head);

		/* check that we have enough room in ring */
		if (unlikely(n > *free_entries))
			n = (behavior == RTE_RING_QUEUE_FIXED) ?
					0 : *free_entries;

		if (n == 0)
			return 0;

		*new_head = *old_head + n;
		if (is_sp)
			r->prod.head = *new_head, success = 1;
		else/*由于是多生产者,调用rte_atomic32_cmpset比较r->prod.head和old_head的值,因为无锁队列是全局的,每个核都有机会获取核的机会,当没有抢占到的核用r->prod.head和old_head比较时,不一致,就会while等待,直到当前队列更新new_head,这个时候下一个核终于有机会占有这个队列,更新自己的节点*/
			success = rte_atomic32_cmpset(&r->prod.head,
					*old_head, *new_head);
            /*相同则返回true,不相同则返回0,继续do while循环*/
	} while (unlikely(success == 0));
	return n;
}

static __rte_always_inline void
update_tail(struct rte_ring_headtail *ht, uint32_t old_val, uint32_t new_val,
		uint32_t single, uint32_t enqueue)
{
	if (enqueue)
		rte_smp_wmb();
	else
		rte_smp_rmb();
	/*
	 * If there are other enqueues/dequeues in progress that preceded us,
	 * we need to wait for them to complete
	 */
    /*如果ht tail和old_value不等时,一直等待,直到其它流程处理完之后,更新自己的tail,这个就是图解的最后几步*/
	if (!single)
		while (unlikely(ht->tail != old_val))
			rte_pause();

	ht->tail = new_val;
}

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值