?
原文地址:http://blog.csdn.net/ariesjzj/article/details/8764102
好的操作系統必然要有好的內存管理系統來支持。好的內存管理系統就像一個藝術品,因為在其中我們可以看到空間優化和時間優化的完美平衡(既要省內存又要分配和釋放足夠快)。Linux為我們提供了這樣一個范例,關于它的內存管理在很多講kernel的書都可以找到。但在這一切還沒有建立起來時,系統又是怎么工作的呢?
在系統啟動時內存分配大致經歷了這樣幾個階段(基于kernel 2.6.29):
1. 靜態分配(如果這也算一種的話。。。)
2. e820表
3. bootmem allocator
4. zone allocator(buddy system)
5. slab allocator
6. 虛擬空間分配,如用vmalloc, mmap這些函數分配
當然以上的幾個階段的時間界限并不總是很明顯,有些時候是并存的。以下是初始化代碼中幾個關鍵點:
start_kernel() setup_arch() setup_memory_map() //從boot_params.e820_map讀入e820表信息,以后就可以用find_e820_area()分配了。 init_memory_mapping() kernel_physical_mapping_init() //在虛擬空間映射kernel頁表的low mem部分,創建頁表過程中需要分配內存就是通過find_e820_area()。 initmem_init() setup_bootmem_allocator() //初始化bootmem allocator,bootmem allocator可用。 early_res_to_bootmem() //把之前靜態分配或者從通過find_e820_area()分配的區域置成保留。 paging_init() //完成kernel頁表high mem中persistent kernel mapping和temporary kernel mapping部分的初始化,之后可以通過kmap()或kmap_atomic()把物理頁映射到high mem區域。 zone_sizes_init() //初始化zone allocator,但只是初始化,還沒法用它分配,因為所有的freelist還是被置成空的。 vmalloc_init() //初始化分配noncontiguous memory area所需要的結構。vmalloc能分配high mem中從VMALLOC_START到VMALLOC_END的虛擬空間。加上前面paging_init()中提到的兩種,針對kernel的high mem的三種映射方式就全了。該函數中通過bootmem allocator分配自身需要的內存。 mem_init() //完成zone allocator,也就是buddy system的初始化,之后alloc_page()就可以用了。這里將bootmem allocator中的未分配空間轉到zone allocator中,然后禁用了bootmem allocator。 kmem_cache_init() //初始化slab allocator。它是zone allocator上的一層加強,彌補了zone allocator的一些固有不足,如只能以2的n次冪分配物理頁。kmalloc()會從slab allocator上分配,而slab allocator中cache不夠又會從zone allocator分配。
?
系統啟動剛開始的一些數據是靜態分配的,如kernel本身的代碼段和數據段,因為這時還沒有任何分配器存在。這些都被loader存放在固定的物理地址,并被臨時頁表映射到固定的虛擬地址。
e280表和find_e820_area()可以稱得上最早的allocator,盡管它很簡單。系統啟動早期,detect_memory()函數中,系統通過15h中斷從BIOS中讀取物理內存信息,將之放到boot_params(也就是zeropage中)。之后set_memory_map()函數將這些信息再讀入e820結構體里,之后find_e820_area()就可以從里面分配內存了。分配方式采用簡單的線性查找,并把分配出去的空間通過reserve_early()記錄到early_res這個結構中,這些信息將會在bootmem allocator的初始化時用來置位那些已分配的物理內存區域。舉例來說,當系統要建立kernel頁表時,需要申請頁表本身所占的內存,于是調用one_page_table_init(),它發現bootmem allocator尚不可用,于是調用alloc_low_page(),這個函數就會到[table_start, start_end]這個區域里去拿內存,而這塊內存是在之前find_early_table_space()中通過find_e820_area()申請出來的。當kernel頁表建立完后,reserve_early()被調用,它將[table_start, table_end]這塊區域以"PGTABLE"為label記錄下來。
然后是bootmem allocator,它和e820直接分配一樣,也是一個中間過渡產物。bootmem allocator,顧名思義就是在系統啟動時候用的臨時內存分配器。它在zone allocator建立好之后就被禁用,而在其中仍然空閑的區域會被回收到zone allocator中。bootmem allocator是一種基于bitmap的分配器,因此速度也很快。從bootmem allocator分配使用函數alloc_bootmem()。
再就是zone allocator。我們知道典型的系統上有三個zone:ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM。系統為每個zone都建立了我們熟悉的buddy system。簡單地說,buddy system把物理內存區域按2的n次方(n稱為order)掛在zone->free_area上。分配的時候拆分它們直到滿足分配申請要求,釋放的時候再進行合并。從zone allocator分配和釋放分別用函數alloc_page()和free_page()。
Buddy system非常高效,但帶來了內部碎片問題,因為它只能分配出2的0次方到2的MAX_ORDER次方的頁,而且它也不利于硬件cache的利用。而內核中經常會頻繁申請固定大小的內存如process descriptor, open file object等。如果每次都從buddy system中申請,既費時間又費空間。出于空間和時間的效率考慮,于是有了slab allocator。slab allocator相當于將內存資源按每種固定大小進行緩存,放在cache中。系統要的時候直接從這個cache里拿,而釋放時則不是真的釋放,而是放回到cache中。slab allocator維護很多cache,每個cache中包含同一類型的boject。cache又劃分為slab,slab通常包含幾個連接的物理頁,其中存放在被分配的或者尚空閑的object。對于slab allocator中的內存資源,調用kmalloc()或者直接調用kmem_cache_alloc()進行分配,調用kfree()或者直接調用kmem_cache_free()進行釋放。當kmem_cache_alloc()被調用且cache中沒有object時,會調用cache_grow()來增加cache中的slab,這也是slab的創建過程。cache_grow()繼而調用kmem_getpages()為object分配物理內存。而kmem_getpages最終會到buddy system中去分配(kmem_getpages() => alloc_pages_node() => __alloc_pages())。因此我們說slab allocator不是buddy system的替代,還是加強。
之后,系統的內存管理系統就初步建立好了。系統中的內存資源有兩種-虛擬空間和物理空間。在kernel態,用vmalloc()申請虛擬空間,同時它也調用了alloc_page()申請物理空間,再映射到虛擬空間中。而kmalloc可以直接從slab allocator中申請物理空間,而slab allocator中如果內存不夠了再到buddy system中去分配。這樣申請來的物理空間在虛擬地址空間中還沒有顯式映射,當然了,如果是low mem部分則已經在系統初始化時被映射到PAGE_OFFSET處了。而user態中如果app調用malloc這樣的函數,malloc會調用mmap,而mmap會申請調用進程的虛擬空間,這里虛擬空間還沒有對應的物理頁。只有真地訪問時發生page fault了,在pagefault handler里才會從buddy system中去分配物理頁。
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
