認識 Linux 內存構成:Linux 內存調優之虛擬內存與物理內存認知

認識 Linux 內存構成:Linux 內存調優之虛擬內存與物理內存認知

寫在前面博文內容涉及 linux 內存構成基本認知包括虛擬內存和物理內存映射,多級頁表和MMU簡單認知理解不足小伙伴幫忙指正

對每個人而言,真正的職責只有一個:找到自我。然后在心中堅守其一生,全心全意,永不停息。所有其它的路都是不完整的,是人的逃避方式,是對大眾理想的懦弱回歸,是隨波逐流,是對內心的恐懼 ——赫爾曼·黑塞《德米安》

認識 Linux 內存構成:虛擬內存與物理內存

計算機中的進程小伙伴一定不陌生,一般情況下一個應用會啟動一個主進程,若干個子進程或者線程,每個進程都有一個內存地址空間用于存放當進程的一些共享數據,所以在進程啟動時會請求一定大小的內存,這里的內存不是實際的物理內存地址,不直接定位物理內存。相反,是一塊虛擬內存空間,內核會在進程地址空間中的虛擬地址空間和物理地址做一個映射來達到訪問物理內存的目的。

比如在 Java 啟動參數中,-Xms(Initial Heap Size)指定的 jvm 的初始大小是虛擬內存(Virtual Memory)的大小,而非直接對應物理內存的分配

代碼語言:JavaScript代碼運行次數:0運行復制

java -jar -Xms1G -Xmx4G  arthas-boot.jar

JVM 向操作系統申請 1GB 的虛擬地址空間(對應 VIRT 的一部分)。物理內存占用可能僅為 100MB(取決于 JVM 初始化時的實際內存需求)。

那么這里的虛擬地址空間到物理內存地址的映射是什么?

內存虛擬地址空間物理地址映射

