5.DPDK Ring Library

原文:http://dpdk.org/doc/guides/prog_guide/ring_lib.html

5.Ring Library

ring 管理队列,ring不是一个无限大小的链表,它具有以下属性:

*FIFO
*大小固定,指针存储在表中
*无锁实现
*多消费者或单消费者出队
*多生产者或单生产者入队
*批量出队 - 如果成功,将指定数量的对象出队; 否则失败
*批量入库 - 如果成功,将指定数量的对象入队; 否则失败
*爆发出队 - 如果指定数量的对象无法满足,则将最大可用数量的对象出队
*爆发入队 - 如果指定数量的对象无法满足,则将最大可用数量的对象入队

这种数据结构优于链表队列的

优点如下:

*更快:比较void *大小的数据,只需要执行单次Compare-And-Swap指令,而不需要执行2次Compare-And-Swap指令

*比完全无锁队列简单

*适用于批量入队/出队操作。因为指针存储在表中,多个对象出队并不会像链表队列那样产生大量的缓存未命中,此外,多个对象批量出队不会比单个对象出队开销大

CAS(Compare and Swap)是个原子操作

缺点如下:

*大小固定

*许多环在内存方面的成本比链表列表的成本更高。空环至少包含N个指针。

存储在数据结构中的consumer head,consumer tail,producer head,producer tail简单的表示了ring的逻辑结构


5.1 FreeBSD中Ring实现的参考*
在FreeBSD 8.0中添加了以下代码,并在某些网络设备驱动程序中使用(至少在Intel驱动程序中):
FreeBSD中的bufring.h
FreeBSD中的bufring.c

5.2 Linux中无锁Ring的实现*

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

5.3 附加特性

5.3.1 名字

一个Ring被唯一的名字识别,创建两个名字相同的Ring是不可能的(当尝试这样操作时,rte_ring_create()函数在第二次执行时返回NULL)。

5.4 用例

Ring库的用例包括:

*DPDK应用之间的信息交互

*内存池中的使用

5.5 剖析Ring缓存

本节介绍环形缓存的运作方式。ring结构由两对head,tail组成,一对被生产者使用,一对被消费者使用,在下面的图形中将他们称为prod_head,prod_tail,,cons_head 和 ons_tail。

每种图形代表了环形缓存ring的一个简单状态。局部变量在图形的上面表示,ring结构相关变量在图形的下面表示。

备注:在下面的图解中,红色方框链表代表ring,红色方框上面的变量代表代码实现的局部变量,下面的代表存储在ring结构体里的索引,注意区分哦

5.5.1 单生产者入队

本节介绍当单生产者添加一个对象到ring时发生了什么。在这个例子中,仅仅只有一个生产者,仅仅只有生产者的head和tail(prod_head and prod_tail)索引被修改了。

在初始状态, prod_head 和 prod_tail指向相同的位置。

\lib\librte_ring\rte_ring.h

/**
 * @internal Enqueue several objects on a ring (NOT multi-producers safe).
 *
 * @param r
 *   A pointer to the ring structure.
 * @param obj_table
 *   A pointer to a table of void * pointers (objects).
 * @param n
 *   The number of objects to add in the ring from the obj_table.
 * @param behavior
 *   RTE_RING_QUEUE_FIXED:    Enqueue a fixed number of items from a ring
 *   RTE_RING_QUEUE_VARIABLE: Enqueue as many items a possible from ring
 * @return
 *   Depend on the behavior value
 *   if behavior = RTE_RING_QUEUE_FIXED
 *   - 0: Success; objects enqueue.
 *   - -EDQUOT: Quota exceeded. The objects have been enqueued, but the
 *     high water mark is exceeded.
 *   - -ENOBUFS: Not enough room in the ring to enqueue, no object is enqueued.
 *   if behavior = RTE_RING_QUEUE_VARIABLE
 *   - n: Actual number of objects enqueued.
 */
