上一節內容的學習我們知道了CPU是如何訪問內存的,CPU拿到內存後就可以向其它人(kernel的其它模塊、內核線程、用戶空間進程、等等)提供服務,主要包括:
-
以虛擬地址(VA)的形式,爲應用程序提供遠大于物理內存的虛擬地址空間(Virtual Address Space)
-
每個進程都有獨立的虛擬地址空間,不會相互影響,進而可提供非常好的內存保護(memory protection)
-
提供內存映射(Memory Mapping)機制,以便把物理內存、I/O空間、Kernel Image、文件等對象映射到相應進程的地址空間中,方便進程的訪問
-
提供公平、高效的物理內存分配(Physical Memory Allocation)算法
-
提供進程間內存共享的方法(以虛擬內存的形式),也稱作Shared Virtual Memory
在提供這些服務之前需要對內存進行合理的劃分和管理,下面讓我們看下是如何劃分的。
物理地址空間布局
Linux系統在初始化時,會根據實際的物理內存的大小,爲每個物理頁面創建一個page對象,所有的page對象構成一個mem_map數組。進一步,針對不同的用途,Linux內核將所有的物理頁面劃分到3類內存管理區中,如圖,分別爲ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM。
-
ZONE_DMA 的範圍是 0~16M,該區域的物理頁面專門供 I/O 設備的 DMA 使用。之所以需要單獨管理 DMA 的物理頁面,是因爲 DMA 使用物理地址訪問內存,不經過 MMU,並且需要連續的緩沖區,所以爲了能夠提供物理上連續的緩沖區,必須從物理地址空間專門劃分一段區域用于 DMA。
-
ZONE_NORMAL 的範圍是 16M~896M,該區域的物理頁面是內核能夠直接使用的。
-
ZONE_HIGHMEM 的範圍是 896M~結束,該區域即爲高端內存,內核不能直接使用。
Linux內核空間虛擬地址分布
在 Kernel Image 下面有 16M 的內核空間用于 DMA 操作。位于內核空間高端的 128M 地址主要由3部分組成,分別爲 vmalloc area、持久化內核映射區、臨時內核映射區。
由于 ZONE_NORMAL 和內核線性空間存在直接映射關系,所以內核會將頻繁使用的數據如 Kernel 代碼、GDT、IDT、PGD、mem_map 數組等放在 ZONE_NORMAL 裏。而將用戶數據、頁表(PT)等不常用數據放在 ZONE_HIGHMEM 裏,只在要訪問這些數據時才建立映射關系(kmap())。比如,當內核要訪問 I/O 設備存儲空間時,就使用 ioremap 將位于物理地址高端的 mmio 區內存映射到內核空間的 vmalloc area 中,在使用完之後便斷開映射關系。
Linux用戶空間虛擬地址分布
用戶進程的代碼區一般從虛擬地址空間的 0x08048000 開始,這是爲了便于檢查空指針。代碼區之上便是數據區,未初始化數據區,堆區,棧區,以及參數、全局環境變量。
Linux物理地址和虛擬地址的關系
Linux 將 4G 的線性地址空間分爲2部分,0~3G 爲 user space,3G~4G 爲 kernel space。
由于開啓了分頁機制,內核想要訪問物理地址空間的話,必須先建立映射關系,然後通過虛擬地址來訪問。爲了能夠訪問所有的物理地址空間,就要將全部物理地址空間映射到 1G 的內核線性空間中,這顯然不可能。于是,內核將 0~896M 的物理地址空間一對一映射到自己的線性地址空間中,這樣它便可以隨時訪問 ZONE_DMA 和 ZONE_NORMAL 裏的物理頁面;此時內核剩下的 128M 線性地址空間不足以完全映射所有的 ZONE_HIGHMEM,Linux 采取了動態映射的方法,即按需的將 ZONE_HIGHMEM 裏的物理頁面映射到 kernel space 的最後 128M 線性地址空間裏,使用完之後釋放映射關系,以供其它物理頁面映射。雖然這樣存在效率的問題,但是內核畢竟可以正常的訪問所有的物理地址空間了。
到這裏我們應該知道了 Linux 是如何用虛擬地址來映射物理地址的,最後我們用一張圖來總結一下: