(三)DPDK-Core Libraries

 

環形緩衝區管理(librte_ring)

簡介

dpdk的無鎖隊列ring是借鑑了linux內核kfifo無鎖隊列。ring的實質是FIFO的環形隊列。

ring的特點:

  • 無鎖出入隊(除了cas(compare and swap)操作)
  • 多消費/生產者同時出入隊

使用方法:

  • structrte_ring * rte_ring_create(constchar *name, unsigned count, int socket_id, unsignedflags)
    
    /*
    name:ring的name
    
    count:ring隊列的長度必須是2的冪次方。
    
    socket_id:ring位於的socket。
    
    flags:指定創建的ring的屬性:單/多生產者、單/多消費者兩者之間的組合。
    
    0表示使用默認屬性(多生產者、多消費者)。不同的屬性出入隊的操作會有所不同。
    
    */

出入隊

  • 有不同的出入隊方式(單、bulk、burst)都在rte_ring.h中。
  • 例如:rte_ring_enqueue和rte_ring_dequeue

創建ring

struct rte_ring {
	TAILQ_ENTRY(rte_ring) next;      /**< Next in list. */
 
	//ring的唯一標示,不可能同時有兩個相同name的ring存在
	char name[RTE_RING_NAMESIZE];    /**< Name of the ring. */
	int flags;                       /**< Flags supplied at creation. */
 
	/** Ring producer status. */
	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. */
	} prod __rte_cache_aligned;
 
	/** Ring consumer status. */
	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. */
#ifdef RTE_RING_SPLIT_PROD_CONS
	/*這個屬性就是要求gcc在編譯的時候,把cons/prod結構都單獨分配到一個cache行,爲什麼這樣做?
	 因爲如果沒有這些的話,這兩個結構在內存上是連續的,編譯器不會把他們分配到不同cache 行,而
	一般上這兩個結構是要被不同的核訪問的,如果連續的話這兩個核就會產生僞共享問題。*/
	} cons __rte_cache_aligned;
#else
	} cons;
#endif
 
#ifdef RTE_LIBRTE_RING_DEBUG
	struct rte_ring_debug_stats stats[RTE_MAX_LCORE];
#endif
 
	void * ring[0] __rte_cache_aligned; /**< Memory space of ring starts here.
	 	 	 	 	 	 	 	 	 	 * not volatile so need to be careful
	 	 	 	 	 	 	 	 	 	 * about compiler re-ordering */
};

在rte_ring_list鏈表中創建一個rte_tailq_entry節點。

在memzone中根據隊列的大小count申請一塊內存(rte_ring的大小加上count*sizeof(void *))。

緊鄰着rte_ring結構的void *數組用於放置入隊的對象(單純的賦值指針值)。

rte_ring結構中有生產者結構prod、消費者結構cons。

初始化參數之後,把rte_tailq_entry的data節點指向rte_ring結構地址。

 

在使用這個結構的時候,一般是將1個核作爲生產者,向這個ring隊列裏面添加數據;另一個core或者多個core 作爲消費者從這個ring隊列中獲取數據。生產者核訪問上面的prod結構,消費者訪問cons結構。

 

 

實現多生產/消費者同時生產/消費

  • 移動prod.head表示生產者預定的生產數量
  • 當該生產者生產結束,且在此之前的生產也都結束後,移動prod.tail表示實際生產的位置
  • 同樣,移動cons.head表示消費者預定的消費數量
  • 當該消費者消費結束,且在此之前的消費也都結束後,移動cons.tail表示實際消費的位置

 

出/入隊列

多生產者入隊代碼

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;
	uint32_t mask = r->prod.mask;
	int ret;
 
	/* move prod.head atomically */
	do {
		/* Reset n to the initial burst count */
		n = max;
 
		prod_head = r->prod.head;
		cons_tail = r->cons.tail;
		
		 /*在這裏dpdk提供的索引計算方法,能保證即使prod_head > cons_tail,
		  *取模求得的值也始終落在0~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;
		/*這裏使用CAS指令來移動r->prod.head,去掉了鎖操作,也算優化
		*點
		*/
		success = rte_atomic32_cmpset(&r->prod.head, prod_head,
					      prod_next);
	} while (unlikely(success == 0));
 
	/* write entries in ring */
	ENQUEUE_PTRS();
	/*COMPILER_BARRIER 是一個宏定義,它的作用就是確保上面的ENQUEUE_PTRS
	*宏處理在下面r->prod.tail = prod_next;之前執行?大家可能會問,
	*ENQUEUE_PTRS怎麼可能會跑到r->prod.tail = prod_next之後執行?
	*其實是有可能的,GCC爲了提高性能,它會優化代碼,它可能會調整代碼的
	*執行順序,把後面的指令放到前面,GCC這些編譯器是依據單核情況實現,
	*所以這種情況下,程序員必須介入,上面這條指令,就是告訴編譯器不允許
	*調整這個指令順序。
	*/
	rte_compiler_barrier();
 
	/* 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 preceeded us,
	 * we need to wait for them to complete
	 */
	while (unlikely(r->prod.tail != prod_head))
		rte_pause();
 
	r->prod.tail = prod_next;
	return ret;
}

 

內存池管理(librte_mempool)

網絡報文緩衝區管理(librte_mbuf)

定時器管理(librte_timer) 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章