static inline int __attribute__((always_inline))
__rte_ring_sp_do_enqueue(struct rte_ring *r, void * const *obj_table,
			 unsigned n, enum rte_ring_queue_behavior behavior)
{
	uint32_t prod_head, cons_tail;
	uint32_t prod_next, free_entries;
	unsigned i;
	uint32_t mask = r->prod.mask;
	int ret;

	prod_head = r->prod.head;
	cons_tail = r->cons.tail;
	/* The subtraction is done between two unsigned 32bits value
	 * (the result is always modulo 32 bits even if we have
	 * prod_head > cons_tail). So 'free_entries' is always between 0
	 * and size(ring)-1. */
	free_entries = mask + cons_tail - prod_head;

	/* check that we have enough room in ring */
	if (unlikely(n > free_entries)) {
		if (behavior == RTE_RING_QUEUE_FIXED) {
			__RING_STAT_ADD(r, enq_fail, n);
			return -ENOBUFS;
		}
		else {
			/* No free entry available */
			if (unlikely(free_entries == 0)) {
				__RING_STAT_ADD(r, enq_fail, n);
				return 0;
			}

			n = free_entries;
		}
	}

	prod_next = prod_head + n;
	r->prod.head = prod_next;

	/* write entries in ring */
	ENQUEUE_PTRS();
	rte_smp_wmb();

	/* if we exceed the watermark */
	if (unlikely(((mask + 1) - free_entries + n) > r->prod.watermark)) {
		ret = (behavior == RTE_RING_QUEUE_FIXED) ? -EDQUOT :
			(int)(n | RTE_RING_QUOT_EXCEED);
		__RING_STAT_ADD(r, enq_quota, n);
	}
	else {
		ret = (behavior == RTE_RING_QUEUE_FIXED) ? 0 : n;
		__RING_STAT_ADD(r, enq_success, n);
	}

	r->prod.tail = prod_next;
	return ret;
}


5.5.1.1 入队第一步 

首先,局部变量保存ring->prod_head 和 ring->cons_tail。 prod_next局部变量指向prod_head的下一个元素,若是批量入队就指prod_head的下几个元素。

假如ring里没有足够的空间(检查cons_tail获知),入队函数将返回error


5.5.1.2.  入队第二步

第二步是修改ring结构体里的ring->prod_head 索引,将它指向上面提到的局部变量prod_next指向的位置。

接下来就是将添加对象(下图ring中的obj4)的索引复制到ring的里面。


5.5.1.3. 入队最后一步

一旦添加对象被复制到ring后,ring结构体里的 ring->prod_tail索引将指向 ring->prod_head的位置,入队操作完成。


5.5.2. 单消费者出队

本节介绍单消费者从ring取出一个对象时发生了什么。在这个例子中,仅仅只有一个消费者,仅仅只有消费者的head和tail(cons_head and cons_tail)索引被修改了。

在初始状态, cons_head 和 cons_tail指向相同的位置。

/**
 * @internal Dequeue several objects from a ring (NOT multi-consumers safe).
 * When the request objects are more than the available objects, only dequeue
 * the actual number of objects
 *
 * @param r
 *   A pointer to the ring structure.
 * @param obj_table
 *   A pointer to a table of void * pointers (objects) that will be filled.
 * @param n
 *   The number of objects to dequeue from the ring to the obj_table.
 * @param behavior
 *   RTE_RING_QUEUE_FIXED:    Dequeue a fixed number of items from a ring
 *   RTE_RING_QUEUE_VARIABLE: Dequeue as many items a possible from ring
 * @return
 *   Depend on the behavior value
 *   if behavior = RTE_RING_QUEUE_FIXED
 *   - 0: Success; objects dequeued.
 *   - -ENOENT: Not enough entries in the ring to dequeue; no object is
 *     dequeued.
 *   if behavior = RTE_RING_QUEUE_VARIABLE
 *   - n: Actual number of objects dequeued.
 */
static inline int __attribute__((always_inline))
__rte_ring_sc_do_dequeue(struct rte_ring *r, void **obj_table,
		 unsigned n, enum rte_ring_queue_behavior behavior)
{
	uint32_t cons_head, prod_tail;
	uint32_t cons_next, entries;
	unsigned i;
	uint32_t mask = r->prod.mask;

	cons_head = r->cons.head;
	prod_tail = r->prod.tail;
	/* The subtraction is done between two unsigned 32bits value
	 * (the result is always modulo 32 bits even if we have
	 * cons_head > prod_tail). So 'entries' is always between 0
	 * and size(ring)-1. */
	entries = prod_tail - cons_head;

	if (n > entries) {
		if (behavior == RTE_RING_QUEUE_FIXED) {
			__RING_STAT_ADD(r, deq_fail, n);
			return -ENOENT;
		}
		else {
			if (unlikely(entries == 0)){
				__RING_STAT_ADD(r, deq_fail, n);
				return 0;
			}

			n = entries;
		}
	}

	cons_next = cons_head + n;
	r->cons.head = cons_next;

	/* copy in table */
	DEQUEUE_PTRS();
	rte_smp_rmb();

	__RING_STAT_ADD(r, deq_success, n);
	r->cons.tail = cons_next;
	return behavior == RTE_RING_QUEUE_FIXED ? 0 : n;
}


