ARM32位系統的記憶體佈局圖
32位作業系統的記憶體佈局很經典,很多書籍都是以32位系統為例子去講解的。32位的系統可訪問的地址空間為4GB,使用者空間為1GB ~ 3GB,核心空間為3GB ~ 4GB。
為什麼要劃分為使用者空間和核心空間呢?
一般處理器會把執行模式分為好幾個,比如x86分為rang0 ~ rang3級別。ARMv7架構中,又分為好幾個模式,比如svc模式是給核心用的,usr模式是給使用者態使用的。
當一個程序執行系統呼叫時,會陷入到核心態中,這個時候執行模式就從usr模式轉換為svc模式,這就是我們常說的核心態。處於核心態的程序是可以訪問核心空間的。所以就根據CPU的執行模式劃分了兩個空間。
我們先看下1GB的核心空間是怎麼劃分的,32位的系統中,通常配置的實體記憶體通常是大於1GB的,所以實體記憶體會劃分為兩部分,低端記憶體稱為線性對映區,高階記憶體稱為高階對映區。那這個分界線是怎麼計算的呢,在ARM32中,分界線為760M。低端記憶體會做一比一對映到3GB ~ 3GB+760M。
這裡講的線性對映就是直接把實體記憶體的地址對映到線性對映區中,假設實體記憶體的DDR起始地址是0,對映的時候就有一個偏移量,這個偏移量就是0XC0000000,page offset。線性對映的地址我們就可以很方便的完成虛擬地址到實體地址的轉換,只需要加減一個offset就可以。
高階記憶體的對映就沒有線性對映那麼簡單了,使用高階記憶體時需要完成動態對映。
我們先看下1GB的核心空間剩下都做什麼使用了。
-
vmalloc區域:分配的記憶體在虛擬地址是連續的,物理頁面可以是離散的。vmalloc大概佔用了200M實體記憶體。
-
fixmap:Fix map中的fix指的是固定的意思,那麼固定什麼東西呢?其實就是虛擬地址是固定的,也就是說,有些虛擬地址在編譯(compile-time)的時候就固定下來了,而這些虛擬地址對應的實體地址不是固定的,是在kernel啟動過程中被確定的。
-
vector:vector區域用於對映CPU vector page,大小一頁4KB,從0xffff0000 - 0xffff1000。
接下來看下3GB使用者空間的劃分方式,一個程序要執行起來,必然要有自己的程式碼段和資料段,這部分在載入的時候就會被對映到虛擬地址。
-
堆空間:從程序的開始到1GB的這部分我們稱為堆空間,這部分主要是給malloc使用的。
-
mmap空間:1GB到3GB這部分是給mmap空間使用的,mmap可以用來對映檔案也可以對映匿名頁面。通常使用者態分配大段記憶體的時候,Linux通常會使用mmap來完成分配。
從程序的角度看記憶體佈局
readelf 檢視程式段
接下來,我們透過一個C語言程式學習下記憶體佈局,這個例子很簡單,用malloc函式分配了記憶體記憶體,然後使用memset將該區域清零。
使用gcc編譯為elf後,可以使用readelf 檢視該程式包含那些段。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define SIZE (100 * 1024)
void main()
{
char* buf = malloc(SIZE);
memset(buf, 0x58, SIZE);
while(1)
sleep(10000);
}
gcc -static memory_process.c -o memory_process.elf
我們知道,通常Linux中流行的可執行檔案的格式就是elf。使用gcc編譯的elf就是我們講的elf檔案,目標檔案除了包含了編譯後的機器指令程式碼,還包含其他連結資訊,比如符號表,除錯資訊,字串等,通常這些資訊會根據不同的屬性存放在不同的段(section)中,這裡我們只關注常見的段 。
-
.init:程式初始化的程式碼段。
-
.text:程式碼段,程式編譯完後的機器指令。
-
.data:初始化過的全域性的靜態變數,還有一些區域性的靜態變數。
-
.rodata:只讀變數,字串,常量等。
-
.bss:未初始化的全域性變數以及初始化為零的變數。
readelf 檢視程式頭
使用-l引數讀下程式頭(program header),它是用來描述OS是如何被對映到程序的虛擬地址空間的。
之前我們看到的30個段,在這裡分成了7個族,並且顯示每個族都包含那些段,這裡我們只關注叫load的族,其他族主要是在程式裝載的時候起到輔助作用。
第一個族裡麵包含init,text段,他的執行許可權是隻讀,可執行的(RE)。起始地址0x0000000000400000
,大小是0x00000000000b5986
。
另外一個族主要包含data和bss段,他的執行許可權是可讀寫(RW)。起始地址0x00000000006b6120
,大小是0x00000000000051b8
。
程序對映的過程
-
地址:本段在虛擬記憶體中的地址範圍;對應
vm_area_struct
中的vm_start
和vm_end
。 -
許可權:本段的許可權; r-讀,w-寫,x-執行, p-私有;對應vm_flags。
-
偏移地址:即本段對映地址在檔案中的偏移;對於有名對映指本段對映地址在檔案中的偏移,對應
vm_pgoff
;對於匿名對映為vm_area_struct->vm_start
。 -
主裝置號與次裝置號:所對映的檔案所屬裝置的裝置號,對應
vm_file->f_dentry->d_inode->i_sb->s_dev
。匿名對映為0。其中fd為主裝置號,00為次裝置號。 -
檔案索引節點號:對應
vm_file->f_dentry->d_inode->i_ino
,與ls –i顯示的內容相符。匿名對映為0。 -
對映的檔名:對有名對映而言,是對映的檔名,對匿名對映來說,是此段記憶體在程序中的作用。[stack]表示本段記憶體作為棧來使用,[heap]作為堆來使用,其他情況則為無。
smaps 可以檢視更多的內容
➜ example cat /proc/5823/smaps
00400000-004b6000 r-xp 00000000 08:01 2319863 /home/zhongyi/code/example/memory_process.elf
Size: 728 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 640 kB
Pss: 640 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 640 kB
Private_Dirty: 0 kB
Referenced: 640 kB
Anonymous: 0 kB
LazyFree: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
FilePmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 0 kB
THPeligible: 0
VmFlags: rd ex mr mw me dw sd
006b6000-006bc000 rw-p 000b6000 08:01 2319863 /home/zhongyi/code/example/memory_process.elf
Size: 24 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 24 kB
Pss: 24 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 8 kB
Private_Dirty: 16 kB
Referenced: 24 kB
Anonymous: 16 kB
LazyFree: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
FilePmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 0 kB
THPeligible: 0
VmFlags: rd wr mr mw me dw ac sd
006bc000-006bd000 rw-p 00000000 00:00 0
Size: 4 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 4 kB
Pss: 4 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 4 kB
Referenced: 4 kB
Anonymous: 4 kB
LazyFree: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
FilePmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 0 kB
THPeligible: 0
VmFlags: rd wr mr mw me ac sd
010cc000-010ef000 rw-p 00000000 00:00 0 [heap]
Size: 140 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 108 kB
Pss: 108 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 108 kB
Referenced: 108 kB
Anonymous: 108 kB
LazyFree: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
FilePmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 0 kB
THPeligible: 0
VmFlags: rd wr mr mw me ac sd
7ffd5e0db000-7ffd5e0fc000 rw-p 00000000 00:00 0 [stack]
Size: 132 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 16 kB
Pss: 16 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 16 kB
Referenced: 16 kB
Anonymous: 16 kB
LazyFree: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
FilePmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 0 kB
THPeligible: 0
VmFlags: rd wr mr mw me gd ac
7ffd5e100000-7ffd5e103000 r--p 00000000 00:00 0 [vvar]
Size: 12 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 0 kB
Pss: 0 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
Referenced: 0 kB
Anonymous: 0 kB
LazyFree: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
FilePmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 0 kB
THPeligible: 0
VmFlags: rd mr pf io de dd sd
7ffd5e103000-7ffd5e105000 r-xp 00000000 00:00 0 [vdso]
Size: 8 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 4 kB
Pss: 0 kB
Shared_Clean: 4 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
Referenced: 4 kB
Anonymous: 0 kB
LazyFree: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
FilePmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 0 kB
THPeligible: 0
VmFlags: rd ex mr mw me de sd
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
Size: 4 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 0 kB
Pss: 0 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
Referenced: 0 kB
Anonymous: 0 kB
LazyFree: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
FilePmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 0 kB
THPeligible: 0
VmFlags: ex
堆裡面,匿名頁面分配了108個實體記憶體,但我們的測試程式只分配了100k實體記憶體,這裡匿名頁面比分配的要大,這是因為程序在裝載的時候也要消耗一些匿名頁面。
010cc000-010ef000 rw-p 00000000 00:00 0 [heap]
Size: 140 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 108 kB
Pss: 108 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 108 kB
Referenced: 108 kB
Anonymous: 108 kB
LazyFree: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
FilePmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 0 kB
THPeligible: 0
VmFlags: rd wr mr mw me ac sd
根據以上資訊,可以繪製出測試程式記憶體的佈局圖。
測試程式程序的elf這裡只列出了常用的段。程式碼段的VMA屬於page cache對映,這裡把init段,text段,rodata段分為一個族,因為他們具有相同的許可權,在程序載入的時候,會對映到程式碼段的VMA中。
資料段的VMA屬於匿名對映,bss,data段具有相同的許可權,在OS載入時,會對映到資料段的VMA中。
從資料段開始的地方就屬於堆空間,我們在程式中用malloc分配了100K空間,這100K大小,也是在堆空間有對應的位置存在。
另外就是棧的VMA,程序有屬於自己的VMA的棧。
以上就介紹了程序的ELF如何和程序的地址空間對映起來的。
64位系統的佈局圖
64位系統可以訪問的空間就變得很大了。不過是ARM還是X86,實際的實體地址都不會用到64根地址線,通常是使用了48根地址線。而且,劃分的使用者空間和核心空間都是非常大的。
大家可以看這張圖,把空間分為了三部分,一部分是核心空間,一部分是非規範區域(大家都不使用的),最後是使用者空間。
-
使用者空間:0x0000_0000_0000_0000到0x0000_ffff_ffff_ffff,一共有256TB。
-
非規範區域
-
核心空間:0xffff_0000_0000_0000到0xffff_ffff_ffff_ffff。一共有256TB。
核心空間又做了如下細分:
-
vmalloc區域:vmalloc函式使用的虛擬地址空間,kernel image也在vmalloc區域,核心映象的起始地址 = KIMAGE_ADDR + TEXT_OFFSET, TEXT_OFFSET是記憶體中的核心映象相對記憶體起始位置的偏移。
-
vmemmap區域:記憶體的實體地址如果不連續的話,就會存在記憶體空洞(稀疏記憶體),vmemmap就用來存放稀疏記憶體的page結構體的資料的虛擬地址空間。
-
PCI I/O區域:pci裝置的I/O地址空間
-
Modules區域:核心模組使用的虛擬地址空間
-
normal memory線性對映區:範圍是【0xffff_8000_0000_0000, 0xffff_ffff_ffff_ffff】, 一共有128TB, 但這裡程式碼對應的是
memblock_start_of_DRAM()
和memblock_end_of_DRAM()
函式。
memory根據實際實體記憶體大小做了限制,所以memroy顯示了實際能夠訪問的記憶體區。MLM(__phys_to_virt(memblock_start_of_DRAM()), (unsigned long)high_memory)) high_memory = __va(memblock_end_of_DRAM() - 1) + 1;
最終是透過dts或acpi中配置的memory節點確定的。