DPDK(11):內存初始化

寫的很好的文章,我應該是不能寫的比這個更加清晰了。

需要指出的一點的時DPDK多進程通過將大葉內存映射到相同的虛擬地址,達到通過隊列發送報文到另外進程只需發送指針即可,能夠取到相同的內容。DPDK的內存管理是DPDK一個很重要的點,是DPDK的基礎。


轉自:http://www.cnblogs.com/jiayy/p/3429725.html

一  前言

http://www.dpdk.org/  dpdk 是 intel 開發的x86芯片上用於高性能網絡處理的基礎庫,業內比較常用的模式是linux-app模式,即

利用該基礎庫,在用戶層空間做數據包處理,有了這個基礎庫,可以方便地在寫應用層的網絡包處理高性能程序,目前該庫已經開源。

Main libraries

  • multicore framework   多核框架,dpdk庫面向intel i3/i5/i7/ep 等多核架構芯片,內置了對多核的支持
  • huge page memory  內存管理,dpdk庫基於linux hugepage實現了一套內存管理基礎庫,爲應用層包處理做了很多優化
  • ring buffers          共享隊列,dpdk庫提供的無鎖多生產者-多消費者隊列,是應用層包處理程序的基礎組件
  • poll-mode drivers     輪詢驅動,dpdk庫基於linux uio實現的用戶態網卡驅動

下面分幾個模塊說明上述組件的功能和實現。

二 內存管理

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)

{

1、這裏就在free_memseg數組裏面找滿足socket_id的要求,最小的memseg,從這裏面劃出來一塊內存,當然這時候free_memseg需要更新把這部分內存刨出去。

2、把這塊內存記錄到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共享內存

 

發佈了24 篇原創文章 · 獲贊 8 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章