DPDK數據包與內存專題-mempool內存池


前言:DPDK提供了內存池機制,使得內存的管理的使用更加簡單安全。在設計大的數據結構時,都可以使用mempool分配內存,同時,mempool也提供了內存的獲取和釋放等操作接口。對於數據包mempool甚至提供了更加詳細的接口-rte_pktmbuf_pool_create(),接下來重點分析通用的內存池相關內容。使用DPDK-17.02版本。

一. mempool的創建

內存池的創建使用的接口是rte_mempool_create()。在仔細分析代碼之前,先說明一下mempool的設計思路:在DPDK-17.02版本中(和2.1等先前版本在初始化略有差異),總體來說,mempool的組織是通過3個部分實現的

  • 1.mempool頭結構。mempool由名字區分,掛接在struct rte_tailq_elem rte_mempool_tailq全局隊列中,可以根據mempool的名字進行查找,使用rte_mempool_lookup()接口即可。這只是個mempool的指示結構,mempool分配的內存區並不在這里面,只是通過物理和虛擬地址指向實際的內存地址。
  • 2.mempool的實際空間。這就是通過內存分配出來的地址連續的空間,用來存儲mempool的obj對象。
  • 3.ring隊列。ring是個環形無鎖隊列,關於這個話題,可以參考官方文檔來了解。其作用就是存放mempool中的對象指針,提供了方便存取使用mempool的空間的辦法。

接下來,就來具體看看mempool的創建和初始化過程。
先注意一下rte_mempool_create的參數中的兩個-mp_initobj_init,前者負責初始化mempool中配置的私有參數,如在數據包中加入的我們自己的私有結構;后者負責初始化每個mempool對象。我們然后按照mempool的3個關鍵部分展開說明。

  • 1.mempool頭結構的創建
    在DPDK-17.02中,mempool頭結構包含3個部分:struct rte_mempool,cache和mempool private。創建是在rte_mempool_create_empty()中完成的,看這個函數,先進行了對齊的檢查
RTE_BUILD_BUG_ON((sizeof(struct rte_mempool) & RTE_CACHE_LINE_MASK) != 0); 

然后從mempool隊列中取出頭節點,我們創建的mempool結構填充好,就掛接在這個節點上。接下來做一些檢查工作和創建flag的設置。

rte_mempool_calc_obj_size()計算了每個obj的大小,這個obj又是由三個部分組成的,objhdr,elt_size,objtlr,即頭,數據區,尾。在沒有開啟RTE_LIBRTE_MEMPOOL_DEBUG調試時,沒有尾部分;頭部分的結構為:struct rte_mempool_objhdr,通過這個頭部,mempool中的obj都是鏈接到隊列中的,所以,提供了遍歷obj的方式(盡管很少這么用)。函數返回最后計算對齊后的obj的大小,為后面分配空間提供依據。

然后分配了一個mempool隊列條目,為后面掛接在隊列做准備。

te = rte_zmalloc("MEMPOOL_TAILQ_ENTRY", sizeof(*te), 0);
	if (te == NULL) {
		RTE_LOG(ERR, MEMPOOL, "Cannot allocate tailq entry!\n");
		goto exit_unlock;
	}

接下來,就是計算整個mempool頭結構多大,吐槽這里的命名!

	mempool_size = MEMPOOL_HEADER_SIZE(mp, cache_size);
	mempool_size += private_data_size;
	mempool_size = RTE_ALIGN_CEIL(mempool_size, RTE_MEMPOOL_ALIGN);

mempool_size這個名字太有誤導性,這里指的是計算mempool的頭結構的大小。而不是內存池實際的大小。在這里可以清晰的看出這個mempool頭結構是由三部分組成的。cache計算的是所有核上的cache之和。

然后,分配這個mempool頭結構大小的空間,填充mempool結構體,並把mempool頭結構中的cache地址分配給mempool。初始化這部分cache.

最后就是掛接mempool結構。TAILQ_INSERT_TAIL(mempool_list, te, next);

  • 2.mempool實際空間的創建和ring的創建

這部分的創建是在函數rte_mempool_populate_default() 中完成的。

首先計算了每個elt的總共的大小

total_elt_sz = mp->header_size + mp->elt_size + mp->trailer_size;

然后計算為這些元素需要分配多大的空間,rte_mempool_xmem_size(n, total_elt_sz, pg_shift);

接着rte_memzone_reserve_aligned()分配空間。
終於到關鍵的一步了,rte_mempool_populate_phys()把元素添加到mempool,實際上就是把申請的空間分給每個元素。

先看到的是這么一段代碼:

 
if ((mp->flags & MEMPOOL_F_POOL_CREATED) == 0) {
		ret = rte_mempool_ops_alloc(mp);
		if (ret != 0)
			return ret;
		mp->flags |= MEMPOOL_F_POOL_CREATED;
} 

這就是創建ring的過程咯,其中的函數rte_mempool_ops_alloc()就是實現。那么,對應的ops->alloc()在哪注冊的呢?

if ((flags & MEMPOOL_F_SP_PUT) && (flags & MEMPOOL_F_SC_GET))
		rte_mempool_set_ops_byname(mp, "ring_sp_sc", NULL);
	else if (flags & MEMPOOL_F_SP_PUT)
		rte_mempool_set_ops_byname(mp, "ring_sp_mc", NULL);
	else if (flags & MEMPOOL_F_SC_GET)
		rte_mempool_set_ops_byname(mp, "ring_mp_sc", NULL);
	else
		rte_mempool_set_ops_byname(mp, "ring_mp_mc", NULL);

就是根據ring的類型,來注冊對應的操作函數,如默認的就是ring_mp_mc,多生產者多消費者模型,其操作函數不難找到:

static const struct rte_mempool_ops ops_mp_mc = {
	.name = "ring_mp_mc",
	.alloc = common_ring_alloc,
	.free = common_ring_free,
	.enqueue = common_ring_mp_enqueue,
	.dequeue = common_ring_mc_dequeue,
	.get_count = common_ring_get_count,
};

接下來,又分配了一個struct rte_mempool_memhdr *memhdr;結構的變量,就是這個變量管理着mempool的實際內存區,它記錄着mempool實際地址區的物理地址,虛擬地址,長度等信息。

再然后,就是把每個元素對應到mempool池中了:mempool_add_elem()。在其中,把每個元素都掛在了elt_list中,可以遍歷每個元素。最后rte_mempool_ops_enqueue_bulk(mp, &obj, 1);,最終,把元素對應的地址入隊,這樣,mempool中的每個元素都放入了ring中。

創建完成!!!

二 . mempool的使用

mempool的常見使用是獲取元素空間和釋放空間。

  • rte_mempool_get可以獲得池中的元素,其實就是從ring取出可用元素的地址。
  • rte_mempool_put可以釋放元素到池中。
  • rte_mempool_in_use_count查看池中已經使用的元素個數
  • rte_mempool_avail_count 查看池中可以使用的元素個數

三. 后記

mempool是DPDK內存管理的重要組件,這篇重點介紹了 mempool創建使用的過程,對於系統如何做大頁映射,統一地址並沒有涉及,希望在后面的篇幅中,關注一下大頁的映射和共享內存等。再往后,會介紹驅動與收發包等聯系較大的內容。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM