vpp编程
vpp編程與部分編譯問題
- VPP Infra
- 1.1 基本數據類型(types.h)
- 1.2 vec.h
- 1.3 bitmap.h
- 1.4 pool.h
- 1.5 heap.h
- 1.6 bihash.h
- 1.7 format.h(格式轉換)
- 1.8 elog.h(事件記錄)
- 1.9 time.h
- 時間輪
- 初始化一個雙定時器、單個 2048 槽輪,具有 1 秒定時器粒度[]
- 啟動計時器[]
- 停止計時器[]
- vlib
- 2.1 vlib node
- 2.2 plugins
- 2.3 vlib buffers
- vpp-pc編譯問題
VPP 的整體框架如下圖所示:
由上圖分析,VPP源碼模塊主要包括以下幾個層次,且關系如下:
| VPP Infra | 提供一些基本的通用的功能函數庫:包括內存管理,向量操作,hash, timer等 |
| VLIB | 主要提供基本的應用管理庫:buffer管理,graph node管理,線程,CLI,trace等 |
| Vnet | 提供網絡資源能力:比如設備,L2,L3,L4功能,session管理,控制管理,流量管理等 |
| Plugins | 主要為實現一些功能,在程序啟動的時候加載,一般情況下會在插件中加入一些node節點去實現相關功能 |
VPP Infra
VPPinfra 是基本 c 庫服務的集合,足以構建獨立程序以直接在裸機上運行。它還提供高性能動態數組、散列、位圖、高精度實時時鐘支持、細粒度事件記錄和數據結構序列化(指的是數據存儲類似數組的形式,而不是通過指針尋找next)。
我們先來看看vppinfra 給我們提供的structures:
1.1 基本數據類型(types.h)
u8, u16, u32, u64, u32x4 (intrinsics)
i8, …
f32, f64
uword
這個uword類型如下:
1.2 vec.h
Vppinfra 向量是無處不在的動態調整大小的數組,由用戶定義“header”。許多 vpppinfra 數據結構(例如hash、heap、pool)是具有各種不同header的向量。
內存布局如下所示:
User header (optional, uword aligned)Alignment padding (if needed)Vector length in elementsUser's pointer -> Vector element 0Vector element 1...Vector element N-1vector 這個概念很重要,vpp基本就是圍繞vector和node兩大概念展開的。下面這篇的作者寫的很好,原創連接:
vpp之vec學習
向量是一個帶有一些原數據自動調整大小的C數組:參數:類型T,頭部大小,元素對齊。
內存分配只會增加。
向量原點指針可能改變!
存儲索引(不是指針)!
為了避免內存分配器的混亂,人們經常將向量的長度重置為零,同時保留內存分配。通過 vec_reset_length(v) 宏將向量長度字段設置為零。向量元素可以是任何 C 類型,例如(int、double、struct bar)。對于建立在向量之上的數據類型(例如堆、池等)也是如此。
1.3 bitmap.h
操作和獲取位圖信息,可以根據需要分配任意bit的位圖。
從上面可以知道,申請的bitmap其實就是一個vector類型,這個vector的成員為uword類型,如果設備CPU是64位處理器,那么一個uword類型就是一個64bit的整數類型。
1.4 pool.h
Vppinfra pool結合了向量和位圖,以快速分配和釋放具有獨立生命周期的固定大小的數據結構。pool非常適合分配每個會話的結構。
池基于動態數組和位圖構建。主要用于需要頻繁申請和釋放固定大小內存的場合。先釋放到池里面,再次申請的時候,從池里面獲取,提升效率。這樣做,也能有效的避免內存碎片的產生。
| free_bitmap | 當前池里面,哪些內存塊是空閑的(未被使用的,置1),哪些是正在被使用的(置0)。 |
| free_indices | 一個動態數組,每一個元素是一個空閑塊的編號,數組長度與上述位圖中置1的bit數目相同。 |
| max_elts | 當前池最多支持的元素的個數 |
| mmap_base | 當前池的內存首地址 |
| mmap_size | 當前池的內存大小,字節數目。 |
池可以使用預分配的內存構建,也可以使用動態數組構建。max_elts 、mmap_base 、mmap_size 這3個元素僅用于預分配內存構建的池。
詳細介紹可以查看這篇文章:VPP代碼閱讀中文注解–pool.h
代碼中常見的幾個pool api記錄一下
| pool_alloc | 內存池擴容N個內存塊。這個時候,空閑內存塊向量也一并擴容,但len不變,只是把內存空間先申請到,免得后面申請不到空間。其實位圖內存塊也可以先把內存占到,但是這里并沒有這樣做,畢竟位圖占用的內存還是比較少,后面使用到時再申請。 |
| pool_elt_at_index | 返回第 i 號內存塊的地址。 |
| pool_elts | B3 |
| pool_foreach | 遍歷內存池中內存塊,執行用戶指定的操作BODY。主要利用了位圖的特性,一個uword為0,則uword內所有bit都為0。找出連續的0 bit, 分別對他們進行遍歷。 |
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Sawo4VtQ-1624928025083)($res/Snipaste_2021-06-02_15-04-49.png)]
分析一下上面的代碼:先調用pool_alloc保證向量有足夠的內存塊,pool_get 從向量表lru_pool申請一塊內存塊,返回值head指向這個內存塊,head - tsm->lru_pool;獲取這個內存塊在向量表lru_pool的位置信息index,類似于數組的下標,這個index用于記錄申請的內存塊,從上面的信息可以知道index是不變的,head每次都是變化的,head指向當前申請的內存塊。
1.5 heap.h
很少使用,這個可以跳過
1.6 bihash.h
Bihashes 是線程安全的。不需要讀鎖定。一個簡單的自旋鎖確保一次只有一個線程寫入一個條目。
VPP里的Bihash全名為Bounded-index extensible hash。它的最大特點是,在查找時是無鎖并且線程安全的。修改操作之間會有互斥,但是修改操作時仍然可以進行查找操作。
vpp里的Bihash優化成了兩種,bihash_kv_8_8和bihash_kv_24_8,區別在于hash key是8字節還是24字 節。最大限度的利用SSE4.2指令集中的_mm_crc32_u64來進行hash計算。如果CPU不支持英特爾SSE4指令集,那么就利用軟件或者編譯器計算。
核心函數在bihash_template.c中。根據包含的頭文件是bihash_8_8.h還是bihash_24_8.h,BV宏和BTV 宏將把名字做出對應擴展。例如:BV (clib_bihash_init)擴展為clib_bihash_init_8_8()或者 clib_bihash_init_24_8()。BVT (clib_bihash)擴展為clib_bihash_8_8_t或者為 clib_bihash_24_8_t。
這個vpp的bihash可以參照這篇文章:VPP-BIHASH實現分析
文章中指出BVT (clib_bihash_shared_header)這個結構定義的變量在多個CPU是共享的,我的理解是在這個數據在CPU多核之間是共享的,查看源代碼發現,這個數據BVT (clib_bihash_shared_header) * sh; 所指向的內存地址是通過linux mmap內存映射的方式實現的,進程1和進程2通過mmap的方式把內存的一塊物理地址(文件系統從磁盤讀取文件,把該文件放置在這個物理內存塊中)映射到各自的進程虛擬地址表中,從而實現共享文件的機制。具體描述可以參照下面這篇文章:mmap實現原理淺析
在任何一種情況下,人們幾乎總是在哈希表中查找鍵以獲得相關向量或池中的索引。API 非常簡單,但在使用非托管的任意大小的密鑰結構體時必須小心。Hash_set_mem(hash_table, key_pointer, value) 記憶key_pointer。將向量元素的地址作為第二個參數傳遞給 hash_set_mem 通常是一個嚴重的錯誤。記住文本段中的常量字符串地址是完全沒問題的之前做cfm和tpoam時,匹配不同的tpoam報文與cfm報文字段生成兩個key,這兩個key指向同一個session的index這個時候需要注意,key與value是一一對應的,后面的key會覆蓋之前的,遇到這種情況,key必須自己申請內存,自己管理,當然也可以修改value + 一個偏移量來存放新的key與value。存儲結構體的key需要特別小心,因為key是指針,這個密鑰結構體必須申請內存。
1.7 format.h(格式轉換)
這個是把你想格式的任何數據類型都轉為字符串的格式,便于查看打印信息。
大部分的數據轉化格式都有,如下所示:
unformat 相關的函數正好相反,用來把字符串轉化為數據。
下面是一個解析l2tp命令行的例子:
1.8 elog.h(事件記錄)
vppinfra 事件記錄器提供非常輕量級(低于 100 納秒)、精確時間戳的事件記錄服務。網址介紹:https://wiki.fd.io/view/VPP/elog
- Event-logging enabled in …/vlib/vlib/main.c:vlib_main(…)
- Use the elog_main_t in vlib_global_main, aka &vlib_global_main.elog_main
-
Default ring size 128K events
+Thread safe—lock-free atomic increment to dole out event slots -
Each event-slot is 32 bytes: u64 time-in-cpu-clocks, u16 event-id, u16 track, 20 bytes of data
每個記錄的事件槽32bytes,其中8個字節的cpu clocks,2字節的event-id和2字節的track,20個字節數據 -
Logging an event costs less than 100ns
-
每個節點、每個幀最多有幾個事件(這是說不讓隨便用這個event log嗎?)
從官方的資料知道,這個事件記錄器應該主要用于調試一些速率比較快的處理流程時,用來查看相關信息。命令行如下:
vpp# event-logger clear vpp# event-logger save <filename> # 為了安全,寫入/tmp/<filename>。# <文件名> 不能包含“.” 或 '/' 字符 vpp# show event-logger [all] [<nnn>] # 顯示事件日志# 默認情況下,最后 250 個條目此外,VPP 不光有event-logger,還有syslog、logging等調試信息,后續再研究他們用法的區別。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Dp5RFecL-1624928025098)($res/Snipaste_2021-06-03_11-05-58.png)]
后面的vppinfra官方居然不補充了,emm。。。,最重要的內存管理、線程調度、時間輪居然不講了,哎后面自己慢慢看吧
先記錄一下。
總結:
1.9 time.h
vlib/clib_time_now(…) 返回一個 64 位浮點值;自 vpp 啟動以來的秒數。vlib/clib_time_now_不_提供超過 1e-6 秒的精度。也就是說定時器最多精確到us級。
clib_cpu_time_now() 用ARM匯編指令獲取當前CPU clocks。
時間輪
Vppinfra 包括可配置的時間輪支持。參見…/src/vppinfra/tw_timer_template.[ch]中的源代碼,以及…/src/vppinfra/tw_timer_中定義的大量模板實例.[ch]。
tw_timer_template.h 的實例化生成命名結構以實現特定的時間輪幾何形狀。選項包括:時間輪的數量(當前為 1 或 2)、每個環的插槽數量(2 的冪)以及每個“對象句柄”的計時器數量。
在內部,用戶對象/定時器句柄是 32 位整數,因此如果選擇 16 個定時器/對象(4 位),則生成的定時器輪句柄限制為 2**28 個對象。
以下是生成單個 2048 插槽輪所需的特定設置,該輪支持每個對象 2 個計時器:
tw_timer_template.h 不打算直接#included。客戶端代碼可以包含多個計時器幾何頭文件,盡管在這種情況下使用 TW 和 TWT 宏需要格外小心。比如bfd模塊用的時間輪如下:
…/src/vppinfra/test_tw_timer.c 中的單元測試代碼提供了一個具體的 API 使用示例。它使用合成時鐘快速運行底層 tw_timer_expire_timers(…) 模板。
要調用的 API 例程并不多。
初始化一個雙定時器、單個 2048 槽輪,具有 1 秒定時器粒度[]
tw_timer_wheel_init_2t_1w_2048sl (&tm->single_wheel,expired_timer_single_callback,1.0 / * timer interval * / );啟動計時器[]
handle = tw_timer_start_2t_1w_2048sl (&tm->single_wheel, elt_index,[0 | 1] / * timer id * / ,expiration_time_in_u32_ticks);停止計時器[]
tw_timer_stop_2t_1w_2048sl (&tm->single_wheel, handle);vppinfra 總結:
- 存儲索引,而不存儲指針(調整數組大小)。
- 宏確實會更改參數(不僅是指針)。
- format和unformat功能很有用。
- 對齊對于多線程效率很重要。
vlib
VPP基于2個主要思想:
1.向量(捆綁在一起的0到256個緩沖區)
2.Node執行相關操作并傳遞向量(vectors)的node。
vlib是定義所有這些抽象模型的地方。不考慮任何網絡因素(無IP,無以太網等)。
它還包括:
- Counters(計數器)
- CLI(命令行)
- Scheduler(調度器)(main loop).
- 其他
2.1 vlib node
- VLIB_NODE TYPE_INTERNAL : 最典型的節點接收緩沖區向量,執行操作,包括TX節點。
- VLIB_NODE_TYPE_INPUT: 通常是設備輸入節點,從頭開始創建幀并將其分發到內部節點
- VLIB_ NODE_ TYPE_PRE_INPUT: input node之前被調用,幾乎不怎么用
- VLIB_NODE_ TYPE_ PROCESS:
類似線程一樣。
可以掛起,等待事件,恢復…
(基于setjump / longjump)。
2.2 plugins
2.3 vlib buffers
vlib_buffer_t是VPP包的基本數據結構(相當于DPDK的rte_mbuf)。
當DPDK==1時,vlib_buffer_t被封裝在rte_mbuf中。
#define vlib_buffer_from_rte_mbuf(x)((vlib_buffer_t*)(x+1))
#define rte_mbuf_from_vlib_buffer(x)(((struct rte_mbuf *)x)-1)
vpp-pc編譯問題
1.
sudo apt-get install libssl1.0-dev
總結
- 上一篇: EasyRecovery14免费激活码序
- 下一篇: EasyRecovery 15win/m