本文完全參考
- 虛擬內存
- 內存分段
- 內存分頁
- 段頁式內存管理
- Linux內存管理
一、虛擬內存
1. 單片機的絕對物理地址
以單片機作為引子,它沒有操作系統,每次寫完程序是借助工具將程序燒錄進單片機,程序才能運行。
單片機由于沒有操作系統,其CPU之間操作內存的物理地址。
這種設計,要在內存中同時運行兩個程序是不可能的。比如程序一在2000位置寫入一個值,將會擦除掉程序二存放在當前位置的所有內容,兩個程序都會崩潰。
單片機的問題:兩個程序都直接操作絕對物理內存。
操作系統如何解決這個問題呢?【虛擬內存】
為了避免不同的進程共用一段相同的絕對物理地址,可以把進程所使用的地址「隔離」開來,即讓操作系統為每個進程分配獨立的一套「虛擬內存」,不同進程使用的內存彼此獨立、互不干涉,前提是每個進程都不能訪問物理內存。
兩種內存地址:
-
虛擬內存地址(Virtual Memory Address):程序所使用的內存地址;
-
物理內存地址(Physical Memory Address):實際存在硬件里面的空間地址;
操作系統提供一種映射機制,將不同進程的虛擬地址與不同的內存物理地址映射起來,當程序訪問虛擬地址時,操作系統轉換成不同的物理地址,這樣不同進程運行時,寫入的是不同的物理地址,這樣就避免了沖突,實現了進程內存隔離。
這個映射機制由CPU中的內存管理單元(MMU)實現,進程持有的虛擬內存可以通過MMU轉變成物理地址,然后通過物理地址訪問內存,如圖:
操作系統具體是如何管理虛擬內存和物理地址的關系?
- 內存分段;
- 內存分頁;
兩種方法,下面逐一介紹:
二、內存分段
1. 程序分段機制
程序是由若干個邏輯分段(Segmentation)組成的,不同段有不同屬性,包括:代碼分段、數據分段、棧分段、堆分段等。
2. 分段機制下,虛擬地址和物理地址如何映射?
分段機制下的虛擬地址由兩部分組成,段選擇因子和段內偏移量。
-
段選擇因子:
- 位置:保存在段寄存器中
- 段號:段選擇因子中最重要的是段號,用作段表索引
- 段表: 段表保存著這個段的基地址、段的界限和特權等級等。
-
段內偏移量:
- 范圍:段內偏移量位于0和段界限之間;
- 規則:如果段內偏移量合法,就將段基地址+段內偏移量=物理內存地址。
即虛擬地址是通過段表與物理地址進行映射,分段機制把程序的虛擬地址分為4個段,每個段在段表中有一個項,在這一項找到段的基地址,再加上偏移量,就能找到物理內存中的地址。
比如要訪問段3中偏移量為500的虛擬地址,可以計算出目標物理地址 = 段3基地址7000 + 偏移量 500 = 7500.
分段內存解決了程序本身不需要關心物理內存地址的問題,但是問題在于:
- 內存碎片;
- 內存交互效率低;
3. 內存碎片問題
3.1 內存碎片產生原因
如圖,由于分段內存機制,真實物理內存中分出了三片內存用于不同的進程,比如游戲、音樂、瀏覽器。當我們關閉中間的瀏覽器內存時,就產生了內存碎片。真實物理內存中有200MB以上的內存空閑,但是由于內存分段機制與內存碎片問題,無法打開200MB的進程!
3.2 內存碎片分類
內存分段帶來的內存碎片主要是外部內存碎片問題。
內存碎片分為內部內存碎片和外部內存碎片。
內存分段管理可以做到段實際需求分配內存,所以可以有多少需求就分配多少的段,不會出現內部內存碎片問題。
但是由于每個段的長度不固定,多個段未必恰好使用所有的內存空間,會產生多個不連續的小物理內存,導致新的程序無法被裝載,出現外部內存碎片問題。
解決外部內存碎片問題就是用內存交換
3.3 內存交換
可以將音樂程序占用的256MB內存暫時寫進磁盤,然后再從磁盤寫回內存,但是不是寫回去原來位置,而是緊緊跟在已占用的512MB內存后面,這樣就解決了外部內存碎片問題,騰出了256MB內存空間,于是新的200MB以上的程序就可以裝載進來了!
這個內存交換空間,在Linux系統中稱為swap空間,這塊空間是從硬盤劃分出來的用于內存與硬盤的空間交換。
3.4 內存分段的效率問題
對于多進程系統,用分段內存的方式映射虛擬內存和物理內存很容易產生外部內存碎片,那就不得不用swap內存區域進行內存交換,這個過程涉及到磁盤I/O,而磁盤速度相比內存慢太多,每次內存交換都需要把一大段連續內存數據寫入磁盤。
所以如果內存交換時,交換的是一個占用很大內存空間的程序,整個機器都會卡頓!
3.5 內存分段總結
外部內存碎片問題和內存交換效率極低問題,為了解決這兩個問題,我們用到內存分頁機制。
三、內存分頁
分段的好處就是能產生連續的內存空間,但是會出現「外部內存碎片和內存交換的空間太大」的問題。
有什么辦法能減少內存碎片問題?或者當需要內存交換的時候,讓需要swap的數據少一些,減少磁盤裝載數據,就可以解決內存分段的問題了。
內存分頁(Paging)把整個虛擬和物理內存切成一段段固定尺寸的大小,一個連續且尺寸固定的內存空間,我們稱為頁(Page),Linux下,每一個內存頁大小為4KB。
3.1 頁表
虛擬內存與物理內存之間通過頁表來映射:
頁表:
頁表是存儲在內存中的,內存管理單元(MMU)負責將虛擬內存地址轉換成物理地址。
缺頁異常處理:
當進程訪問的虛擬內存在頁表中查不到時,系統會產生一個缺頁異常,進入系統內核空間分配物理內存,更新進程頁表,最后返回用戶空間,恢復進程運行。
缺陷:內部內存碎片問題:
內存分頁后,頁與頁之間緊密排列,所以不會產生外部內存碎片,但是由于內存分頁機制的最小單位是一頁4KB,所以即便是程序需求不足一頁大小,我們最少也只能分配一頁,所以頁內可能會出現內存浪費,即內存分頁機制存在內部內存碎片問題。
優勢1:內存交換效率高:
如果內存空間不夠了,操作系統會把其他正在運行的進程中最近沒被使用的內存頁面給釋放掉,注意并不是完全關閉,而是Swap out(換出)到磁盤空間,一旦需要調用,再加載到內存空間,即Swap in (換入)。所以一次性寫入磁盤的也只有少數的一個頁或者幾個頁,不會花太多時間,內存交換效率高。
優勢2:不必一次性加載全部內存頁,分步加載需要的進程即可。
分頁內存的機制,使得我們不需要一次性把程序加載到物理內存,完全可以進行虛擬內存和物理內存頁之間的映射后,并不真的把全部虛擬內存頁加載到物理內存里,而是只有在程序運行中,需要用到對應虛擬內存頁里面的指令或數據時,再將其加載到物理內存中。
分頁機制下,虛擬地址和物理地址具體映射機制?
虛擬地址分為兩個部分,頁號和頁內偏移
- 頁號是頁表的索引,頁表包含物理頁每頁所在的物理地址的基地址,基地址與頁內偏移的組合形成了物理內存地址。
分頁內存機制的內存地址轉換,分三步:
- 把虛擬地址切分為頁號和偏移量
- 根據頁號,從頁表查詢對應的物理頁號
- 物理頁號+偏移量 = 物理內存地址
通過以上映射,虛擬內存中的頁通過頁表映射到了物理內存中的頁:
以上是簡單的分頁內存機制,一視同仁的全部分成等大的頁,有何缺陷?
空間缺陷:
因為操作系統是可以同時運行非常多的進程的,那這不就意味著頁表會非常的龐大。
32 位的環境下,虛擬地址空間共有 4GB,假設一個頁的大小是 4KB(2^12),那么就需要大約 100 萬個頁,每個頁表項就需要4字節大小來存儲,整個4GB空間映射就需要有4MB的內存來存儲頁表。
那么,100 個進程的話,就需要 400MB 的內存來存儲頁表,這是非常大的內存了,更別說 64 位的環境了。
3.2 多級頁表
單頁表的實現方式,在 32 位 和 頁大小 4KB 的環境下,一個進程的頁表需要裝下 100多萬個頁表項,并且每個頁表項是占用4字節大小的,于是相當于每個頁表需要占用4MB大小的空間。
把這100 多萬個頁表項的單級頁表進行再分頁,將頁表(一級頁表)分為1024個二級頁表,每個二級頁表中包含了1024個頁表項目,形成二級分頁。
“單級頁表 vs 二級頁表的內存占用對比”
論證多級頁表如何解決單級頁表的內存浪費問題,核心邏輯如下:
- 單級頁表的痛點:必須“全占滿”內存
頁表的職責是 覆蓋整個虛擬地址空間(否則虛擬地址無法轉物理地址,程序崩潰)。
以 4GB 虛擬地址空間、4KB 頁大小為例:
- 頁表項數量 = 4 G B 4 K B = 100 萬 + ( 即 2 20 項 ) 頁表項數量 = \frac{4GB}{4KB} = 100萬+(即2^{20}項) 頁表項數量=4KB4GB?=100萬+(即220項)
- 若每項占 4 字節,單級頁表總內存 = 100萬 ? 4B = 4MB
問題:即使程序只用了 20% 的虛擬地址,單級頁表仍需預先分配 4MB 內存存所有頁表項(包括未使用的 80%),造成巨大浪費。
- 二級頁表的優化:分層 + 按需創建
二級頁表將頁表拆為 “一級頁表(占位) + 二級頁表(按需建)”:
- 一級頁表:先建 1024 項(覆蓋整個 4GB 虛擬地址空間),每項存“二級頁表的位置索引指針”,僅占 1024 ? 4B = 4KB
- 二級頁表:僅當程序訪問某段虛擬地址時,才創建對應二級頁表(未訪問的虛擬地址,其二級頁表不建,節省內存)。
計算對比(假設僅 20% 一級頁表項被使用):
總內存 = 一級頁表(4KB) + 20% × 二級頁表總容量(4MB) ≈ 0.804MB,遠小于單級頁表的 4MB。
- 核心結論:分層讓“全覆蓋”和“省內存”共存
單級頁表必須為所有虛擬地址預先建頁表項,導致內存浪費;
二級頁表通過 “一級頁表先覆蓋全空間(滿足地址翻譯的必要性),二級頁表按需創建(避免未使用地址的內存開銷)” ,實現了“覆蓋全虛擬地址 + 節省內存”的平衡。
簡言之,多級頁表的本質是 用“分層占位 + 按需加載”的策略,解決單級頁表“全量預分配”的內存臃腫問題。
多級頁表概念
多級頁表是為解決單級頁表在大地址空間下的內存浪費問題而設計的分層地址轉換結構,核心是 “按需存儲頁表項”。
基本實現思路:
將虛擬地址轉換所需的頁表拆分成多個層級(比如二級、三級),虛擬地址被分成對應層級的索引段。只有實際被程序使用的頁表層級才會加載到內存,未使用的層級可以保存在外存(如硬盤),避免單級頁表 “不管用不用都占滿內存” 的問題。
舉例(二級頁表):
虛擬地址分為三部分:頁目錄索引→頁表索引→頁內偏移。
- 第一級(頁目錄):存儲指向第二級頁表的指針(僅記錄被使用的頁表位置);
- 第二級(頁表):存儲實際的物理頁地址;
- 轉換時,先通過頁目錄索引找到對應的頁表,再通過頁表索引找到物理頁,最后結合頁內偏移得到物理地址。
核心作用:
大幅減少頁表對內存的占用(尤其適合 64 位等大地址空間系統),同時保持地址轉換功能。代價是地址轉換時可能需要多次訪問內存(可通過 TLB 緩存緩解)。
對于64位系統,二級分頁也不夠,變成了四級分頁:
- 全局頁目錄項 PGD(Page Global Directory);
- 上層頁目錄項 PUD(Page Upper Directory);
- 中間頁目錄項 PMD(Page Middle Directory);
- 頁表項 PTE(Page Table Entry);
TLB
TLB(Translation Lookaside Buffer,地址轉換高速緩存)是 多級頁表的“性能補丁” ,專門解決多級頁表地址轉換時的速度瓶頸,核心邏輯如下:
-
為什么多級頁表需要TLB?
多級頁表通過“分層 + 按需創建”節省了內存,但地址轉換時 需要多次訪問內存(比如二級頁表要先查一級頁表,再查二級頁表),導致速度變慢。
TLB的作用是 “緩存常用的地址映射關系” ,避免每次都走多級頁表的繁瑣流程。 -
TLB是什么?
TLB是CPU內部的 高速緩存,存儲 最近使用的虛擬地址→物理地址的映射(即頁表項的副本) 。
可以理解為:把多級頁表里 常用的“虛擬頁→物理頁”翻譯結果 ,臨時存在CPU附近的“快捷欄”里。 -
TLB如何工作?(結合多級頁表)
當CPU需要轉換虛擬地址時,流程如下:
- ① 先查TLB:如果TLB里有對應的虛擬頁→物理頁映射(TLB命中),直接用物理地址訪問內存,跳過多級頁表的查詢。
- ② 若TLB沒命中:才去遍歷多級頁表(比如一級頁表→二級頁表),找到物理頁后,把這次的映射存入TLB(TLB滿了就按策略替換,比如LRU淘汰最久沒用的項)。
- TLB和多級頁表的關系:互補設計
- 多級頁表:解決 “內存占用大” 問題(按需創建頁表,不浪費內存);
- TLB:解決 “地址轉換慢” 問題(緩存常用映射,減少多級頁表的內存訪問次數)。
兩者結合,讓大地址空間(如64位系統)的內存管理 既省內存,又高效 。
通俗類比:
把多級頁表想象成 “分層的字典”(一級目錄→二級字典),查單詞(地址轉換)需要先翻目錄、再翻字典,步驟多;
TLB就是 “把常用單詞的翻譯記在便利貼上” ,下次直接看便利貼,不用再翻字典,速度飛起!
TLB也稱快表、頁表緩存、轉址旁路緩存等,CPU中封裝了內存管理單元(Memory Management Unit)芯片,用來完成地址轉換和TLB訪問交互。有了 TLB 后,那么 CPU 在尋址時,會先查 TLB,如果沒找到,才會繼續查常規的頁表。TLB 的命中率其實是很高的,因為程序最常訪問的頁就那么幾個。
四、段頁式內存管理
內存分段和內存分頁并不是對立的,而是可以組合在一個系統里面使用,組合后稱為段頁式內存管理。
段頁式內存管理實現:
- 先分段:將程序劃分為多個有邏輯意義的段,代碼段、數據段、棧和堆等;
- 再分頁:對分段劃分出來的連續空間,再劃分固定大小的頁;
段頁式內存管理是 “段式 + 頁式的混合方案” ,核心是 “先按邏輯分段,再把每個段分頁管理” ,結合兩者優勢、規避缺點:
1. 核心設計:“段套頁”的兩層結構
-
第一步:按邏輯分段
把虛擬地址空間按 邏輯功能劃分成段(如代碼段、數據段、棧段),每個段有獨立的 段描述符(存段的基址、長度、訪問權限等)。
→ 實現 邏輯模塊化(比如代碼段只讀,數據段可讀寫,方便權限管理和共享)。 -
第二步:段內分頁
每個段內部,再按 固定大小(如4KB)分成頁,用 頁表 管理“虛擬頁→物理頁”的映射。
→ 解決 段式的內存碎片問題(段式按段分配易產生碎片,分頁后碎片粒度變小),同時支持 虛擬內存(頁可換入換出硬盤)。
2. 地址轉換流程(以虛擬地址→物理地址為例)
虛擬地址被拆成三部分:段號 + 頁號 + 頁內偏移,轉換分兩步:
-
查段描述符:
根據段號找到對應的段描述符,檢查:- 權限(如代碼段是否被非法寫操作訪問);
- 范圍(頁號是否超過段的長度,避免越界)。
若合法,獲取 段的基址(段在虛擬地址空間的起始位置)。
-
查頁表(段內分頁):
結合段基址 + 頁號,定位到 頁表項,獲取物理頁框號,最后加上 頁內偏移,得到物理地址。
→ 若頁表項標記“頁不在內存”,會觸發 缺頁中斷,將頁從硬盤換入內存。
地址轉換舉例子:
用于段頁式地址變換的數據結構是每一個程序一張段表,每個段又建立一張頁表,段表中的地址是頁表的起始地址,而頁表中的地址則為某頁的物理頁號,如圖所示:
3. 對比段式、頁式,段頁式的優劣勢
特性 | 段式 | 頁式 | 段頁式 |
---|---|---|---|
邏輯管理 | 清晰(按功能分段) | 模糊(無邏輯劃分) | 清晰(段負責邏輯,頁負責物理) |
內存碎片 | 易產生大碎片(按段分配) | 碎片小(按頁分配) | 碎片小(繼承頁式優點) |
權限/共享 | 方便(段級控制) | 麻煩(需額外標記) | 方便(段級控制) |
地址轉換開銷 | 一次查表(段→物理) | 一次查表(頁→物理) | 兩次查表(段→頁表→物理),依賴TLB加速 |
4. 通俗類比:“文件夾套文件”
- 段 ≈ 文件夾:按功能分類(代碼文件夾、數據文件夾),每個文件夾有“屬性”(只讀/可寫);
- 頁 ≈ 文件夾里的文件:每個文件夾里的內容按固定大小(如4KB)分成多個文件,方便存儲和搬運(換入換出硬盤);
- 地址轉換 ≈ 找文件:先找文件夾(段),再在文件夾里找具體文件(頁),最后定位文件內的位置(偏移)。
核心結論:
段頁式通過 “段管邏輯、頁管物理” ,平衡了 模塊化(權限、共享) 和 內存效率(碎片、虛擬內存) ,代價是地址轉換更復雜(需兩次查表),因此依賴 TLB(高速緩存) 加速常用地址的轉換。
現代操作系統(如Linux、Windows)的內存管理,本質都是段頁式的變種(結合了虛擬內存、TLB優化)。
五、Linux內存管理
5.1 從intel處理器發展歷史講起
要理解 Intel 處理器發展中 內存分段與虛擬內存 的演變,可按 “問題→方案→升級” 的邏輯,結合關鍵 CPU 型號拆解:
一、早期 CPU(8080/8085):無虛擬內存,直接物理尋址
- 核心問題:程序直接操作物理內存,多進程會互相覆蓋數據(比如進程 A 改寫進程 B 的代碼),且寄存器位數和地址總線一致(如 8 位 CPU 只能尋址 8 位空間),擴展能力差。
二、8086(1978):內存分段解決“尋址能力不足”,但無保護
- 矛盾:8086 是 16 位寄存器,但地址總線是 20 位(可尋址 1MB 內存)。16 位寄存器最多只能表示 64KB(21?),如何訪問 1MB 空間?
- 方案:內存分段:
- 物理地址公式:
物理地址 = 段基址 × 16 + 段內偏移
。 - 舉例:段寄存器
CS=0x1000
(段基址),指令指針IP=0x0005
(段內偏移),則物理地址 =0x1000×16 + 0x0005 = 0x10005
。 - 本質:把 1MB 內存切成多個 64KB 的段(段內偏移最大 0xFFFF,即 64KB),讓 16 位寄存器間接訪問 20 位地址。
- 物理地址公式:
- 缺陷:仍為 實模式,段基址可隨意設置,程序能直接篡改物理內存,多進程無法安全共存。
三、80286(1982):保護模式 + 分段,虛擬內存雛形
- 目標:解決實模式的安全問題,支持多進程。
- 升級:保護模式的分段:
- 段寄存器不再存“段基址”,而是 段選擇子(類似“索引”)。
- 通過段選擇子,去 全局描述符表(GDT) 查找 段描述符(存段基址、段長度、權限,如“代碼段只讀”)。
- 操作系統管理 GDT,進程不能隨意修改段選擇子,必須通過 中斷/調用門 切換特權級(如用戶態→內核態),防止非法訪問。
- 意義:首次實現 內存保護(段級權限控制)和 多進程隔離(不同進程的段描述符不同,地址空間互不干擾),是虛擬內存的雛形(但未解決內存碎片和交換效率問題)。
四、80386(1985):段頁式結合,虛擬內存成熟
80386 除了完成并完善從 80286 開始的段式內存管理的同時還實現了頁式內存管理。但沒有繞開段式內存管理,而是建立在段式內存管理基礎上,這就意味著頁式內存管理則作用是:由段式內存管理所映射而成的地址上再加一層地址映射。
由于此時由段式內存管理映射而成的地址不再是物理地址,intel就稱之為線性地址(虛擬地址)。于是段式內存管理先將邏輯地址映射成了線性地址,然后由頁式內存管理將線性地址映射成了物理地址。
- 邏輯地址:程序所使用的地址,通常沒有被段式內存管理映射的地址;
- 線性地址(虛擬地址):通過段式內存管理映射的地址;
- 新增:分頁機制:
- 在保護模式基礎上,引入 分頁:把虛擬地址和物理地址切成 固定大小的頁(如 4KB),用 頁表 管理“虛擬頁→物理頁”的映射。
- 地址轉換流程:
- 先通過 分段 得到 線性地址(段基址 + 段內偏移);
- 再通過 分頁 將線性地址轉成物理地址(若關閉分頁,線性地址直接是物理地址)。
- Linux 的優化:平坦內存模型:
- Linux 為簡化設計,將所有段的 段基址設為 0,段界限設為整個虛擬地址空間(如 32 位下 4GB),讓分段“名存實亡”(只剩權限檢查功能,如代碼段只讀),實際用 分頁 主導虛擬內存管理(多級頁表、Swap、缺頁中斷等)。
五、64 位時代(如 x86-64):分段弱化,分頁主導
- 趨勢:64 位 CPU 中,分段機制進一步簡化:
- 段基址被強制設為 0,段界限覆蓋整個 64 位地址空間,實際變成 平坦模式,分段僅保留最基本的權限檢查(如代碼段不可寫)。
- 分頁成為虛擬內存的核心,發展出 多級頁表(解決頁表內存占用)、TLB(加速地址轉換)等優化,支撐 TB 級虛擬地址空間。
關鍵邏輯總結:
- 內存分段的誕生:是 8086 為解決“16 位寄存器訪問 20 位地址”的權宜之計,后來在保護模式中承擔 內存保護 職責。
- 虛擬內存的實現:保護模式(分段)實現 地址隔離和權限控制,分頁解決 內存碎片和 Swap 效率問題,兩者結合形成段頁式。
- Linux 的取舍:通過“平坦模型”弱化分段的尋址功能,僅用其保護特性,讓 分頁成為虛擬內存管理的核心。
- Intel 的兼容性:新處理器仍保留分段機制,只為向后兼容,實際應用中已幾乎不用分段的尋址能力。
理解這段歷史,就能明白:內存分段是硬件歷史遺留的“補丁”,而分頁才是現代虛擬內存的靈魂,Linux 等系統通過巧妙設計,讓分段“退居幕后”,分頁“挑大梁”。
5.2 Linux 采用了什么方式管理內存?
Linux 內存主要采用的是頁式內存管理,但同時也不可避免地涉及了段機制。
從 整體架構→空間劃分→用戶態布局 層層拆解,Linux內存管理核心邏輯如下:
一、硬件歷史倒逼:Linux如何應對Intel的“分段”要求?
Intel x86 CPU的設計規則是:程序地址必須先經過“分段映射”,再進行“分頁映射”(歷史遺留,源于8086的分段尋址)。但Linux的核心需求是 “簡單、高效的虛擬內存” ,于是采取“折中策略”:
- 讓分段“形同虛設”:把所有段的起始地址設為0,段長度覆蓋整個虛擬地址空間(如32位下占4GB)。這樣,分段后的“線性地址”和原始虛擬地址幾乎一致,相當于屏蔽了分段的“尋址功能”,只保留其 “權限控制” (如代碼段只讀、數據段可讀寫)。
Linux系統中每個段都是從0地址開始的整個4GB的虛擬內存空間,也就是所有段的起始地址都一樣,這意味著Linux系統中的代碼,包括操作系統本身的代碼和應用程序代碼,所面對的地址空間都是線性地址空間(虛擬地址),這種做法相當于屏蔽了段內存分配的段尋址,處理器中邏輯地址的概念,段只有訪問控制和內存保護的功能。
二、Linux虛擬地址空間:內核與用戶的“楚河漢界”
Linux 操作系統中,虛擬地址空間被劃分為 內核空間 和 用戶空間 ,不同位數系統的地址空間范圍劃分不同:
系統位數 | 虛擬地址總大小 | 內核空間占比 | 用戶空間占比 | 特點 |
---|---|---|---|---|
32位 | 4GB | 1GB(高地址,如0xC0000000~0xFFFFFFFF ) | 3GB(低地址,如0x00000000~0xBFFFFFFF ) | 內核占比小,用戶空間充足 |
64位 | 超大(如256T) | 128T(最高地址段) | 128T(最低地址段) | 中間大量地址未定義 |
內核與用戶空間的核心區別:
- 訪問權限:
- 進程在 用戶態 時,只能訪問 用戶空間 內存;
- 只有進入 內核態(如系統調用、中斷),才能訪問 內核空間 。
- 物理映射:
所有進程的 內核虛擬地址 ,都映射到同一塊物理內存(內核代碼、數據全局共享)。這樣,進程切換到內核態后,無需重新加載內核數據,直接訪問即可。
三、用戶空間布局:從低到高的“分段江湖”(以32位為例)
用戶空間從 低地址→高地址 ,依次分布7個區域(含保留區),每個區域分工明確:
1. 保留區(最底部,接近0地址)
- 作用:攔截非法地址訪問(如
NULL
指針)。 - 原理:系統規定“低數值地址(如0x00000000附近)為非法”,若程序訪問這些地址(比如空指針解引用),會觸發段錯誤(Segmentation Fault),避免程序崩潰后亂改內存。
2. 代碼段(.text)
- 內容:存放程序的可執行二進制代碼(如函數指令)。
- 特性:只讀(防止程序意外修改自身代碼)。
3. 數據段(.data)
- 內容:存放已初始化的全局變量、靜態常量(如
int g_var = 10;
)。 - 特性:可讀寫,程序運行時能修改這些變量。
4. BSS段(.bss)
- 內容:存放未初始化的全局變量、靜態變量(如
int g_var;
,默認值為0)。 - 特性:可讀寫,加載時會自動清零。
5. 堆(Heap)
- 內容:動態分配的內存(如
malloc()
申請的空間)。 - 增長方向:從低地址→高地址向上擴展,需手動管理(
free()
釋放)。
6. 文件映射段
- 內容:動態庫(如
libc.so
)、共享內存(如mmap()
映射的文件)。 - 增長方向:從低地址→高地址向上擴展(和堆同方向,中間可能有未使用的間隙)。
7. 棧(Stack)
- 內容:局部變量、函數調用的上下文(如參數、返回地址)。
- 增長方向:從高地址→低地址向下擴展,大小固定(默認8MB,可通過
ulimit -s
調整)。 - 風險:棧溢出(如遞歸過深)會導致程序崩潰。
動態分配的秘密:
- 堆 + 文件映射段 是用戶空間的“動態區域”:
- 堆用
malloc()
分配,由程序員手動管理; - 文件映射段用
mmap()
分配,可映射文件或匿名內存(如共享內存)。
- 堆用
- 棧是“靜態區域”,大小固定,滿了就溢出。
核心邏輯總結:
- 硬件妥協:Linux為兼容Intel的分段機制,讓分段“名存實亡”,實際以 頁式管理 為主。
- 空間隔離:虛擬地址分為內核(高權)和用戶(低權)空間,保障系統安全。
- 用戶態細節:從低到高的分段布局,讓代碼、數據、動態內存、棧各司其職,保留區防止空指針踩雷,堆和文件映射支持靈活分配。
理解這套設計,就能明白:Linux的虛擬內存,是“硬件約束”和“軟件高效”妥協后的產物,既兼容歷史,又通過分層布局實現了安全與靈活。
操作系統內存管理總結
虛擬內存是操作系統應對 “多進程共存、物理內存有限、安全訪問” 難題的核心方案,其設計圍繞 “硬件妥協+效率優化” 展開,核心邏輯可總結為:
一、誕生背景:解決三大痛點
多進程環境下,直接使用物理內存會出現:
- 地址沖突:進程互相篡改數據(如進程A覆蓋進程B的代碼);
- 內存不足:物理內存撐不起大量進程/大程序;
- 安全風險:程序非法訪問系統核心內存(如篡改內核)。
二、實現進化:從分段到分頁的迭代
1. 分段:邏輯清晰,但低效
- 思路:按“代碼、數據、棧”等邏輯功能劃分虛擬地址,每個段連續,方便權限管理(如代碼段只讀)。
- 缺陷:段大小不固定,產生 外部內存碎片(空閑內存分散,無法分配大塊空間),且換入換出效率低(整段操作)。
2. 分頁:固定大小,解決碎片
- 思路:將虛擬/物理地址切成 等長頁(如Linux的4KB),用頁表記錄“虛擬頁→物理頁”的映射。
- 優勢:
- 碎片粒度小(僅4KB),解決外部碎片;
- 換入換出高效(僅操作單個頁)。
- 新問題:頁表太大(如32位系統需百萬級頁表項),占用內存多。
3. 多級頁表 + TLB:優化空間與速度
- 多級頁表:將頁表分層(如二級、三級),按需加載頁表層級(未使用的層級不占內存),解決頁表臃腫問題。
- TLB(地址轉換緩存):在CPU內緩存 常用頁表映射,避免每次地址轉換都遍歷多級頁表,加速地址轉換。
4. Linux的妥協:兼容Intel,弱化分段
因Intel處理器強制“先分段再分頁”,Linux采取 “段基址設為0” 的策略:
- 讓所有段的起始地址相同,虛擬地址近似“線性空間”,屏蔽分段的尋址功能,僅用分段做 權限檢查(如代碼段只讀);
- 實際以 分頁+多級頁表+TLB 主導虛擬內存管理。
三、核心價值:虛擬內存的三大作用
-
突破物理內存限制:
利用 局部性原理(程序僅頻繁訪問部分內存),將“不常用頁”換出到硬盤(Swap),需要時再換入,讓程序“以為”擁有超大內存。 -
實現多進程隔離:
每個進程有獨立頁表,虛擬地址空間互不干擾(進程A的虛擬地址≠進程B的物理地址),防止地址沖突和非法訪問。 -
增強內存安全:
頁表項的 權限標記(如只讀、存在位)+ 分段的權限控制,防止程序越權訪問(如用戶態程序篡改內核內存)。
邏輯閉環:
虛擬內存是 “硬件約束(Intel分段)”與“軟件需求(高效、安全、多進程)”妥協的產物:
從分段的“邏輯清晰但低效”,進化到分頁的“高效但頁表臃腫”,再通過多級頁表和TLB優化,最終Linux以“弱化分段、強化分頁”落地,實現 “內存擴容、進程隔離、安全防護” 的核心目標。
這就是虛擬內存的底層邏輯——用復雜的硬件適配和算法優化,讓程序跑得更穩、更自由。