深入淺出記憶體管理--記憶體管理概述
記憶體管理我的理解是分為兩個部分,一個是實體記憶體的管理,另一個部分是實體記憶體地址到虛擬地址的轉換。
實體記憶體管理
核心中實現了很多機制和演算法來進行實體記憶體的管理,比如大名鼎鼎的夥伴系統,以及slab分配器等等。我們知道隨著Linux系統的執行,記憶體是不斷的趨於碎片化的,記憶體碎片分為兩種型別,一種為外碎片,所謂外碎片就是以頁為單位的記憶體之間的碎片化,另一種為內碎片,內碎片是指同一個頁面內的碎片化,那麼如果來優化這種記憶體碎片問題呢?
-
夥伴系統
夥伴系統可以用來減少外碎片的,通過更加合理的分配以頁為單位的記憶體,可以減少外碎片的產生,以儘量保持系統記憶體的連續性。 -
slab分配器
slab是用於優化內碎片問題的,通過把小塊記憶體以物件的方式管理起來,並且建立slab快取,方便同種型別物件的分配和釋放,減少了內碎片的產生,同時這些小塊記憶體會盡可能的保持在硬體cache中,所以極大提升了訪問效率。
實體記憶體管理這塊比較複雜,本文僅僅做一個簡述,關於這兩者的核心API以及實現,將在後續文章中再做介紹。
實體地址到虛擬地址的轉換
本文只介紹核心中訪問所有實體記憶體的方式,當前我們面對的問題是:如何實體記憶體對映到核心空間(3G-4G)這一段區域內?對於使用者空間訪問實體記憶體的話題,我們後續再開專門的文章進行介紹。
核心中把實體記憶體的低端區域作為直接對映區,高地址區域定義為高階記憶體,通過一個變數high_memory來界定他們的分界線。high_memory是一個虛擬地址,定義了高階記憶體被允許對映到核心的起始地址。
它在arm平臺上的定義如下:
void * high_memory;
EXPORT_SYMBOL(high_memory);
arm_lowmem_limit = lowmem_limit;
high_memory = __va(arm_lowmem_limit - 1) + 1;
if (!memblock_limit)
memblock_limit = arm_lowmem_limit;
以我的測試板子為例:
Memory: 1030548K/1048576K available (5078K kernel code, 221K rwdata, 1624K rodata, 1584K init, 179K bss, 18028K reserved, 0K cma-reserved, 270336K highmem) Virtual kernel memory layout: vector : 0xffff0000 - 0xffff1000 ( 4 kB) fixmap : 0xffc00000 - 0xfff00000 (3072 kB) vmalloc : 0xf0000000 - 0xff000000 ( 240 MB) lowmem : 0xc0000000 - 0xef800000 ( 760 MB) pkmap : 0xbfe00000 - 0xc0000000 ( 2 MB) modules : 0xbf000000 - 0xbfe00000 ( 14 MB) .text : 0xc0008000 - 0xc0693dd8 (6704 kB) .init : 0xc0694000 - 0xc0820000 (1584 kB) .data : 0xc0820000 - 0xc0857708 ( 222 kB) .bss : 0xc0857708 - 0xc0884700 ( 180 kB)
它的虛擬記憶體分佈如上所示。這塊資訊的實現程式碼如下:
pr_notice("Virtual kernel memory layout:\n"
" vector : 0x%08lx - 0x%08lx (%4ld kB)\n"
#ifdef CONFIG_HAVE_TCM
" DTCM : 0x%08lx - 0x%08lx (%4ld kB)\n"
" ITCM : 0x%08lx - 0x%08lx (%4ld kB)\n"
#endif
" fixmap : 0x%08lx - 0x%08lx (%4ld kB)\n",
MLK(UL(CONFIG_VECTORS_BASE), UL(CONFIG_VECTORS_BASE) +
(PAGE_SIZE)),
#ifdef CONFIG_HAVE_TCM
MLK(DTCM_OFFSET, (unsigned long) dtcm_end),
MLK(ITCM_OFFSET, (unsigned long) itcm_end),
#endif
MLK(FIXADDR_START, FIXADDR_END));
#ifdef CONFIG_ENABLE_VMALLOC_SAVING
print_vmalloc_lowmem_info();
#else
printk(KERN_NOTICE
" vmalloc : 0x%08lx - 0x%08lx (%4ld MB)\n"
" lowmem : 0x%08lx - 0x%08lx (%4ld MB)\n",
MLM(VMALLOC_START, VMALLOC_END),
MLM(PAGE_OFFSET, (unsigned long)high_memory));
#endif
printk(KERN_NOTICE
#ifdef CONFIG_HIGHMEM
" pkmap : 0x%08lx - 0x%08lx (%4ld MB)\n"
#endif
#ifdef CONFIG_MODULES
" modules : 0x%08lx - 0x%08lx (%4ld MB)\n"
#endif
" .text : 0x%p" " - 0x%p" " (%4d kB)\n"
" .init : 0x%p" " - 0x%p" " (%4d kB)\n"
" .data : 0x%p" " - 0x%p" " (%4d kB)\n"
" .bss : 0x%p" " - 0x%p" " (%4d kB)\n",
#ifdef CONFIG_HIGHMEM
MLM(PKMAP_BASE, (PKMAP_BASE) + (LAST_PKMAP) *
(PAGE_SIZE)),
#endif
#ifdef CONFIG_MODULES
MLM(MODULES_VADDR, MODULES_END),
#endif
MLK_ROUNDUP(_text, _etext),
MLK_ROUNDUP(__init_begin, __init_end),
MLK_ROUNDUP(_sdata, _edata),
MLK_ROUNDUP(__bss_start, __bss_stop));
我們通過上面機器的啟動log打印出來的memory layout可以知道,在3G以下的區域也是被核心資料所佔用了,可是上面不是說使用者空間是0-3G嗎?這裡不會被使用者所佔用導致衝突嗎?
實際上,使用者空間的對映區定義如下:
00001000 TASK_SIZE-1 User space mappings
Per-thread mappings are placed here via
the mmap() system call.
這裡TASK_SIZE實際上並不是PAGE_OFFSET-1,而是中間間隔了一段區域(16M):
/*
* TASK_SIZE - the maximum size of a user space task.
* TASK_UNMAPPED_BASE - the lower boundary of the mmap VM area
*/
#define TASK_SIZE (UL(CONFIG_PAGE_OFFSET) - UL(SZ_16M))
#define TASK_UNMAPPED_BASE ALIGN(TASK_SIZE / 3, SZ_16M)
低端記憶體對映
核心空間1G的虛擬空間,其中有一部分用於直接對映,線性對映區,在arm32平臺上,實體地址[0:760M]這部分記憶體被線性的對映到[3G:3G+760M]的虛擬地址上,剩餘的264M虛擬地址做什麼呢?
是保留給高階記憶體對映使用的,這部分是能夠動態分配和釋放的,因為平臺上實際的實體記憶體可能會超過1G,那麼核心必須要具有能夠定址到整個實體記憶體的能力。線性對映區在啟動時就完成了頁表的建立,沒有必要再過多介紹。
測試平臺上的線性對映區域:
lowmem : 0xc0000000 - 0xef800000 ( 760 MB)
對應的解釋如下:
PAGE_OFFSET high_memory-1 Kernel direct-mapped RAM region.
This maps the platforms RAM, and typically
maps all platform RAM in a 1:1 relationship.
高階記憶體對映
針對高階記憶體的對映,核心又劃分了多個區域,因為需要在264M有限的區域內去訪問除了760M之外的所有實體記憶體,所以這部分相比線性對映區將變得更加複雜。核心有三種方式用於將高階記憶體對映到核心空間,分別是pkmap、fixmap和vmalloc。
- pkmap
測試平臺上的資料如下:
pkmap : 0xbfe00000 - 0xc0000000 ( 2 MB)
說明:
PKMAP_BASE PAGE_OFFSET-1 Permanent kernel mappings
One way of mapping HIGHMEM pages into kernel
space.
永久核心對映區,對映高階記憶體到核心空間的一種方式。pkmap在用於對映高階實體記憶體的,當我們從夥伴系統中分配到高階記憶體後,是無法直接操作的,必須要經過map操作,此時就可以使用pkmap,它對應的
核心API為
void *kmap(struct page *page);
傳入的是一個實體記憶體頁對應的struct page結構體,返回一個虛擬地址。使用方法如下:
使用alloc_pages()在高階儲存器區得到struct page結構,然後呼叫kmap(struct *page)在核心地址空間[PKMAP_BASE : PAGE_OFFSET-1]中建立永久對映,如果page結構對應的是低端實體記憶體的頁,該函式僅僅返回該頁對應的虛擬地址。
另外需要注意kmap()可能引起睡眠,所以不能用在中斷和持有鎖的程式碼中使用。從使用方法上我們知道,它是針對struct page來進行的操作,所以至少會對映一個page。
- fixmap
測試平臺上的資料如下:
fixmap : 0xffc00000 - 0xfff00000 (3072 kB)
說明:
ffc00000 ffefffff Fixmap mapping region. Addresses provided
by fix_to_virt() will be located here.
fixmap也叫臨時對映區,他是一個固定的一塊虛擬空間用於對映不同的實體地址,並且是在申請時使用,不用時釋放。它於pkmap的區別在於這塊地址的對映不會引起睡眠,是可以在中斷和持有鎖的程式碼中執行的,它的核心API如下:
void *kmap_atomic(struct page *page);
- vmalloc
測試平臺上的資料如下:
vmalloc : 0xf0000000 - 0xff000000 ( 240 MB)
lowmem : 0xc0000000 - 0xef800000 ( 760 MB)
說明:
VMALLOC_START VMALLOC_END-1 vmalloc() / ioremap() space.
Memory returned by vmalloc/ioremap will
be dynamically placed in this region.
Machine specific static mappings are also
located here through iotable_init().
VMALLOC_START is based upon the value
of the high_memory variable, and VMALLOC_END
is equal to 0xff800000.
從平臺列印的資料來看,vmalloc和lowmem線性對映區並沒有完全緊靠著,而是中間有一個hole空洞(8M),這個8M的空間是為了捕獲越界訪問的。
vmalloc會分配非連續實體記憶體,這裡的非連續指的是實體記憶體不連續,虛擬地址是連續的,優先使用高階記憶體來分配物理頁,如果分配失敗,才會從Normal zone分配。這個介面和上面的都不同,它會自動分配實體記憶體,然後完成對映後直接返回虛擬地址,而上面兩個都是隻進行對映。
void *vmalloc(unsigned long size);