記憶體管理五 實體記憶體初始化
一、概序
linux記憶體管理包含記憶體初始化、頁表對映過程、核心記憶體佈局圖、夥伴系統、SLAB分配器、vmalloc、malloc、mmap缺頁中斷等內容。按層分可以分為使用者空間、核心空間和硬體層,下面的圖可以詳細的說明:
二、核心記憶體分佈
1、實體記憶體大小:
現在裝置的管理方式都是通過裝置數來管理,memory的大小也同樣在裝置樹的DTS中定義如下:
memory {
device_type = "memory";
reg = <0 0x80000000 0 0x20000000>;
};
在核心啟動start_kernel函式中回去獲取對應的memory的起始地址和大小,並加入到memblock中,因為在核心啟動的初期,夥伴系統等分配的記憶體的方式還未初始化好,所以通過memblock的方式來分配管理記憶體。其呼叫流程如下:
start_kernel -> setup_arch -> setup_machine_fdt -> early_init_dt_scan_nodes -> early_init_dt_scan_memory int __init early_init_dt_scan_memory(unsigned long node, const char *uname, int depth, void *data) { /* We are scanning "memory" nodes only */ if (type == NULL) { if (!IS_ENABLED(CONFIG_PPC32) || depth != 1 || strcmp(uname, "
[email protected]") != 0) return 0; } else if (strcmp(type, "memory") != 0) return 0; base = dt_mem_next_cell(dt_root_addr_cells, ®); size = dt_mem_next_cell(dt_root_size_cells, ®); early_init_dt_add_memory_arch(base, size); //在這裡通過一系列的處理後加入到memblock中 return 0; } void __init __weak early_init_dt_add_memory_arch(u64 base, u64 size) { memblock_add(base, size); }
2、記憶體的管理方式:
在start_kernel ->…-> create_mapping中建立頁表後,核心就可以對實體記憶體進行管理了,管理方式如下:
a、記憶體被劃分為很多node的節點,通過pg_data_t型別的結構體來管理;
b、每一個node又劃分為不同簇即zone來管理,一般劃分為ZONE_DMA/ZONE_NOMAL/ZONE_HIGHEM ;
通過struct zone_struct的結構體來描敘;
c、頁面(page)是頁面分配的基本單位,記憶體中的每個頁都會建立一個struct page來管理。
其中zone的型別在如下檔案中定義:
enum zone_type {
ZONE_DMA, //0~15MB,此部分簇的記憶體用來執行DMA操作
ZONE_NORMAL, //16MB~895MB,這個區包含的都是能正常對映的頁
ZONE_HIGHMEM, //896MB~實體記憶體結束,這部分在“高階記憶體”中使用到,64bit的的kernel一般無此zone
}
zone的初始化在start_kernel( ) -> free_area_init_core中在free_area_init_core函式中會依次建立每一個zone,另外在start_kernel->build_zonelists_node的中會初始化zonelist的結構體夥伴系統會從zonelist中開始分配記憶體。
3、使用者空間和核心空間劃分
linux的使用者核心空間劃分,對於ARM32bit和ARM64bit的核心存在一定的差異,因為32bit的linux的能使用的虛擬地址的空間為4G,當實體記憶體大於4G時即無法訪問,故存在高階記憶體的概率(後續詳細講解)。但ARM64bit的結構採用48位物理定址機制,最大可以訪問256TB的實體地址空間,對於目前的應用來說,完全已經足夠,不需要擴充套件到64位的物理定址。虛擬地址同樣最大支援48位定址,所以把虛擬地址空間劃分為兩個空間,每個空間最大支援256TB。
(1)32bit 核心空間劃分:
32bit的核心使用者空間和核心空間劃分通常按照3:1劃分,當然也支援2:2來劃分,由以下程式碼控制,預設會選擇VMSPLIT_3G,即使用者空間大小為3G,核心空間的大小為1G。PAGE_OFFSET用於計算實體地址無虛擬地址的偏移量。
kernel-3.18/arch/arm/Kconfig
choice
prompt "Memory split"
depends on MMU
default VMSPLIT_3G
config VMSPLIT_3G
bool "3G/1G user/kernel split"
config VMSPLIT_2G
bool "2G/2G user/kernel split"
config VMSPLIT_1G
bool "1G/3G user/kernel split"
endchoice
config PAGE_OFFSET
hex
default PHYS_OFFSET if !MMU
default 0x40000000 if VMSPLIT_1G
default 0x80000000 if VMSPLIT_2G
default 0xC0000000
下面看ARM32核心的分佈圖,在linux核心啟動的會打印出對應的核心空間佈局圖如下:
[ 0.000000] <0>-(0)[0:swapper]Virtual kernel memory layout:
[ 0.000000] <0> vector : 0xffff0000 - 0xffff1000 ( 4 kB)
[ 0.000000] <0> fixmap : 0xffc00000 - 0xfff00000 (3072 kB)
[ 0.000000] <0> vmalloc : 0xe1000000 - 0xff800000 ( 488 MB)
[ 0.000000] <0> lowmem : 0xc0000000 - 0xe0800000 ( 520 MB)
[ 0.000000] <0> pkmap : 0xbfe00000 - 0xc0000000 ( 2 MB)
[ 0.000000] <0> modules : 0xbf000000 - 0xbfe00000 ( 14 MB)
[ 0.000000] <0> .text : 0xc0008000 - 0xc0e00000 (14304 kB)
[ 0.000000] <0> .init : 0xc1200000 - 0xc1400000 (2048 kB)
[ 0.000000] <0> .data : 0xc1400000 - 0xc1526154 (1177 kB)
[ 0.000000] <0> .bss : 0xc1528000 - 0xc1867984 (3327 kB)
其中核心編譯出來的Bootimage本身佔據了記憶體空間的_text段到 _end段,分為如下幾段,在核心編譯出來的System.map檔案中可以查詢到:
- 程式碼段:_text和_etext為程式碼段的起始地址和結束地址,包含了核心編譯成的核心程式碼;
- init段:__init_begin和__init_end中間的部分,包含了模組初始化的資料;
-資料段:_sdata和_edata之間的部分,包含核心的變數;
-BSS段:__bss_start和__bss_stop之間的部分,包含初始化為0的所有靜態全域性變數;
使用者空間和核心空間使用3:1的方式劃分,核心空間1GB的空間部分空間可以直接訪問實體地址,稱為線性對映區,實體地址的[0:760M]的記憶體被線性對映到[3GB:3GB+760M]的虛擬地址上。線性對映區的實體地址與虛擬地址的相差PAGE_OFFSET(3GB/0xc0000000)的大小,可以通過如下函式實現轉換:
//kernel-3.18/arch/arm/include/asm/memory.h
static inline phys_addr_t __virt_to_phys(unsigned long x)
{
return (phys_addr_t)x - PAGE_OFFSET + PHYS_OFFSET;
}
static inline unsigned long __phys_to_virt(phys_addr_t x)
{
return x - PHYS_OFFSET + PAGE_OFFSET;
}
核心把實體記憶體低於760M的稱為線性對映區(Normal memory),高於760M的實體記憶體稱為高階記憶體(High memory),由於32位系統的定址能力只有4GB,對於實體記憶體760M~4GB的情況,保留240MB的虛擬地址空間劃分用於動態對映高階記憶體,這樣核心空間就可以訪問到全部的4GB的實體記憶體了。對於實體記憶體高於4GB的請,可以使用LPE機制來拓展實體記憶體訪問。其中記憶體佈局圖如下:
(2)64bit 核心空間劃分:
ARM64架構的核心採用48位物理定址,最大可以訪問256TB的實體地址空間,通常吧虛擬地址空間劃分為兩個空間,每個空間最大支援256TB,劃分如下:
-使用者空間:0x0000 0000 0000 0000 - 0x0000 ffff ffff ffff 256TB
-核心空間:0xffff 0000 0000 0000 - 0xffff ffff ffff ffff 256TB
64位的核心沒有高階記憶體的概念,器記憶體分佈圖如下:
三、實體記憶體初始化
核心分配記憶體的方式大部分都是通過或者間接的通過buddy系統來管理,使用者在在申請記憶體時,夥伴系統會分配一塊合適的記憶體塊(2^order page)給使用者,在使用者釋放時回收。order的最大值為MAX_ORDER,即11,故每個記憶體塊連結串列包含2/4/8/16…1024個連續的頁面。實體記憶體分為不同的zone來管理,不同的zone都通過如下結構體來管理:
struct zone {
/* zone watermarks, access with *_wmark_pages(zone) macros */
unsigned long watermark[NR_WMARK];//zone記憶體水位相關
/* free areas of different sizes */
struct free_area free_area[MAX_ORDER]; //空閒頁面
unsigned long *pageblock_flags; //頁面型別
......
}
struct free_area {
struct list_head free_list[MIGRATE_TYPES];
unsigned long nr_free;
};
上面可以看出zone的結構體中中有free_area 的陣列,陣列的大小是MAX_ORDER,free_area的資料結構中有MIGRATE_TYPES個連結串列,其關係如下圖:
MIGRATE_TYPES的的型別也在mmzone.h的檔案中定義:
enum {
MIGRATE_UNMOVABLE,
MIGRATE_MOVABLE,
MIGRATE_RECLAIMABLE,
MIGRATE_PCPTYPES, /* the number of types on the pcp lists */
MIGRATE_HIGHATOMIC = MIGRATE_PCPTYPES,
MIGRATE_TYPES
};
在核心初始化的時候,使用的是memblock的方式來管理記憶體的,當核心準備好的時候,會遍歷所有的memblock的記憶體塊,找到記憶體塊的起始地址和結束地址以2^order的方式加入的夥伴系統中。
start_kernel -> mm_init -> mem_init-> free_all_bootmem -> free_low_memory_core_early
static unsigned long __init free_low_memory_core_early(void)
{
unsigned long count = 0;
phys_addr_t start, end;
memblock_clear_hotplug(0, -1);
//遍歷所有memblock的起始地址和結束地址
for_each_free_mem_range(i, NUMA_NO_NODE, MEMBLOCK_NONE, &start, &end,NULL)
//開始釋放記憶體到夥伴系統中
count += __free_memory_core(start, end);
return count;
}
__free_memory_core經過一系列處理後會呼叫到夥伴系統的核心函式__free_pages來釋放記憶體:
__free_memory_core -> __free_pages_memory -> __free_pages_bootmem -> __free_pages_boot_core -> __free_pages
static void __init __free_pages_boot_core(struct page *page,
unsigned long pfn, unsigned int order)
{
unsigned int nr_pages = 1 << order;
struct page *p = page;
unsigned int loop;
prefetchw(p);
for (loop = 0; loop < (nr_pages - 1); loop++, p++) {
prefetchw(p + 1);
__ClearPageReserved(p);
set_page_count(p, 0);
}
__ClearPageReserved(p);
set_page_count(p, 0);
page_zone(page)->managed_pages += nr_pages;
set_page_refcounted(page);
__free_pages(page, order);
}
作者:frank_zyp
您的支援是對博主最大的鼓勵,感謝您的認真閱讀。
本文無所謂版權,歡迎轉載。