Linux 記憶體管理【轉】
轉自:https://www.jianshu.com/p/eecbb1506eee
Linux 記憶體管理
1 頁的概念
linux 核心中把物理頁作為記憶體分配的最小單位,32位CPU 頁的大小通常為4K,64位的CPU通常支援8K的也。記憶體管理單元MMU 同樣以頁為大小分配記憶體。
2 核心虛擬地址分割槽和實體記憶體分割槽
在32位核心中,核心虛擬地址空間為0-4G,其中使用者態為1-3G空間,核心態為3G-4G,核心空間根據實體地址的特性大概可以分為三個區:
區 | 描述 | 32位系統實體記憶體大小 |
---|---|---|
ZONE_DMA | 和硬體操作相關的記憶體區域 | < 16M |
ZONE_NORMAL | 核心正常對映的物理頁 | 16 - 896M |
ZONE_HIGH | 高階記憶體,由於核心空間大小的原理部分頁不能永久的對映到核心,需要動態對映的 | > 896M |
下面的圖描述了核心地址空間和實體記憶體的對映關係:
32位 核心記憶體分割槽
Linux 核心啟動後的mm 的初始化過程:
/*
* Set up kernel memory allocators
*/
static void __init mm_init(void)
{
/*
* page_ext requires contiguous pages,
* bigger than MAX_ORDER unless SPARSEMEM.
*/
page_ext_init_flatmem();
mem_init();
kmem_cache_init();
percpu_init_late();
pgtable_init();
vmalloc_init();
ioremap_huge_init();
}
3夥伴系統演算法
3.1 簡介
在實際應用中,經常需要分配一組連續的頁,而頻繁地申請和釋放不同大小的連續頁,必然導致在已分配頁框的記憶體塊中分散了許多小塊的空閒頁框。這樣,即使這些頁框是空閒的,其他需要分配連續頁框的應用也很難得到滿足。為了避免出現這種情況,Linux核心中引入了夥伴系統演算法(buddy system)。把所有的空閒頁框分組為11個塊連結串列,每個塊連結串列分別包含大小為1,2,4,8,16,32,64,128,256,512和1024個連續頁框的頁框塊。最大可以申請1024個連續頁框,對應4MB大小的連續記憶體。每個頁框塊的第一個頁框的實體地址是該塊大小的整數倍。
假設要申請一個256個頁框的塊,先從256個頁框的連結串列中查詢空閒塊,如果沒有,就去512個頁框的連結串列中找,找到了則將頁框塊分為2個256個頁框的塊,一個分配給應用,另外一個移到256個頁框的連結串列中。如果512個頁框的連結串列中仍沒有空閒塊,繼續向1024個頁框的連結串列查詢,如果仍然沒有,則返回錯誤。頁框塊在釋放時,會主動將兩個連續的頁框塊合併為一個較大的頁框塊。
mem_init() 函式中會把核心啟動後的空閒記憶體用buddy 系統管理。
3.2 夥伴系統演算法分配函式
mem_init 初始化完夥伴系統後通過 alloc_page(s) 函式分配夥伴系統記憶體池的記憶體。
函式 | 描述 |
---|---|
struct page * alloc_page(unsigned int gfp_mask) | 分配一頁實體記憶體並返回該頁實體記憶體的page結構指標 |
struct page * alloc_pages(unsigned int gfp_mask, unsigned int order) | 分配2的order次方連續的物理頁並返回分配的第一個物理頁的page結構指標 |
unsigned long get_free_page(unsigned int gfp_mask) | 只分配一頁,返回頁的邏輯地址 |
unsigned long __get_free_pages(unsigned int gfp_mask, unsigned int order) | 分配 2的order頁,返回也是邏輯地址 |
3.3 get_free_page(s)與alloc_page(s)的差異
alloc_page alloc_pages 分配後還不能直接使用, 需要得到該頁對應的虛擬地址
- void *page_address(struct page *page);
- 低端記憶體的對映方式:__va((unsigned long)(page - mem_map) << 12)
- 高階記憶體到對映方式:struct page_address_map分配一個動態結構來管理高階記憶體。(核心是訪問不到vma的3G以下的虛擬地址的) 具體對映由kmap / kmap_atomic執行。
get_free_page(s)與alloc_page(s)系列最大的區別是無法申請高階記憶體,因為它返回到是一個邏輯地址,而高階記憶體是需要額外對映才可以
Android x86 的buffyinfo.
以Normal區域進行分析,第二列值為459,表示當前系統中normal區域,可用的連續兩頁的記憶體大小為459*2^1*PAGE_SIZE;第三列值為52,表示當前系統中normal區域,可用的連續四頁的記憶體大小為52*2^2*PAGE_SIZE
generic_x86:/ # cat /proc/buddyinfo
Node 0, zone DMA 4 1 2 2 3 2 3 1 2 0 1
Node 0, zone Normal 1186 459 220 142 25 13 2 0 1 2 138
Node 0, zone HighMem 87 74 12 9 0 1 1 0 0 0 0
4 Slab 記憶體分配演算法
Slab 記憶體分配演算法 和Java中的物件池是一個概念。採用buddy演算法,解決了外碎片問題,這種方法適合大塊記憶體請求,不適合小記憶體區請求
4.1 Slab 記憶體分配演算法
slab分配器源於 Solaris 2.4 的分配演算法,工作於物理記憶體頁框分配器之上,管理特定大小物件的快取,進行快速而高效的記憶體分配。slab分配器為每種使用的核心物件建立單獨的緩衝區。Linux 核心已經採用了夥伴系統管理實體記憶體頁框,因此 slab分配器直接工作於夥伴系統之上。每種緩衝區由多個 slab 組成,每個 slab就是一組連續的實體記憶體頁框,被劃分成了固定數目的物件。根據物件大小的不同,預設情況下一個 slab 最多可以由 1024個頁框構成。出於對齊等其它方面的要求,slab 中分配給物件的記憶體可能大於使用者要求的物件實際大小,這會造成一定的記憶體浪費。
Linux 所使用的 slab 分配器的基礎是 Jeff Bonwick 為SunOS 作業系統首次引入的一種演算法。Jeff的分配器是圍繞物件快取進行的。在核心中,會為有限的物件集(例如檔案描述符和其他常見結構)分配大量記憶體。Jeff發現對核心中普通物件進行初始化所需的時間超過了對其進行分配和釋放所需的時間。因此他的結論是不應該將記憶體釋放回一個全域性的記憶體池,而是將記憶體保持為針對特定目而初始化的狀態。例如,如果記憶體被分配給了一個互斥鎖,那麼只需在為互斥鎖首次分配記憶體時執行一次互斥鎖初始化函式(mutex_init)即可。後續的記憶體分配不需要執行這個初始化函式,因為從上次釋放和呼叫析構之後,它已經處於所需的狀態中了。
4.2 Slab 記憶體結構
kmem_cache_alloc 分配的所有的記憶體塊在核心中以連結串列的形式組織。
kmem_cache_alloc 從buddy系統分配到記憶體後,在內部被分為 slab 單元,這是一段連續的記憶體塊(通常都是頁面)。所有的物件都分配在這些slab 單元上,這些slab 單元被組織為三個連結串列:
- slabs_full 完全分配的 slab
- slabs_partial 部分分配的 slab
- slabs_free 可以回收的 slab
4.3 slab 著色區和slab 結構
每個Slab的首部都有一個小小的區域是不用的,稱為“著色區(coloring area)”。著色區的大小使Slab中的每個物件的起始地址都按快取記憶體中的”快取行(cache line)”大小進行對齊(80386的一級快取記憶體行大小為16位元組,Pentium為32位元組)。因為Slab是由1個頁面或多個頁面(最多為32)組成,因此,每個Slab都是從一個頁面邊界開始的,它自然按快取記憶體的緩衝行對齊。但是,Slab中的物件大小不確定,設定著色區的目的就是將Slab中第一個物件的起始地址往後推到與緩衝行對齊的位置。每個Slab上最後一個物件以後也有個小小的區是不用的,這是對著色區大小的補償,其大小取決於著色區的大小,以及Slab與其每個物件的相對大小。
slab 記憶體分配4.4 Slab 記憶體函式
mm_init --> kmem_cache_init(); kernel 初始化
- kmem_cache_t* xx_cache; // 連結串列頭
- 建立: xx_cache = kmem_cache_create("name", sizeof(struct xx), SLAB_HWCACHE_ALIGN, NULL, NULL);
- 分配: kmem_cache_alloc(xx_cache, GFP_KERNEL);
- 釋放: kmem_cache_free(xx_cache, addr);
slab 記憶體用結構體 kmem_cache_t 表示:
4.5 slabinfo物件
從 /proc/slabinfo 中看一看出,核心為大結構體使用了slab 快取。如ext4_inode_cache vm_area_struct task_struct等。
generic_x86:/ # cat /proc/slabinfo
slabinfo - version: 2.1
# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
...
ext4_inode_cache 2025 2025 632 25 4 : tunables 0 0 0 : slabdata 81 81 0
ext4_allocation_context 156 156 104 39 1 : tunables 0 0 0 : slabdata 4 4 0
ext4_prealloc_space 224 224 72 56 1 : tunables 0 0 0 : slabdata 4 4 0
ext4_io_end 408 408 40 102 1 : tunables 0 0 0 : slabdata 4 4 0
ext4_extent_status 2048 2048 32 128 1 : tunables 0 0 0 : slabdata 16 16 0
...
vm_area_struct 20791 22402 88 46 1 : tunables 0 0 0 : slabdata 487 487 0
mm_struct 85 85 480 17 2 : tunables 0 0 0 : slabdata 5 5 0
...
task_struct 621 621 1184 27 8 : tunables 0 0 0 : slabdata 23 23 0
...
kmalloc-8192 28 28 8192 4 8 : tunables 0 0 0 : slabdata 7 7 0
kmalloc-4096 96 104 4096 8 8 : tunables 0 0 0 : slabdata 13 13 0
kmalloc-2048 128 128 2048 16 8 : tunables 0 0 0 : slabdata 8 8 0
kmalloc-1024 336 336 1024 16 4 : tunables 0 0 0 : slabdata 21 21 0
kmalloc-512 752 752 512 16 2 : tunables 0 0 0 : slabdata 47 47 0
kmalloc-256 698 752 256 16 1 : tunables 0 0 0 : slabdata 47 47 0
kmalloc-192 903 903 192 21 1 : tunables 0 0 0 : slabdata 43 43 0
kmalloc-128 1760 1760 128 32 1 : tunables 0 0 0 : slabdata 55 55 0
kmalloc-