【內存管理】內存佈局介紹
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 節點確定的。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/ENysyY_YWv7Nf15H2J93aA