5.5.2.1 出队第一步

首先,局部变量保存ring->cons_head 和 ring->prod_tail。 cons_next局部变量指向cons_head的下一个元素,若是批量出队就指向cons_head的下几个元素。

假如ring里没有足够的空间(prod_tail检查获知),出队函数将返回error


5.5.2.2. 出队第二步

第二步是修改ring结构体里的ring->cons_head 索引,将它指向上面提到的局部变量cons_next指向的位置。

接下来就是将对象(下图ring中的obj1)的指针复制到用户传进来的指针中。


5.5.2.3. 出队最后一步

最后,ring结构体中的 ring->cons_tail索引指向和 ring->cons_head,局部变量cons_next相同的位置(obj2的位置)。出队操作完成。

5.5.3 多生产者入队

本节介绍当两个生产者同时添加对象到ring时发生了什么。在这个例子中,仅仅只有生产者的head和tail(prod_head and prod_tail)索引被修改了。
在初始状态, prod_head 和 prod_tail指向相同的位置。

/**
 * @internal Enqueue several objects on the ring (multi-producers safe).
 *
 * This function uses a "compare and set" instruction to move the
 * producer index atomically.
 *
 * @param r
 *   A pointer to the ring structure.
 * @param obj_table
 *   A pointer to a table of void * pointers (objects).
 * @param n
 *   The number of objects to add in the ring from the obj_table.
 * @param behavior
 *   RTE_RING_QUEUE_FIXED:    Enqueue a fixed number of items from a ring
 *   RTE_RING_QUEUE_VARIABLE: Enqueue as many items a possible from ring
 * @return
 *   Depend on the behavior value
 *   if behavior = RTE_RING_QUEUE_FIXED
 *   - 0: Success; objects enqueue.
 *   - -EDQUOT: Quota exceeded. The objects have been enqueued, but the
 *     high water mark is exceeded.
 *   - -ENOBUFS: Not enough room in the ring to enqueue, no object is enqueued.
 *   if behavior = RTE_RING_QUEUE_VARIABLE
 *   - n: Actual number of objects enqueued.
 */
static inline int __attribute__((always_inline))
__rte_ring_mp_do_enqueue(struct rte_ring *r, void * const *obj_table,
			 unsigned n, enum rte_ring_queue_behavior behavior)
{
	uint32_t prod_head, prod_next;
	uint32_t cons_tail, free_entries;
	const unsigned max = n;
	int success;
	unsigned i, rep = 0;
	uint32_t mask = r->prod.mask;
	int ret;

	/* Avoid the unnecessary cmpset operation below, which is also
	 * potentially harmful when n equals 0. */
	if (n == 0)
		return 0;

	/* move prod.head atomically */
	do {
		/* Reset n to the initial burst count */
		n = max;

		prod_head = r->prod.head;
		cons_tail = r->cons.tail;
		/* The subtraction is done between two unsigned 32bits value
		 * (the result is always modulo 32 bits even if we have
		 * prod_head > cons_tail). So 'free_entries' is always between 0
		 * and size(ring)-1. */
		free_entries = (mask + cons_tail - prod_head);

		/* check that we have enough room in ring */
		if (unlikely(n > free_entries)) {
			if (behavior == RTE_RING_QUEUE_FIXED) {
				__RING_STAT_ADD(r, enq_fail, n);
				return -ENOBUFS;
			}
			else {
				/* No free entry available */
				if (unlikely(free_entries == 0)) {
					__RING_STAT_ADD(r, enq_fail, n);
					return 0;
				}

				n = free_entries;
			}
		}

		prod_next = prod_head + n;
		success = rte_atomic32_cmpset(&r->prod.head, prod_head,
					      prod_next);
	} while (unlikely(success == 0));

	/* write entries in ring */
	ENQUEUE_PTRS();
	rte_smp_wmb();

	/* if we exceed the watermark */
	if (unlikely(((mask + 1) - free_entries + n) > r->prod.watermark)) {
		ret = (behavior == RTE_RING_QUEUE_FIXED) ? -EDQUOT :
				(int)(n | RTE_RING_QUOT_EXCEED);
		__RING_STAT_ADD(r, enq_quota, n);
	}
	else {
		ret = (behavior == RTE_RING_QUEUE_FIXED) ? 0 : n;
		__RING_STAT_ADD(r, enq_success, n);
	}

	/*
	 * If there are other enqueues in progress that preceded us,
	 * we need to wait for them to complete
	 */
	while (unlikely(r->prod.tail != prod_head)) {
		rte_pause();

		/* Set RTE_RING_PAUSE_REP_COUNT to avoid spin too long waiting
		 * for other thread finish. It gives pre-empted thread a chance
		 * to proceed and finish with ring dequeue operation. */
		if (RTE_RING_PAUSE_REP_COUNT &&
		    ++rep == RTE_RING_PAUSE_REP_COUNT) {
			rep = 0;
			sched_yield();
		}
	}
	r->prod.tail = prod_next;
	return ret;
}


