環形緩衝區管理(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)