記憶體管理之函式mm init解讀之mem init

2021-07-27 14:51:44 字數 3991 閱讀 4536

在linux 記憶體管理中,在系統啟動時,函式start_kernel()呼叫mm_init()對記憶體相關的模組初始化。這裡我們關注mem_init()函式實現,這個是跟體系架構相關的,不同體系架構實現並不相同,但大致處理類似,即釋放記憶體到夥伴系統,對一些記憶體方面的全域性變數設定。我們下面看不同體系下實現:
arm:void __initmem_init(void)

}

arm64:void __initmem_init(void)

}

x86_32:
void __initmem_init(void)

x86_64:
void __init mem_init(void)

mips:
void __initmem_init(void)

上面這些函式儘管不同體系架構實現不同,甚至同一架構下不同位的架構也實現不同。我們這裡以arm64為例,說明這些函式主要處理:

首先我們需要明白mem_map作用,其是描述所有的物理記憶體採用的struct page結構的陣列的基指標。比如說,對於4gb的記憶體來說,如果乙個頁定義為4kb,即2^12位元組。那麼可想而知,總共這個mem_map陣列大小為2^20個。注意我們這裡都以flat型記憶體描述為主,即平坦型記憶體模型、

而這些頁都有乙個具體的頁幀號與之對應。頁幀號一般用pfn來表示,那麼由於每個頁都有乙個頁幀號,那最小的頁幀號和最大的頁幀號為多少呢?需要特別注意的是,頁幀號也是與mem_map陣列的index相對應。我們一般認為pfn_min為0,而最大pfn_max為mem_map陣列下標的最大值,這個最大值也就是max_pfn,這個值跟核心的max_mapnr相對應。

函式set_max_mapnr()就是用於計算max_mapnr。我們可能會想,這個max_pfn是什麼時候設定的呢?這個是在setup_arch的paging_init()中呼叫bootmem_init()來進行的。在成功設定max_mapnr後,我們要把啟動過程時所有的空閒記憶體釋放到夥伴系統,這裡需要注意三點:

一. bootmem記憶體管理或者nobootmem管理

二. memblock記憶體管理

三. 夥伴系統

顯然,啟動時,不存在夥伴系統,在linux 核心啟動的早期,bsp相關的**需要把核心能使用的記憶體塊大小告知核心,要麼通過bootload傳遞引數給出ddr大小,要麼通過命令列形式給出ddr大小,或者通過fdt等形式對dts分析得出ddr大小。不管什麼樣的方法,核心需要了解這些資訊,我們這裡以dts形式給出記憶體大小,這些記憶體塊會以

memblock_add形式新增到memblock記憶體管理塊中。這些新增到核心中的記憶體塊被標記為memory型別,另外一種型別為reserve型別。bsp可以通過不同方式新增到核心,

但是在我們核心使用記憶體之前,必須先新增一塊,否則我們使用的記憶體**來呢,使用的記憶體被標記為reserve。這樣,通過這種簡單的管理,memblock把所有的記憶體塊維護起來,之後核心慢慢的從這些記憶體塊中獲取記憶體。我們一般稱memblock是邏輯記憶體塊管理。

對於bootmem來說,它是物理記憶體管理。我們這裡不詳細介紹,後面會有篇章分析。

函式free_unused_memmap()和free_all_bootmem()都是把空閒記憶體釋放到夥伴系統,前者釋放memblock中空閒記憶體,後者釋放bootmem中記憶體。

函式mem_init_print_info()是把核心映像的各個段位址列印出來。我們這裡看下這個資訊:

[    0.000000] memory:832mb 2080mb=2912mb total[    0.000000] <0>    vmalloc : 0xffffff8000000000 - 0xffffffbbffff0000   (245759 mb)

[    0.000000] <0>    vmemmap : 0xffffffbc001dc000 - 0xffffffbc029c8000   (    39 mb)

[    0.000000] <0>    modules : 0xffffffbffc000000 - 0xffffffc000000000   (    64 mb)

[    0.000000] <0>    memory  : 0xffffffc000000000 - 0xffffffc0b6800000   (  2920 mb)

