以 rte_mempool_ops_table 为例描述 dpdk 程序库链接顺序对程序执行的影响
dpdk mempool_ops
dpdk mempool_ops 是對舊版 mempool 代碼的抽象,在 dpdk-16.07 中被引入。
老版本在創建 mempool 時會創建一個內部的 ring 來完成入隊與出隊的操作,底層區分了多生產者、多消費者,單生產者、單消費者模型。
老版本 mempool 創建時會將所有的元素預先 enqueue 到 ring 中,并對每個元素執行初始化操作,這部分代碼隱含在 mempool 的內部實現中。用戶從 mempool 中申請、釋放 mem,最底層是通過 dequeue、enqueue ring 來實現的。
隨著 dpdk 支持場景的拓寬,在 mempool 底層使用 ring 這一種數據結構來控制 mem 的申請與釋放不能滿足所有場景的使用需求。
在一些場景下,一些外部的內存子系統在使用 DPDK 時需要對 mempool 底層的入隊與出隊的行為進行定制化開發,這意味著底層的這部分功能需要向外部開放,必須能夠讓程序注冊一個自定義的入隊、出隊方法及關聯數據結構,rte_mempool_ops 就是這種功能的具體實現。
rte_mempool_ops 的抽象
16.07 中 rte_mempool_ops 的定義如下:
struct rte_mempool_ops {char name[RTE_MEMPOOL_OPS_NAMESIZE]; /**< Name of mempool ops struct. */rte_mempool_alloc_t alloc; /**< Allocate private data. */rte_mempool_free_t free; /**< Free the external pool. */rte_mempool_enqueue_t enqueue; /**< Enqueue an object. */rte_mempool_dequeue_t dequeue; /**< Dequeue an object. */rte_mempool_get_count get_count; /**< Get qty of available objs. */ } __rte_cache_aligned;name 用于唯一標識每個 mempool_ops,alloc 用于 mempool_ops 內部數據結構的創建,free 用于 mempool_ops 內部數據結構的銷毀,enqueue 負責入隊列,dequeue 負責出隊列,get_count 用以獲取當前可用的對象數量。
總結起來有如下三部分功能:
以 ring 描述在 mempool_ops 框架下 mempool_ops 的使用過程
每一個 mempool_ops 需要實例化一個 rte_mempool_ops 結構并將此結構注冊到系統中,對單生產者與單消費者這種基于 ring 的模型而言它實例化的 rte_mempool_ops 定義如下;
static const struct rte_mempool_ops ops_sp_sc = {.name = "ring_sp_sc",.alloc = common_ring_alloc,.free = common_ring_free,.enqueue = common_ring_sp_enqueue,.dequeue = common_ring_sc_dequeue,.get_count = common_ring_get_count, };使用如下命令注冊:
MEMPOOL_REGISTER_OPS(ops_sp_sc);MEMPOOL_REGISTER_OPS 通過 gcc 的構造函數聲明,調用 rte_mempool_register_ops 函數將 ops_sp_sc mempool_ops 注冊到系統中。
rte_mempool_register_ops 在進行一系列的內容檢查后,將 ops_sp_sc 注冊到 rte_mempool_ops_table 表中(在獲取到互斥鎖的前提下,保證對 rte_mempool_ops_table 的互斥訪問)。
成功后,會獲取到 ops_index (ops 在 rte_mempool_ops_table 中的下標),這個下標會被保存到 mempool 結構的 ops_index 字段中(通過調用 rte_mempool_set_ops_byname 函數設置)。
其功能定義如下:
/*** Index into rte_mempool_ops_table array of mempool ops* structs, which contain callback function pointers.* We're using an index here rather than pointers to the callbacks* to facilitate any secondary processes that may want to use* this mempool.*/int32_t ops_index;mempool_ops 被調用的地方
使用 mempool_ops 時,通過 rte_mempool_get 函數出隊列過程函數調用圖示如下:
當 cache 中沒有緩存時,就調用底層 ops 中注冊的 dequeue 接口來完成。
dpdk-16.07 中相關 git commit
相關的 commit 信息如下:
commit 449c49b93a6b87506c7bb07468e82b539efddca3 Author: David Hunt <david.hunt@intel.com> Date: Wed Jun 22 10:27:27 2016 +0100mempool: support handler operationsUntil now, the objects stored in a mempool were internally stored in aring. This patch introduces the possibility to register external handlersreplacing the ring.The default behavior remains unchanged, but calling the new functionrte_mempool_set_ops_byname() right after rte_mempool_create_empty() allowsthe user to change the handler that will be used when populatingthe mempool.This patch also adds a set of default ops (function callbacks) basedon rte_ring.Signed-off-by: David Hunt <david.hunt@intel.com>Signed-off-by: Olivier Matz <olivier.matz@6wind.com>Acked-by: Shreyansh Jain <shreyansh.jain@nxp.com>Acked-by: Olivier Matz <olivier.matz@6wind.com>此接口在后面的版本有一些優化,但是主體框架沒有大的變動。注冊一個自己的 mempool_ops 的實例可以參照 vpp dpdk_plugins 中的實現代碼。
19.11 中 dpdk 程序初始化后 rte_mempool_ops_table 的布局情況
dpdk-19.11 中 dpdk 程序初始化后 rte_mempool_ops_table 結構示例如下圖:
上圖中,rte_mempool_ops_table 的不同表項指向不同的 mempool_ops 實例。
使用靜態庫時,鏈接不同 mempool_ops 所在庫的順序決定了 rte_mempool_ops_table 中不同 mempool_ops 的布局。如果 dpdk primary 進程與 secondary 進程鏈接不同 mempool_ops 構造函數所在庫的順序不同,則會有不同的 rte_mempool_ops_table 布局,當 mempool 需要在 primary 與 secondary 中共享時,不同的 rte_mempool_ops_table 布局就會帶來嚴重的問題!
第一種布局方式——dpdk 內部示例程序的布局
這里以 dpdk-pdump 為代表,其鏈接參數可以查看如下文件:
x86_64-native-linuxapp-gcc/build/app/pdump/.dpdk-pdump.cmd此種方式下 rte_mempool_ops_table 的前 4 個元素布局情況見下圖:
可以看到,mp_mc 這種基于 ring 的多生產者與多消費者占據 rte_mempool_ops_table 表中 ops 數組的第一個表項,examples 目錄下的 dpdk primary 程序中 rte_mempool_ops_table 表的布局與 app 下的程序布局一致。
使用 libdpdk.a 鏈接外部程序時的布局情況
libdpdk.a 中 mempool 相關靜態庫的鏈接順序如下:
cat ./x86_64-native-linux-gcc/lib/libdpdk.a GROUP (...librte_mempool_bucket.a librte_mempool_dpaa2.a librte_mempool_octeontx.a librte_mempool_octeontx2.a librte_mempool_ring.a librte_mempool_stack.a ...)此時程序運行后,rte_mempool_ops_table 表中 mempool_ops 的布局情況見下圖:
可以看到,此時 mempool_bucket_ops 占據 rte_mempool_ops_table 中 ops 的第一個元素,這與第一種情況是不同的。
問題描述
primary 進程使用 libdpdk.a 中的鏈接順序,使用第二種 rte_mempool_ops_table 布局方式,secondary 進程使用 dpdk-pdump,使用此程序來抓取報文。
問題現象:
打流情況下,dpdk-pdump 程序運行起來后會導致 primary 進程段錯誤。
dpdk-pdump dump 報文的原理淺析
dpdk-pdump 首先創建 pdump_tuples 中使能表項的不同字段,一個 pdump_tuples 表項 能夠完整的描述一個待 dump 的目標接口。
dpdk-pdump 會為每一個 pdump_tuples 創建單獨的 pktmbuf_pool 并使用 mp_mc_ops 這種 mempool_ops,同時會根據配置的功能,來創建相應的 ring。
pdump_tuples 及 vdev 接口初始化完成后,dpdk-pdump 會調 rte_pdump_enable_by_deviceid 來使能 dump 指定接口指定隊列上指定收、方向報文的功能。
rte_pdump_enable_by_deviceid 會使用 pdump_tuples 中配置表項的內容作為參數,構造一條請求,然后將這條請求通過本地套接字發送給 primary 進程。
primary 進程收到這條消息后,進行解析并調用 pdump_server 函數,pdump_server 調用 set_pdump_rxtx_cbs 來向請求的 port 與 queue 的 rx、tx 方向注冊回調函數,rx 這方為 pdump_rx,tx 這方為 pdump_tx。
完成了上述操作后,pdump_server 會向對方發送一個 reply,表明配置狀態。
當程序調用 rte_eth_rx_burst 時,成功收到報文后,會遍歷接收回調函數并執行,在這里就調用到了 pdump_rx 函數。
pdump_rx 函數使用 dpdk-pdump 發送請求中指定的 mempool 來申請 mbuf,然后拷貝報文到 mbuf 中,成功后就嘗試將報文投遞到 dpdk-pdump 發送請求中指定的 ring 中,失敗則直接釋放報文。
由于 dpdk 程序在初始化的過程中已經執行了一些內存的共享操作,primary 進程能夠直接使用 secondary 進程中的一些虛擬地址,這里的 mempool 的地址就是一個實例。
問題就出在這里!當 primary 進程收到包后,調用到 pdump_rx 函數時,在從 mempool 中申請 mbuf 的時候,由于 dpdk-pdump 與 primary 進程的 rte_mempool_ops_table 表中 mempool_ops 的布局不同,在 dpdk-pdump 對應 ops_mp_mc 這個 mempool_ops 的表項處,在 primary 進程中實際對應的是 bucket 的 mempool_ops,就造成了 primary 進程段錯誤!
解決方案
調整 libdpdk.a 中 mempool_ops 所在庫的鏈接順序,與 dpdk-pdump 保持一致,重新編譯 primary 程序。
總結
本文從 rte_mempool_ops 著手描述,目的在于說明由于庫鏈接順序的區別導致 dpdk primary 進程與 secondary 進程中 mempool_ops 在 rte_mempool_ops_table 占據不同的表項,進一步造成程序段錯誤的問題。
使用 gcc 的 constructor 修飾符來聲明構造函數,在使用靜態庫的情況下,鏈接順序就決定了初始化的順序,進而影響到了在不同程序中表單的布局,這一布局又隨著多個程序之間的交互被誤用,最終造成了嚴重的問題。
使用 constructor 修飾符讓 mempool_ops 的動態添加變得非常簡單,避免了硬編碼。但是使用了 constructor 的同時也繼承了 constructor 潛在的問題,即鏈接的順序決定了初始化的順序,這算是種隱式的依賴,不太容易發現!
正如一件事物有好有壞,一個技術也有好有壞,更準確點說應該是有優勢也有限制條件。我們應當做到既清楚它的優勢也清楚它的限制條件,正如我們需要明悟自己的長短一般,并不是那么容易!
本篇文章算是使用 groff 繪圖的一個開端,繪出的圖讓人挺滿意的,這是一個很好的開端,也是一個改變的點。
最后將 mempool_ops 中某張圖片的 pic 代碼貼到下面,僅供參考!
.PSdefine rte_mempool_ops { box "..........";box $1 fill 0.4;box $2 fill 0.6;}boxht=0.4; boxwid=2.5;A:box "sl"; down; box "num_ops" with .nw at A.sw; B:box "ops[0]" fill 0.5; C:box "ops[1]" fill 0.5; D: box "ops[2]" fill 0.5; E: box "ops[3]" fill 0.5; F:box "ops[...]" fill 0.5;boxwid=1.8;H:box "octeontx" with .nw at A.se + (2, -4) fill 0.2; rte_mempool_ops("otx2_npa_enq", "otx2_npa_deq");I:box "dpaa" with .nw at A.e + (2, 0.5) fill 0.2; rte_mempool_ops("dpaa_mbuf_free_bulk", "dpaa_mbuf_alloc_bulk");J:box "bucket" with .nw at A.se + (2, 3) fill 0.2; rte_mempool_ops("bucket_enqueue", "bucket_dequeue");K:box "dpaa2" with .nw at A.se + (2, -1.5) fill 0.2; rte_mempool_ops("irte_hw_mbuf_free_bulk", "rte_dpaa2_mbuf_alloc_bulk");"\fBrte_mempool_ops_table\fR" textwid 2 with .nw at A.c + (0, 0.4); "\fB mempool_octeontx_ops\fR" textwid 2 with .nw at H.c + (0, 0.4); "\fB mempool_dpaa_ops\fR" textwid 2 with .nw at I.c + (0, 0.4); "\fB mempool_bucket_ops\fR" textwid 2 with .nw at J.c + (0, 0.4); "\fB mempool_dpaa2_ops\fR" textwid 2 with .nw at K.c + (0, 0.4);line chop 0 chop 0 from B.e to J.w ->; line chop 0 chop 0 from C.e to I.w ->; line chop 0 chop 0 from D.e to K.w ->; line chop 0 chop 0 from E.e to H.w ->;總結
以上是生活随笔為你收集整理的以 rte_mempool_ops_table 为例描述 dpdk 程序库链接顺序对程序执行的影响的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TOPWAY智能彩色TFT液晶显示模块
- 下一篇: Epoll触发事件的类型(转载)