首先這里的虛擬空間地址大小由`處理器架構位數決定

在x86_64的 64 位系統上面,理論的內存地址就是 16EiB(2^64) 的大小。但實際實現中受硬件架構和操作系統設計的限制,僅部分地址位被有效利用,硬件層面通過符號擴展機制僅支持 48 位虛擬地址(低 48 位),高 16 位(48-63)需填充為第 47 位的值,形成規范地址(Canonical Address),實際可尋址空間為 256 TB(2^48 字節),分為兩部分:

用戶空間:0x0000000000000000 至 0x00007FFFFFFFFFFF(128 TB)內核空間:0xFFFF800000000000 至 0xFFFFFFFFFFFFFFFF(128 TB)

Linux 系統默認使用完整的 48 位地址,但用戶進程實際可用空間通常更小(如通過 TASK_SIZE_MAX 限制為 128 TB 減去保護頁)

在 Linux 系統中查看 /proc/cpuinfo 時,address sizes 字段描述了 CPU 的物理地址和虛擬地址的尋址能力

代碼語言:javascript代碼運行次數:0運行復制

┌──[root@liruilongs.github.io]-[~]└─$cat /proc/cpuinfo | grep addressaddress sizes   : 45 bits physical, 48 bits virtualaddress sizes   : 45 bits physical, 48 bits virtual┌──[root@liruilongs.github.io]-[~]└─$

45 bits physical:表示 CPU 可以尋址的物理內存空間大小,決定了 CPU 能直接訪問的最大物理內存, 2^45 字節,CPU 理論上最多支持 32 TiB 的物理內存

48 bits virtual:表示 CPU 的虛擬地址空間大小。決定了單個進程能使用的最大虛擬內存空間,2^48 字節,每個進程的虛擬地址空間上限為 256 TiB(操作系統通過虛擬內存機制將虛擬地址映射到物理地址或磁盤交換空間)。

所以進程虛擬地址空間的大小不取決于安裝的物理內存大小 RAM,而是取決于處理器架構,單個進程通常不使用其整個地址空間。其中大部分是未分配的,并且沒有映射到任何實際的物理內存。

其次進程在通過虛擬地址空間訪問物理內存時

通過多級頁表實現虛擬→物理地址轉換,通過內存管理單元(MMU)執行實時地址轉換與訪問權限校驗,同時支持按需分頁(Demand Paging)機制延遲物理頁幀分配.

代碼語言:javascript代碼運行次數:0運行復制

進程訪問虛擬地址 → MMU 查詢 TLB → [命中 → 直接獲取物理地址]                │                └→ [未命中 → 查詢頁表 → 權限檢查 → 缺頁處理(可選)→ 生成物理地址 → 更新 TLB]                │                └→ 訪問物理內存

這里的多級頁表和MMU是什么?

多級頁表和MMU

處理器架構定義的標準內存單元為頁面(Page),在x86_64架構中采用固定大小4 KiB(4096字節),計算機系統將內存組織為固定大小的塊,這里的塊就是頁面,或者叫頁幀。頁表用來存儲虛擬頁到物理頁幀的映射,由操作系統 MMU 維護的數據結構。MMU(Memory Management Unit)內存管理單元.

多級頁表:現代系統使用多級頁表(如 x86-64 的 四級頁表),逐步縮小搜索范圍。

虛擬地址拆分:虛擬地址被分割為多個索引字段,逐級查詢頁表項(PTE),48 位虛擬地址可能拆分為:PGD索引(9位) → PUD索引(9位) → PMD索引(9位) → PTE索引(9位) → 頁內偏移(12位)

進程通過頁表查詢虛擬地址和物理地址的映射關系, 首先會檢查 TLB 緩存,TLB(Translation Lookaside Buffer)高速緩存頁表項的硬件緩存

命中(TLB Hit):若 TLB 中存在該虛擬地址對應的物理地址,直接使用緩存結果,跳過頁表查詢。 未命中(TLB Miss):若 TLB 中無緩存,需查詢頁表。

在實際的查詢之前,還有一個驗證頁表項的步驟,主要進行權限檢查,MMU 檢查頁表項的權限(如讀/寫/執行、用戶/內核模式權限)

虛擬地址分頁號和偏移量,頁面定位頁表中的對應索引位置,根據頁面查詢到物理地址的起始位置,然后再通過偏移量找到具體的數據, 如果權限違規,會觸發 段錯誤(Segmentation Fault)(例如嘗試寫入只讀頁,溢出,虛擬內存溢出之類)。

看一個Demo,通過 ulimit 模塊設置單個進程棧大小為 16

代碼語言:javascript代碼運行次數:0運行復制

┌──[root@liruilongs.github.io]-[~]└─$ulimit -s 16┌──[root@liruilongs.github.io]-[~]└─$ulimit  -a | grep stackstack size              (kbytes, -s) 16

運行 ls /etc/ 命令時,發生了段錯誤,這里實際會生成了一個核心轉儲文件。

代碼語言:javascript代碼運行次數:0運行復制

┌──[root@liruilongs.github.io]-[~]└─$ls /etc/Segmentation fault (core dumped)

上面設置了 棧的大小,所以執行的命令存在棧溢出的情況,也可以通過 ulimit 配置虛擬內存大小,進程在初始化階段(如加載自身代碼或動態鏈接器)就耗盡虛擬地址空間。 動態鏈接器無法完成基礎的內存分配(如棧、堆、代碼段),導致進程崩潰。

代碼語言:javascript代碼運行次數:0運行復制

┌──[root@liruilongs.github.io]-[~]└─$ulimit -v 1024┌──[root@liruilongs.github.io]-[~]└─$lsSegmentation fault

當啟動一個程序時,先給程序分配合適的虛擬地址空間,但是不需要把所有虛擬地址空間都映射到物理內存,而是把程序在運行中需要的數據,映射到物理內存,需要時可以再動態映射分配物理內存

因為每個進程都維護著自己的虛擬地址空間,每個進程都有一個頁表來定位虛擬內存到物理內存的映射,每個虛擬內存也在表中都有一個對應的條目,

當進程訪問虛擬地址,但是在頁面中查不到時,內核就會產生一個缺頁異常(Page Fault)內核此時會重新分配物理內存,更新頁表。

所以在驗證頁表項通過之后,查詢頁表數據標記為不存在,會促發缺頁中斷,會重新分配物理頁幀(從空閑內存或通過頁面置換算法如 LRU 淘汰舊頁),或者磁盤(如交換分區或文件)加載數據到物理頁幀,更新頁表項,標記為有效,重新執行觸發缺頁的指令。

通過頁表項獲得物理頁幀基地址,加上虛擬地址中的頁內偏移,得到最終物理地址。MMU 將物理地址發送到內存總線,CPU 讀取或寫入物理內存。

博文部分內容參考

? 文中涉及參考鏈接內容版權歸原作者所有,如有侵權請告知:)

red Hat Performance Tuning 442 》

《性能之巔 系統、企業與云可觀測性(第2版)》

? 2018-至今 liruilonger@gmail.com, All rights reserved. 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)

? 版權聲明
THE END
喜歡就支持一下吧
點贊13 分享