一 前言node
http://www.dpdk.org/ dpdk 是 intel 開發的x86芯片上用於高性能網絡處理的基礎庫,業內比較經常使用的模式是linux-app模式,即linux
利用該基礎庫,在用戶層空間作數據包處理,有了這個基礎庫,能夠方便地在寫應用層的網絡包處理高性能程序,目前該庫已經開源。數據庫
multicore framework 多核框架,dpdk庫面向intel i3/i5/i7/ep 等多核架構芯片,內置了對多核的支持數組
huge page memory 內存管理,dpdk庫基於linux hugepage實現了一套內存管理基礎庫,爲應用層包處理作了不少優化緩存
ring buffers 共享隊列,dpdk庫提供的無鎖多生產者-多消費者隊列,是應用層包處理程序的基礎組件網絡
poll-mode drivers 輪詢驅動,dpdk庫基於linux uio實現的用戶態網卡驅動數據結構
下面分幾個模塊說明上述組件的功能和實現。架構
二 內存管理app
1 hugepage技術框架
hugepage(2M/1G..)相對於普通的page(4K)來講有幾個特色,a hugepage 這種頁面不受虛擬內存管理影響,不會被替換出內存,而
普通的4kpage 若是物理內存不夠可能會被虛擬內存管理模塊替換到交換區。 b 一樣的內存大小,hugepage產生的頁表項數目遠少於4kpage.舉一個例子,用戶進程須要使用 4M 大小的內存,若是採用4Kpage, 須要1K的頁表項存放虛擬地址到物理地址的映射關係,而
採用hugepage 2M 只須要產生2條頁表項,這樣會帶來兩個後果,一是使用hugepage的內存產生的頁表比較少,這對於數據庫系統等
動不動就須要映射很是大的數據到進程的應用來講,頁表的開銷是很可觀的,因此不少數據庫系統都採用hugepage技術。二是tlb衝突率
大大減小,tlb 駐留在cpu的1級cache裏,是芯片訪問最快的緩存,通常只能容納100多條頁表項,若是採用hugepage,則能夠極大減小
tlb miss 致使的開銷:tlb命中,當即就獲取到物理地址,若是不命中,須要查 rc3->進程頁目錄表pgd->進程頁中間表pmd->進程頁框->物理內存,若是這中間pmd或者頁框被虛擬內存系統替換到交互區,則還須要交互區load回內存。。總之,tlb miss是性能大殺手,而採用
hugepage能夠有效下降tlb miss
linux 使用hugepage的方式比較簡單,
/sys/kernel/mm/hugepages/hugepages-2048kB/ 經過修改這個目錄下的文件能夠修改hugepage頁面的大小和總數目
mount -t hugetlbfs nodev /mnt/huge linux將hugepage實現爲一種文件系統hugetlbfs,須要將該文件系統mount到某個文件
mmap /mnt/huge 在用戶進程裏經過mmap 映射hugetlbfs mount 的目標文件,這個mmap返回的地址就是大頁面的了
2 多進程共享
mmap 系統調用能夠設置爲共享的映射,dpdk的內存共享就依賴於此,在這多個進程中,分爲兩種角色,第一種是主進程(RTE_PROC_PRIMARY),第二種是從進程(RTE_PROC_SECONDARY)。主進程只有一個,必須在從進程以前啓動,
負責執行DPDK庫環境的初始化,從進程attach到主進程初始化的DPDK上,主進程先mmap hugetlbfs 文件,構建內存管理相關結構
將這些結構存入hugetlbfs 上的配置文件rte_config,而後其餘進程mmap rte_config文件,獲取內存管理結構,dpdk採用了必定的
技巧,使得最終一樣的共享物理內存在不一樣進程內部對應的虛擬地址是徹底同樣的,意味着一個進程內部的基於dpdk的共享數據和指向
這些共享數據的指針,能夠在不一樣進程間通用。
內存全局配置結構rte_config
rte_config 是每一個程序私有的數據結構,這些東西都是每一個程序的私有配置。
lcore_role:這個DPDK程序使用-c參數設置的它同時跑在哪幾個核上。
master_lcore:DPDK的架構上,每一個程序分配的lcore_role 有一個主核,對使用者來講影響不大。
lcore_count:這個程序可使用的核數。
process_type:DPDK多進程:一個程序是主程序,不然初始化DPDK內存表,其餘從程序使用這個表。RTE_PROC_PRIMARY/RTE_PROC_SECONDARY
mem_config:指向設備各個DPDK程序共享的內存配置結構,這個結構被mmap到文件/var/run/.rte_config,經過這個方式多進程實現對mem_config結構的共享。
Hugepage頁表數組
這個是struct hugepage數組,每一個struct hugepage 都表明一個hugepage 頁面,存儲的每一個頁面的物理地址和程序的虛擬地址的映射關係。而後,把整個數組映射到文件/var/run /. rte_hugepage_info,一樣這個文件也是設備共享的,主/從進程都能訪問它。
file_id: 每一個文件在hugepage 文件系統中都有一個編號,就是數組1-N
filepath:%s/%smap_%file_id mount 的hugepage文件系統中的文件路徑名
size; 這個hugepage頁面的size,2M仍是1G
socket_id:這個頁面屬於那個CPU socket 。
Physaddr:這個hugepage 頁面的物理地址
orig_va:它和final_va同樣都是指這個huagepage頁面的虛擬地址。這個地址是主程序初始化huagepage用的,後來就沒用了。
final_va:這個最終這個頁面映射到主/從程序中的虛擬地址。
首先由於整個數組都映射到文件裏面,全部的程序之間都是共享的。主程序負責初始化這個數組,首先在它內部經過mmap把全部的hugepage物理頁面都映射到虛存空間裏面,而後把這種映射關係保存到這個文件裏面。從程序啓動的時候,讀取這個文件,而後在它內存也建立和它如出一轍的映射,這樣的話,整個DPDK管理的內存在全部的程序裏面都是可見,並且地址都同樣。
在對各個頁面的物理地址份配虛擬地址時,DPDK儘量把物理地址連續的頁面分配連續的虛存地址上,這個東西仍是比較有用的,由於CPU/cache/內存控制器的等等看到的都是物理內存,咱們在訪問內存時,若是物理地址連續的話,性能會高一些。
至於到底哪些地址是連續的,那些不是連續的,DPDK在這個結構之上又有一個新的結構rte_mem_config. Memseg來管理。由於rte_mem_config也映射到文件裏面,全部的程序均可見rte_mem_config. Memseg結構。
rte_mem_config
這個數據結構mmap 到文件/var/run /.rte_config中,主/從進程經過這個文件訪問實現對這個數據結構的共享。
在每一個程序內,使用rte_config .mem_config 訪問這個結構。
memseg
memseg 數組是維護物理地址的,在上面講到struct hugepage結構對每一個hugepage物理頁面都存儲了它在程序裏面的虛存地址。memseg 數組的做用是將物理地址、虛擬地址都連續的hugepage,而且都在同一個socket,pagesize 也相同的hugepage頁面集合,把它們都劃在一個memseg結構裏面,這樣作的好處就是優化內存。
Rte_memseg這個結構也很簡單:
phys_addr:這個memseg的包含的全部的hugepage頁面的起始地址
addr:這些hugepage頁面的起始的虛存地址
len:這個memseg的包含的空間size
hugepage_sz; 這些頁面的size 2M /1G?
這些信息都是從hugepage頁表數組裏面得到的。
free_memseg
上面memseg是存儲一個總體的這種映射的映射,最終這些地址是要被分配出去的。free_memseg記錄了當前整個DPDK內存空閒的memseg段,注意,這是對全部進程而言的。初始化等於memseg表示全部的內存都是free的,後面隨着分配內存,它愈來愈小。
必須得說明,rte_mem_config結構在各個程序裏面都是共享的,因此對任何成員的修改都必須加鎖。
mlock :讀寫鎖,用來保護memseg結構。
rte_memzone_reserve
通常狀況下,各個功能分配內存都使用這個函數分配一個memzone
const struct rte_memzone *
rte_memzone_reserve(const char *name, size_t len, int socket_id,
unsigned flags)
{
一、這裏就在free_memseg數組裏面找知足socket_id的要求,最小的memseg,從這裏面劃出來一塊內存,固然這時候free_memseg須要更新把這部份內存刨出去。
二、把這塊內存記錄到memzone裏面。
}
上面提到rte_memzone_reserve分配內存後,同時在rte_mem_config.memzone數組裏面分配一個元素保存它。對於從memseg中分配內存,以memzone爲單位來分配,對於全部的分配狀況,都記錄在memzone數組裏面,固然這個數組是多進程共享,你們都能看到。
在rte_mem_config結構裏面memzone_idx 變量記錄當前分配的memzone,每申請一次這個變量+1。
這個rte_memzone結構以下:
Name:給這片內存起個名字。
phys_addr:這個memzone 分配的內存區的物理地址
addr:這個memzone 分配的內存區的虛擬地址
len:這個zone的空間長度
hugepage_sz:這個zone的頁面size
socket_id:頁面的socket號
rte_memzone 是dpdk內存管理最終向客戶程序提供的基礎接口,經過 rte_memzone_reverse 能夠獲取基於
dpdk hugepage 的屬於同一個物理cpu的物理內存連續的虛擬內存也連續的一塊地址。rte_ring/rte_malloc/rte_mempool等
組件都是依賴於rte_memzone 組件實現的。
3 dpdk 內存初始化源碼解析
入口:
rte_eal_init(int argc,char ** argv) dpdk 運行環境初始化入口函數
—— eal_hugepage_info_init 這4個是內存相關的初始化函數
——rte_config_init
——rte_eal_memory_init
——rte_eal_memzone_init
3.1 eal_hugepage_info_init
這個函數比較簡單,主要是從 /sys/kernel/mm/hugepages 目錄下面讀取目錄名和文件名,從而獲取系統的hugetlbfs文件系統數,
以及每一個 hugetlbfs 的大頁面數目和每一個頁面大小,並保存在一個文件裏,這個函數,只有主進程會調用。存放在internal_config結構裏
3.2 rte_config_init
構造 rte_config 結構
rte_config_init
——rte_eal_config_create 主進程執行
——rte_eal_config_attach 從進程執行
rte_eal_config_create 和 rte_eal_config_attach 作的事情比較簡單,就是將 /var/run/.config 文件shared 型
mmap 到本身的進程空間的 rte_config.mem_config結構上,這樣主進程和從進程均可以訪問這塊內存,
rte_eal_config_attach
3.3 rte_eal_memory_init
rte_eal_memory_init
——rte_eal_hugepage_init 主進程執行,dpdk 內存初始化核心函數
——rte_eal_hugepage_attach 從進程執行
rte_eal_hugepage_init 函數分幾個步驟:
/*
* Prepare physical memory mapping: fill configuration structure with
* these infos, return 0 on success.
* 1. map N huge pages in separate files in hugetlbfs
* 2. find associated physical addr
* 3. find associated NUMA socket ID
* 4. sort all huge pages by physical address
* 5. remap these N huge pages in the correct order
* 6. unmap the first mapping
* 7. fill memsegs in configuration with contiguous zones
*/
函數一開始,將rte_config_init函數獲取的配置結構放到本地變量 mcfg 上,而後檢查系統是否開啓hugetlbfs,若是
不開啓,則直接經過系統的malloc函數申請配置須要的內存,而後跳出這個函數。
接下來主要是構建 hugepage 結構的數組 tmp_hp(上圖)
下面就是重點了。。
構建hugepage 結構數組分下面幾步
首先,循環遍歷系統全部的hugetlbfs 文件系統,通常來講,一個系統只會使用一種hugetlbfs ,因此這一層的循環能夠認爲
沒有做用,一種 hugetlbfs 文件系統對應的基礎數據包括:頁面大小,好比2M,頁面數目,好比2K個頁面
其次,將特定的hugetlbfs的所有頁面映射到本進程,放到本進程的 hugepage 數組管理,這個過程主要由 map_all_hugepages函數完成,
第一次映射的虛擬地址存放在 hugepage結構的 orig_va變量
第三,遍歷hugepage數組,找到每一個虛擬地址對應的物理地址和所屬的物理cpu,將這些信息也記入 hugepage數組,物理地址
記錄在hugepage結構的phyaddr變量,物理cpu號記錄在 hugepage結構的socket_id變量
第四,跟據物理地址大小對hugepage數組作排序
第五,根據排序結果從新映射,這個也是由函數 map_all_hugepages完成,從新映射後的虛擬地址存放在hugepage結構的final_va變量
第六,將第一次映射關係解除,即將orig_va 變量對應的虛擬地址空間返回給內核
下面看 map_all_hugepages的實現過程
這個函數是複用的,共有兩次調用。
對於第一次調用,就是根據hugetlbfs 文件系統的頁面數m,構造
m個文件名稱並建立文件,每一個文件對應一個大頁面,而後經過mmap系統調用映射到進程的一塊虛擬地址
空間,並將虛擬地址存放在hugepage結構的orig_va地址上。若是該hugetlbfs有1K個頁面,最終會在
hugetlbfs 掛載的目錄上生成 1K 個文件,這1K 個文件mmap到進程的虛擬地址由進程內部的hugepage數組維護
對於第二次調用,因爲hugepage數組已經基於物理地址排序,這些有序的物理地址可能有2種狀況,一種是連續的,
另外一種是不連續的,這時候的調用會遍歷這個hugepage數組,而後統計連續物理地址的最大內存,這個統計有什麼好處?
由於第二次的映射須要保證物理內存連續的其虛擬內存也是連續的,在獲取了最大連續物理內存大小後,好比是100個頁面大小,
會調用 get_virtual_area 函數向內涵申請100個頁面大小的虛擬空間,若是成功,說明虛擬地址能夠知足,而後循環100次,
每次映射mmap的首個參數就是get_virtual_area函數返回的虛擬地址+i*頁面大小,這樣,這100個頁面的虛擬地址和物理地址
都是連續的,虛擬地址存放到final_va 變量上。
下面看 find_physaddr的實現過程
這個函數的做用就是找到hugepage數組裏每一個虛擬地址對應的物理地址,並存放到 phyaddr變量上,最終實現由函數
rte_mem_virt2phy(const void * virt)函數實現,其原理至關於頁表查找,主要是經過linux的頁表文件 /proc/self/pagemap 實現
/proc/self/pagemap 頁表文件記錄了本進程的頁表,即本進程虛擬地址到物理地址的映射關係,主要是經過虛擬地址的前面若干位
定位到物理頁框,而後物理頁框+虛擬地址偏移構成物理地址,其實現以下
下面看 find_numasocket的實現過程
這個函數的做用是找到hugepage數組裏每一個虛擬地址對應的物理cpu號,基本原理是經過linux提供的 /proc/self/numa_maps 文件,
/proc/self/numa_maps 文件記錄了本 進程的虛擬地址與物理cpu號(多核系統)的對應關係,在遍歷的時候將非huge page的虛擬地址
過濾掉,剩下的虛擬地址與hugepage數組裏的orig_va 比較,實現以下
sort_by_physaddr 根據hugepage結構的phyaddr 排序,比較簡單
unmap_all_hugepages_orig 調用 mumap 系統調用將 hugepage結構的orig_va 虛擬地址返回給內核
上面幾步就完成了hugepage數組的構造,如今這個數組對應了某個hugetlbfs系統的大頁面,數組的每個節點是一個
hugepage結構,該結構的phyaddr存放着該頁面的物理內存地址,final_va存放着phyaddr映射到進程空間的虛擬地址,
socket_id存放着物理cpu號,若是多個hugepage結構的final_va虛擬地址是連續的,則其 phyaddr物理地址也是連續的。
下面是rte_eal_hugepage_init函數的餘下部分,主要分兩個方面,一是將hugepage數組裏 屬於同一個物理cpu,物理內存連續
的多個hugepage 用一層 memseg 結構管理起來。 一個memseg 結構維護的內存必然是同一個物理cpu上的,虛擬地址和物理
地址都連續的內存,最終的memzone 接口是經過操做memseg實現的;2是將 hugepage數組和memseg數組的信息記錄到共享文件裏,
方便從進程獲取;
遍歷hugepage數組,將物理地址連續的hugepage放到一個memseg結構上,同時將該memseg id 放到 hugepage結構
的 memseg_id 變量上
下面是建立文件 hugepage_info 到共享內存上,而後hugepage數組的信息拷貝到這塊共享內存上,並釋放hugepage數組,
其餘進程經過映射 hugepage_info 文件就能夠獲取 hugepage數組,從而管理hugepage共享內存