[    0.000000] <0>      .init : 0xffffffc000d29000 - 0xffffffc000f881c0   (  2429 kb)

[    0.000000] <0>      .text : 0xffffffc000080000 - 0xffffffc000d28da4   ( 12964 kb)

[    0.000000] <0>      .data : 0xffffffc000f89000 - 0xffffffc001088e78   (  1024 kb)

上面的832mb和2080mb是說明有兩個memblock,第乙個memblock大小為832mb,第二個為2080mb,所以,總共記憶體大小為2912mb。

對於memory:下面幾個數字來說, 2852320k是當前系統空閒的記憶體,這說明總共有713080個空閒頁。需要注意2852320k/2852320k

前面這個是當前系統空閒頁數,它是個動態變化的值。它是由函式nr_free_pages()得到,可以看到,其採用global_page_state(nr_free_pages)方式獲取的空閒記憶體,這個值會動態變化,每次申請記憶體時都會減少。而後面這個值是恆定的,它是從頁的page_count(page)得到,因為對於核心來說,如果bsp申請記憶體一定,這個值就應該恆定,它是每個memblock中除了reserve之後的記憶體,所以,在bsp開發期間,可以通過這個值來了解核心可用記憶體是否減少。

從上面給出的log可以看到,這兩個值相等,這說明memblock中reserve的值後並沒有動態申請記憶體,否則前面這個值應該減小。

最後這個129568k 是reserve的記憶體,那麼什麼是reserve的記憶體呢?其實這個reserve記憶體是相對free記憶體來說的。因為其無法再分配供其他程式使用,這部分記憶體一般是核心一些模組申請,如vfs_caches_init_early()分配的目錄項和索引節點hash,這些模組需要連續的物理記憶體,在系統啟動時可以方便獲取,這樣我們在系統早期分配後,對其標記為reserve,再比如核心**段,資料段等,這些都被標記。這樣看來,上面129568kb的reserve記憶體,即32392個頁被標記reserve。

空閒free頁 + reserve頁 = 總頁數 = 713080 + 32392 = 745472頁。

總記憶體2912mb = 745742個頁。

對於下面的核心映象記憶體布局則很容易理解了:

因為我們是64位系統,虛擬位址空間採用48位。

上面把物理記憶體完全對映到memory  : 0xffffffc000000000 - 0xffffffc0b6800000   (  2920 mb) 這個裡面。 這裡把0x0000000000000000 - 0xffffff8000000000-1 對映到使用者位址空間

0xffffffc000000000 - 0xffff,ffff,ffff,ffff 對映到核心空間

可以看到,核心空間已經足夠大,所以不需要高階記憶體,核心空間已經足夠囊括3gb的物理記憶體了。整個物理記憶體對映到0xffffffc000000000 - 0xffffffc0b6800000   範圍,這裡只所以有8mb的洞,是保護非連續區記憶體管理使用的。

可以看到,核心**段,init段,資料段大小。對於vmemmap和module段很容易理解其大小。這些都跟核心定義的一些巨集或者常熟相關。  

記憶體管理三之函式篇

1.vmalloc kmalloc 是用來分配核心空間記憶體的,malloc 是用來分配使用者空間的記憶體的。kmalloc分配的頁在實體地址上是連續的 虛擬位址自然也是連續的 vmalloc 只確保 頁在虛擬位址空間內是連續的。它通過非連續的物理記憶體塊,再 修正 頁表,把記憶體對映到邏輯位址空間...

記憶體管理函式

一 基本用法 include int brk void addr void sbrk intptr t increment 返回空間位址brk是系統呼叫,通過傳遞的addr來重新設定program break,成功返回0,否則返回 1。sbrk不是系統呼叫,是c庫函式,用來增加heap,增加的大小通...

iOS記憶體管理和malloc原始碼解讀

在接觸ios開發的時候,我們都知道 引用計數 的概念,也知道arc和mrr,但其實這僅僅是對堆記憶體上物件的記憶體管理。用wwdc某session裡的話說,這其實只是記憶體管理的冰山一角。一般的iphone實際物理記憶體都在1g左右,對於超大的記憶體需求怎麼辦?其實也是和其它作業系統一樣的,都是由系...