1. 程式人生 > >深入淺出記憶體管理--記憶體管理概述

深入淺出記憶體管理--記憶體管理概述

記憶體管理我的理解是分為兩個部分,一個是實體記憶體的管理,另一個部分是實體記憶體地址到虛擬地址的轉換。

實體記憶體管理

核心中實現了很多機制和演算法來進行實體記憶體的管理,比如大名鼎鼎的夥伴系統,以及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);