5.5.3.1 多生产者入队第一步

在两个生产者core中(这个core可以理解成同时运行的线程或进程),各自的局部变量都保存ring->prod_head 和 ring->cons_tail。 各自的局部变量prod_next索引指向ring->prod_head的下一个元素,如果是批量入队,指向下几个元素。

假如ring里没有足够的空间(检查cons_tail获知),入队函数将返回error


5.5.3.2. 多生产者入队第二步

第二步是修改ring结构体里的ring->prod_head 索引,将它指向上面提到的局部变量prod_next指向的位置。这个操作是通过使用 Compare And Swap (CAS)执行完成的, Compare And Swap (CAS)包含以下原子操作:

*如果ring->prod_head索引和局部变量prod_head索引不相等,CAS操作失败,代码将从新从第一步开始执行。

*否则,将ring->prod_head索引指向局部变量prod_next的位置,CAS操作成功,继续下一步处理。

在下图中,生产者core1执行成功,生产者core2重新运行。


5.5.3.3. 多生产者入队第三步

生产者core2中CAS指令重试成功

生产者core1更新对象obj4到ring中,生产者core2更新对象obj5到ring中(CAS指令重试后执行成功的)。


5.5.3.4. 多生产者入队第四步

现在每个生产者core都想更新 ring->prod_tail索引。生产者core代码中,只有ring->prod_tail等于自己局部变量prod_head才能被更新,显然从上图中可知,只有生产者core1才能满足,生产者core1完成了入队操作。


5.5.3.5. 多生产者入队最后一步

一旦生产者core1更新了ring->prod_tail后,生产者core2也可以更新ring->prod_tail了。生产者core2也完成了入队操作


5.5.4 32位取模索引

在前面的图例中,prod_head, prod_tail, cons_head 和 cons_tail 都是用箭头表示的。但在实际的代码实现中,他们的值并不是0和ring大小减一之间的数值。索引的大小范围是0----2^32-1,当访问ring中的数据时,真正的索引等于ring中索引值和掩码之后的值。32 bit取模的意思是如果索引操作(加减)的结果的值超出了32 bit数据的范围,溢出的值忽略,只看省下的位组成的数。

下面的两个例子帮助解释索引在ring中如何使用的

!Note

为了简便,例子操作的是16位而不是32位。另外,关键的四个索引也被定义成16位的整数,现实代码实现是用得32位的整数。


ring包含了11000条目


ring包含了12536个条目

!Note

为了便于理解,我们在上面的例子中使用模65536的操作。在真实代码实现中,这是冗长低效的,但是当结果溢出时是自动完成的。

代码实现总是将producer 和 consumer保持0----ring大小减一的距离。 这个特性的好处是我们能在两个32位索引值之间做减法,且差值永远在0----ring大小减一范围内:这也是为什么结果溢出不是什么大问题。

在任何时候,已经使用的条目和空闲的条目永远在0----ring大小减一之间。

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

5.6 参考索引

bufring.h in FreeBSD (version 8)
bufring.c in FreeBSD (version 8)
Linux Lockless Ring Buffer Design

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值