🎁個人主頁:工藤新一1
🔍系列專欄:C++面向對象(類和對象篇)
🌟心中的天空之城,終會照亮我前方的路
🎉歡迎大家點贊👍評論📝收藏?文章
文章目錄
- 虛擬進程地址空間
- 一、虛擬地址空間經典布局
- 二、頁表
- 2.1 核心定義:什么是頁表?
- 2.2 為什么需要頁表?
- 三、虛擬地址空間
- 3.1 虛擬地址空間是什么?
- 3.2 如何管理虛擬地址空間?
- 3.3 為什么要有虛擬地址空間?
- 四、工業疑問
- a. 創建進程時,是否可以只創建 PCB/地址空間/頁表?
- b. 創建進程現有task_struct,還是先加載數據/代碼
- c. 如何理解進程掛起?
- 五、虛擬內存管理
虛擬進程地址空間
虛擬地址空間 是指一個 OS為每個運行中的進程(程序)提供的一個抽象的、獨立的、連續的邏輯地址范圍。這個空間是 “虛擬的”,這并不意味著物理內存中真的有這么一大塊連續的區域,而是通過硬件(MMU
,內存管理單元)和 Kernel
的協作,將虛擬地址映射到分散的物理內存頁上
一、虛擬地址空間經典布局
一個進程的典型內存地址空間布局如下圖所示,它被劃分為多個具有不同權限(讀、寫、執行)的段(Segments):
從高地址到低地址:
- 內核空間(Kernel Space)
- 通常位于地址空間的最高處(例如,在32位Linux中,0x C000 0000以上)。
- 存放操作系統內核的代碼、數據和數據結構。
- 所有進程共享同一份內核映射。但這段空間受保護,用戶態進程無法直接訪問,必須通過系統調用(Syscall)進入內核態才能訪問。
- 棧(Stack)
- 向下增長(向低地址方向)。
- 用于存儲局部變量、函數參數、返回地址等。
- 每個函數被調用時,會在棧上分配一個新的“棧幀”。
- 它的增長是自動的,但大小有限(通常默認為幾MB),溢出會導致“棧溢出”錯誤。
- 內存映射段(Memory Mapping Segment)
- 用于映射文件或匿名內存。
- 動態鏈接庫(如
.so
、.dll
文件)就加載在這里。 - 也可以通過
mmap()
系統調用創建,用于大塊內存的分配或進程間共享內存。
- 堆(Heap)
- 向上增長(向高地址方向)。
- 用于動態內存分配。當程序員使用
malloc()
、new
等申請內存時,內存就從這里分配。 - 堆的大小只受限于系統可用的虛擬內存總量,管理由程序員負責(分配和釋放), improper management leads to memory leaks.
- BSS 段(.bss)
- 存放未初始化的全局變量和靜態變量。
- 在程序開始執行前,操作系統會將此段初始化為零。
- 數據段(.data)
- 存放已初始化的全局變量和靜態變量。
- 代碼段(文本段)(.text)
- 存放程序的執行代碼(機器指令)。
- 通常是只讀和可執行的,以防止代碼被意外修改。
- 保留區(Reserved)
- 通常是最低地址的一段空間(例如
0x0
到0x400000
),不允許訪問,用于捕捉空指針等錯誤。
- 通常是最低地址的一段空間(例如
地址區域分布:
感受虛擬地址:
fork():
一次調用,兩次返回
這是理解 fork()
最關鍵也是最反直覺的一點:
- 在父進程中,
fork()
返回新創建子進程的進程ID(PID)(一個大于0的數)。 - 在子進程中,
fork()
返回 0。 - 如果創建失敗(例如系統資源不足),
fork()
返回 -1。
進程具有獨立性:數據層面上,互不影響;此時就需寫時拷貝,實現進程的個性化
相同地址,獲取不同變量值
二、頁表
2.1 核心定義:什么是頁表?
頁表是虛擬內存系統的核心數據結構,是連接 虛擬地址 和 物理地址 的“地圖”或“翻譯官”;是 Kernel
為每個進程維護的一個映射表,它記錄了該進程的虛擬內存頁對應到物理內存幀的映射關系
簡單來說,它的工作就是回答這個問題:
“這個進程看到的虛擬地址 X,實際上在物理內存的哪個地方?”
OS 會將進程物理地址隱藏起來,我們只能觀測到進程的虛擬地址
解決歷史遺留問題:
2.2 為什么需要頁表?
頁表是實現虛擬地址空間這一抽象概念的技術基礎。沒有頁表,虛擬內存就無法工作。它的存在是為了:
- 實現地址翻譯:將程序使用的 虛擬地址轉換 為硬件使用的 物理地址。
- 實施內存保護:通過頁表項中的權限位,控制進程對內存的訪問(可讀?可寫?可執行?)。
- 支持“換出”到磁盤:通過頁表項中的“存在/不存在”位,操作系統可以知道某頁是否在物理內存中,如果不在,它的數據存放在硬盤的哪個位置。
三、虛擬地址空間
3.1 虛擬地址空間是什么?
- **虛擬地址空間本質上就是 OS 給進程畫的一張餅! **《大富翁例子》 讓進程誤以為其獨占整個內存的相關資源
畫大餅的作用:讓每一個進程都認為自己有 4GB的物理內存空間,或者:讓每一個進程都認為自己在獨占物理內存空間
OS 為每個進程都畫了大餅,所以我們也要把這張大餅管理起來
3.2 如何管理虛擬地址空間?
先描述,在組織!
將所有的 虛擬進程地址空間[餅],用鏈表的方式管理起來;因此,對餅的管理就轉化為了對鏈表的增刪查改
虛擬地址空間本質:Kernel當中為進程創建的結構體對象!
3.3 為什么要有虛擬地址空間?
1. 將無/亂序的物理地址轉變為有序的虛擬地址!
- 對用戶(進程)而言:虛擬地址空間是連續且有序的
- 對系統(操作系統)而言:物理內存[完全隨機且不連續]的分配是靈活且混亂的
2. 地址轉化過程中對地址與操作進行合法性判定,進而保護物理內存!
a. 什么是野指針?
b. char* str = “hello linux!”; *str = ‘H’;
為什么在字符常量區寫入就會崩潰?
3. 缺頁中斷 與 按需調頁
缺頁中斷是操作系統 “欺騙” 進程的基礎,也是它管理內存的得力工具(畫大餅)
4. 使進程管理與內存管理,進行一定程度的解耦合
進程管理 & 內存管理 –——> 直線脫鉤
四、工業疑問
a. 創建進程時,是否可以只創建 PCB/地址空間/頁表?
核心觀點:惰性加載 (Lazy Loading) 與按需調頁 (Demand Paging)
問題背后隱藏著一個關鍵思想:為什么要在進程一開始就把它可能永遠用不到的東西全部加載好呢? 這太浪費了….它們遵循 “惰性”原則,只在真正需要時才分配資源。這個過程就是通過 **缺頁中斷 ** 來實現的
b. 創建進程現有task_struct,還是先加載數據/代碼
一定先有管理結構: task_struct
和 mm_struct
,空頁表[虛物]。滯后加載代碼和數據[實物],發生在第一次訪問時,由缺頁中斷機制驅動。并且,加載過程本身也是由這些數據結構指導
c. 如何理解進程掛起?
五、虛擬內存管理
虛擬內存管理是一種內存抽象機制。虛擬內存管理系統的任務,就是將進程使用的這些虛擬地址(Virtual Address)動態地映射到物理內存上的物理地址(Physical Address),或者必要時映射到磁盤上的交換空間(Swap Space)
這個過程主要由計算機的 內存管理單元(MMU) 和操作系統內核共同完成。
描述 Linux 下進程的地址空間的所有的信息的結構體是 mm_struct
(內存描述符)。每個進程只有? 個 mm_struct
結構,在每個進程的 task_struct
結構中,有?個指向該進程的結構
struct mm_struct{/*...*/struct vm_area_struct *mmap; /* 指向虛擬區間(VMA)鏈表 */ struct rb_root mm_rb; /* red_black樹 */ unsigned long task_size; /*具有該結構體的進程的虛擬地址空間的??*/
/*...*/// 代碼段、數據段、堆棧段、參數段及環境段的起始和結束地址。unsigned long start_code, end_code, start_data, end_data;unsigned long start_brk, brk, start_stack;unsigned long arg_start, arg_end, env_start, env_end;/*...*/
- Virtual Memory
進程具有獨立性:
- 內核數據結構獨立!
- 加載進內存的代碼和數據獨立!
目前,我們對于虛擬地址空間的理解只能做到局部性的邏輯自洽
🌟 各位看官好,我是工藤新一1呀~
🌈 愿各位心中所想,終有所致!