不知有沒有注意過一個現象,運行中的進程會出現相同地址卻儲存不同值的情況?這是什麼原因?本文將聚焦Linux內存底層邏輯,拆解虛擬地址空間、物理內存與頁表的關聯,揭秘進程內存隔離與地址映射的核心原理。
一.同址不同存
先看一段代碼:
#include<stdio.h>
int main()
{
int num = 10;
int n = fork();
if(n == 0){
num =20;
printf("%p: %d\n",&num,num);
}
else{
printf("%p: %d\n",&num,num);
}
return 0;
}
當我們創建子進程改變父進程中創建的變量——同一個變量,同一個地址,同時讀取數據,卻得到了不同內容!
不是一個地址只能存一個數據嗎?為什麼不會互相干擾? 這便是"同址不同存",但它是如何形成的?
二.虛擬(進程/線性)地址空間
地址空間: 地址總線排列組合形成地址範圍[0,2^32](32位計算機中,有32位地址和數據總線)
而進程地址空間,本質是在總空間上標定不同區域的起始和結束,進而可視進程空間範圍大小:
以4G內存為例,對總長4G的空間進程區域劃分,限定不同類型數據的使用範圍(空間區域調整就是區域分配的變化);
但如果內存以這種固定的區域劃分形式存在,會帶來什麼問題?對空間利用不充分;本來有4G的空間可以被任意使用,卻只能被start和end限制住(比如棧區滿了,堆區還空着,但由於"區域",計算機無法使用“空閒”的區域而導致內存不夠用),造成了內存碎片和浪費的;顯然,"真實"內存不能這樣存在。
所以,進程地址空間是“虛擬”的,也就是説這個空間佈局並不是真實存在;或者説 只是給每個進程一個完整4GB的“內存視圖”,並且對這4GB有進行了區域劃分,讓進程能夠以統一的視角看待內存;
為什麼每個進程看到的都是4GB?
相當於給每個進程畫個"大餅",讓進程以為有“全部”空間;不用處理內存碎片、地址衝突;當映射時僅映射實際使用的虛擬地址到物理內存,未用部分不佔物理空間,支持多進程共享物理內存(下文頁表説)。
三.物理內存
物理內存是計算機真實硬件內存,全局共享,按固定大小(如4KB)劃分為頁幀,作為最小分配單位。無固定功能分區,由系統動態分配給進程,承載進程運行所需的實際數據與指令,是數據讀寫的最終載體。
物理內存,是內存條提供的可直接訪問的硬件和儲存空間,物理內存中的實際內容,就是進程加載內存條的數據;
四.頁表
回到最開始的問題,如果變量的地址是物理地址,不可能出現“同一個變量,同一個地址,同時讀取數據,卻得到不同內容”的現象,畢竟物理內存每個編號都是確定的,4GB被進程共享的;所以printf輸出的地址一定是虛擬地址,但是,我們剛才説過,實際數據是被寫到物理中內存的,進程卻拿的是虛擬內存,那麼兩者之間必定有能夠“連接”的方式——頁表。
頁表:操作系統中用於存儲虛擬頁號與物理頁框號映射關係的表格,實現虛擬地址到物理地址的轉換,輔助內存管理。
頁表映射原理文字總結
- 進程生成虛擬地址,CPU將虛擬地址按固定頁大小拆分為 虛擬頁號(VPN) 和 頁內偏移(頁內偏移位數由頁大小決定,如4KB對應12位,全程不變)。
- CPU讀取 CR3寄存器,獲取當前進程頁表的物理基地址(CR3是唯一入口,進程切換時會更新CR3為新進程頁表基址)。
- 用頁表物理基地址 + VPN計算偏移,定位到頁表中對應頁表項(本質是內存地址計算,找到存儲該VPN映射關係的內存單元)。
- 檢查頁表項控制位:確認有效位(該虛擬頁是否已加載到物理內存)、權限位(當前訪問是否合法,如讀/寫/執行),合法則提取頁表項中的 物理頁框號(PFN)。
- 將PFN左移頁內偏移位數,與頁內偏移拼接,生成最終可訪問的 物理地址。
- CPU通過物理地址訪問實際內存,獲取數據/指令返回給進程。
缺頁中斷
- 觸發條件:CPU查頁表時,目標VPN無有效映射(頁表項無效/不存在)。
- 核心作用:讓OS將磁盤中缺失的頁加載到物理內存,補全虛實映射。
頁表: 思考三個問題
問題一:對於一個數據,虛擬內存有隻讀(常量區)概念,物理內存卻沒有,那如何確定一個數據訪問權限?
上圖介紹時,頁表中還有兩列沒有涉及: 其中一列方式,就是對數據訪問權限的控制;
問題二:之前介紹過,進程有一種狀態是被掛起的,是通過頁表實現的嗎?
缺頁中斷的第二種觸發條件:頁表項無效
同理,進程可以對大文件可實現分批加載,對於掛起/分批加載/維持,確定了虛擬內存頁表中數據,但映射到物理內存部分卻不添(加載);包括寫時拷貝,將權限設置為“r”,不允許其寫,導致這在更改數據時頁表不得不讓操作系統重新分配內存,更改數據,權限改“w”。
問題三:進程在被創建時,是先創建內核數據結構還是先將可執行程序加載到內存?
顯然是先創建內核數據結構(核心是PCB),再加載可執行程序;
進程=內核數據結構(task_struct && mm_struct && 頁表) + 程序代碼和數據
頁表作用
有了頁表之後,物理內存在哪兒開闢,什麼時候開闢,怎麼限制都不重要了,只要映射能找到就可以(讓無序變有序);而虛擬內存,我們始終以一種連續、線性的方式,讓進程看待內存,更好地管理內部存儲。
五.總結
為什麼要有虛擬地址空間?=進程地址空間=線性地址空間
- 讓進程以統一的視角看待內存
- 增加進程虛擬地址空間可以讓我們訪問內存時增加一個轉換過程,這個轉換過程中,可以對我們尋址請求作審查,一旦異常訪問,直接攔截,請求不會到達物理內存,保護物理內存。
- 因為有地址空間和頁表的存在,可以將進程管理模塊和內存管理模塊進行解耦合。