Peter Liu:CPU是如何訪問記憶體的?
記憶體管理
記憶體是計算機中重要部件之一,是與CPU進行溝通的橋樑。用於暫存CPU中的運算資料、以及與硬碟等外部儲存器交換的資料。
利用和管理計算機記憶體資源。
記憶體管理演變史
早期,程式直接執行在物理記憶體上,直接操作物理記憶體,這種方式存在幾個問題:
地址空間不隔離:程式操作相同地址空間會造成互相影響甚至崩潰,而且安全性也得不到保證;
使用效率低:沒有特別好的策略保證多個程序對超過物理記憶體大小的記憶體需求的滿足;
程式執行地址不確定:程式執行時,都需要分配空閒區域,而空閒位置不確定,會帶來一些重定位問題;
記憶體管理主要就是想辦法解決上面三個問題;
虛擬記憶體
計算機系統裡任何問題都可以靠引入一箇中間層來解決
,記憶體管理就在程式和物理記憶體之間引入了
虛擬記憶體
的概念;對程序地址和物理地址進行隔離;
記憶體分割槽(ZONE)
Linux 對記憶體節點進行分割槽;將節點分為DMA、Normal、High Memory 記憶體區;
DMA記憶體區:直接記憶體訪問區,通常為物理記憶體的起始16M;主要供I/O外設使用,無需CPU參與的外設和記憶體DMA;
Normal記憶體區:從16M到896M記憶體區;核心可以直接使用
Hight Memory記憶體區:896M以後的記憶體區;高階記憶體,核心不能直接使用
核心空間和使用者空間
Linux 作業系統,將虛擬記憶體劃分為
核心空間
和
使用者空間
;
使用者程序只能訪問使用者空間的虛擬地址,只有透過系統呼叫、外設中斷或異常才能訪問核心空間;
核心空間:
Linux核心空間 1G容量,包括:核心映象、物理頁面表、驅動程式等;
直接對映區:最大896M
高階記憶體線性地址空間:共128MB
動態記憶體對映區(vmalloc region):由核心函式vmalloc 分配;
永久記憶體對映區:alloc_page、 kmap
固定對映區:特定用途,如 ACPI_BASE 等
使用者空間:分為5個不同記憶體區域:
程式碼段:只讀,存放可執行檔案的操作指令;映象;
資料段:存放可執行檔案中已初始化全域性變數;存放靜態變數和全域性變數;
BSS段:未初始化全域性變數
堆:存放被動態分配的記憶體段;
棧:存放臨時建立的區域性變數;
記憶體地址對映
CPU生成的地址是邏輯地址,而記憶體單元中的地址為物理地址;
執行時地址繫結方案會生成不同的邏輯地址和物理地址,這時,邏輯地址通常被稱為虛擬地址
虛擬地址空間
物理地址空間是有限的,虛擬地址空間可以是任意大小;
程式可以透過操作虛擬地址,把虛擬地址空間對映到物理地址空間; Linux透過缺頁中斷和swap機制,實現虛擬地址對映;
虛擬地址優點:
避免直接訪問物理記憶體地址,保護作業系統
每個程序都被分配4GB虛擬記憶體;可使用比實際物理記憶體更大的地址空間。
分頁和分段
虛擬地址和物理地址,主要透過
分段
和
分頁
技術,進行對映;
程式地址:段號+頁號+頁內偏移;
段和頁的區別:
段是資訊的邏輯單位,根據使用者的需要劃分,段對使用者是可見的; 頁時資訊的物理單位,為管理記憶體方便和劃分的,對使用者透明的。
段的大小不固定,根據功能覺得;頁的大小固定,由系統覺得;
段向用戶提供二維地址空間;頁向用戶提供一維地址空間;
段便於儲存保護和資訊共享;頁的保護和共享受到限制;
分段和分頁:分頁的粒度更小;
分段:將程式分為程式碼段、資料段、堆疊段等; 分頁:將段分成均勻的小塊,透過頁表對映物理記憶體;
GDT:全域性描述表,也就是段表,提供段氏儲存機制;儲存分段基址資訊; CPU訪問使用邏輯地址; 分段機制,將邏輯地址轉換成線性地址,也就是分頁系統中的虛擬地址; 分頁機制,將虛擬地址轉換為物理地址;
分頁儲存和分段儲存
分段(Segmentation)
分段地址透過段表,轉換成線性地址; 分段地址包括段號和段內地址; 段表,包括短號、段長、基址;
分頁(Page)
分頁機制就是將虛擬地址空間分為大小相等的頁;物理地址空間也分為若干個物理塊(頁框);頁和頁框大小相等。實現離散分配; 分頁機制的實現需要 MMU 硬體實現;負責分頁地址轉換;
頁大小(粒度)太大浪費;太小,影響分配效率
線性地址透過頁錶轉換成物理地址; 分頁地址包括頁號P和位偏移量W; 頁表,包括頁號、物理塊號、存取控制; 頁表作用就是實現頁號到物理塊號的地址對映;
多級頁表
記憶體分配
使用者程序通常只能訪問使用者空間的虛擬地址,不能訪問核心空間的虛擬地址;
記憶體管理問題: 記憶體碎片太小和管理記憶體碎片的效率問題
記憶體碎片
記憶體碎片:回收記憶體時,將記憶體塊放入空閒連結串列中; 因記憶體越分越小,記憶體塊小而多;當需要一塊大記憶體時,儘管此時空閒記憶體綜合可能滿足需求,但過於零散,沒有一個合適的記憶體塊
記憶體碎片產生原因:分配記憶體時,不能將相鄰記憶體合併;
解決記憶體碎片的方法:
小記憶體單獨分配(記憶體池)、大記憶體由作業系統分配
夥伴系統演算法
slab 演算法
如何避免記憶體碎片:
少用動態記憶體分配函式(儘量使用棧空間)
分配記憶體和釋放的記憶體儘量在同一個函式中
儘量一次性申請較大的記憶體,而不要反覆申請小記憶體
儘可能申請大塊的2的指數冪大小的記憶體空間
外部碎片避免:夥伴系統(Buddy)演算法
內部碎片避免:slab演算法
自己進行記憶體管理工作,設計記憶體池
外部碎片
外部碎片,是指還沒有被分配出去,但由於太小無法分配的記憶體空閒區域;
夥伴系統演算法(Buddy System)
為核心提供了一種用於分配一組連續的頁而建立的一種高效的分配策略,並有效解決了外碎片問題; 分配的記憶體區以頁框為基本單位;
Linux 核心使用夥伴系統演算法,把所有的空閒頁框分組為11個塊連結串列,每個塊連結串列分別包含大小為1,2,4,8,16,32,64,128,256,512和1024個連續頁框的頁框塊。最大可申請1024個連續頁框,對應4MB大小的連續記憶體。每個頁框塊的第一個頁框的物理地址是該塊大小的整數倍;
SLAB分配器
夥伴系統是以頁為單位管理和分配記憶體。但顯式需求卻以位元組為單位,使用夥伴系統就會嚴重浪費記憶體(產生內部碎片)。
slab分配器專為小記憶體分配而生(解決內部碎片); slab分配器分配記憶體以Byte為單位; 基於夥伴系統分配的大記憶體,進一步細分成小記憶體分配;
快取記憶體/TLB控制
slab 快取記憶體分為兩大類:普通快取記憶體和專用快取記憶體;
記憶體分配函式
https://www。
jianshu。com/p/e42f4977f
b7e
kmalloc/ vmalloc / malloc
malloc :負責分配使用者空間記憶體。標準c庫提供了 malloc/free 函式分配釋放記憶體,底層由 sark、brk、mmap、munmap系統呼叫實現。
kmalloc:負責分配核心空間記憶體。用於申請較小、連續的物理記憶體。以位元組為單位進行分配, slab;
vmalloc:負責分配核心空間記憶體。用於申請較大的記憶體空間,虛擬記憶體是連續的,以位元組為單位進行分配;分配速度要慢;
常用函式總結
使用者空間: malloc/calloc/realloc/free alloca mmap/munmap brk/sbrk
核心空間: vmalloc/vfree
核心空間slab: kmalloc/kcalloc/krealloc/kfree kmem_cache_create
核心空間buddy:
get_free_page/
get_free_pages alloc_page/alloc_pages/free_pages
頁面置換演算法
當發生缺頁中斷時,作業系統必須在記憶體中選擇一個頁面將其換出,以便為即將調入的頁面騰出空間;
常見置換演算法有以下四種:
最佳置換演算法(OPT)(不可能實現)
淘汰以後永不使用或最長時間內不再被訪問的頁面;保證獲得最低的缺頁率。 但作業系統無法知道各個頁面下一次將在什麼時候被訪問,因此該演算法是無法被實現的;
先進先出(FIFO)置換演算法
優先淘汰最早進入記憶體的頁面;實現簡單,但效能差;
Belady異常:FIFO演算法會產生當所分配的物理塊數增大而頁故障數不減反增的異常現象;
最近最少使用(LRU)置換演算法
置換未使用時間最長的頁面; LRU是堆疊類演算法,效能較好,但需要暫存器和棧的硬體支援;實現起來困難,且開銷大;
時鐘(CLOCK)置換演算法
環形連結串列,也叫NRU演算法
tcmalloc / jemalloc