本片博客將重點回答三個問題
什么是地址空間?
地址空間是如何設計的?
為什么要有地址空間?
程序地址空間排布圖
在32位下,一個進程的地址空間,取值范圍是0x0000 0000~ 0xFFFF FFFF
回答三個問題之前我們先來證明地址空間排布是按如圖所布局的
各個區空間地址驗證代碼
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int g_unval; // 未初始化數據
int g_val = 100; // 初始化數據,一般指全局初始化數據
int main(int argc, char* argv[], char* env[]) // 命令行參數,環境變量
{// 代碼地址打印 printf("code addr: %p\n", main); printf("init global addr: %p\n", &g_val);printf("uninit global addr: %p\n", &g_unval);// 堆區,指針變量本質是變量,也要開辟空間,不過放的內容是地址char *heap_mem = (char*)malloc(10);printf("heap addr: %p\n", heap_mem);// 棧區,函數內定義的變量都是在棧上開辟空間printf("stack addr: %p\n", &heap_mem); int i = 0;for (i = 0; i < argc; i++){ // 命令行參數地址 printf("argv[%d]: %p\n", i, argv[i]);}int j = 0;for (j = 0; env[j]; j++){printf("env[%d]: %p\n", j, env[j]);}return 0;}
運行結果
堆、棧之間的兩個箭頭表示
棧向地址減小的方向增長
堆向地址增大的方向增長
證明方法也很簡單
運行結果也證明確實是這樣
我們會發現堆區之間差了20字節
我們平時申請空間,系統會多給你一些空間
多出的空間用來記錄你堆的屬性信息
所以平時我們free空間,只要傳起始地址
剩下的系統知道要free多長的空間
我們在系統部分要記的兩個口訣
1、先描述在組織
2、堆、棧相對而生
static 修飾局部變量,本質就是將該變量開辟在全局區域
所有的字面常量將來都是要映編碼進代碼的
在正文代碼上其實有一小段是字符常量區
什么是地址空間以及是如何設計的
我們平時打印各種地址其實就是進程打印
,程序運行之后打印的
在解釋什么是地址空間之前,我們先來講一個故事
有一個富豪,他有5億元家產
他有3個私生子,彼此并不知道對方存在
3個私生子分別叫張三、李四、王五
富豪為了鼓勵3個兒子
對張三說你好好念書將來5億就是你的了
對另外兩個兒子也說了同樣的話
因為不知道彼此存在
對于這三個兒子,他們都認為是5億繼承人
富豪給他們每一個兒子畫了一個大餅
有一天,張三對他爸說要1千買學習資料
李四說我成年了想買一輛兩百萬的跑車
王五說我創業需要50萬
富豪都給了他們需要的錢
只要他們要錢富豪都會給
有一天張三說要1億,富豪說要這么多干嘛
拒絕了張三,即使被拒絕了張三依舊認為自己是5億的繼承人
我們站在上帝視角知道即使富豪過世了
這三個兒子不可能都擁有5億
他們每個兒子可以斷斷續續的要錢
但永遠要不到5億,卻依然堅信自己以后能擁有這5億
對應關系
富豪 ---- 操作系統
兒子 ---- 進程
富豪畫的餅 ---- 地址空間
在內存中的地址空間本質是一種數據結構
將來要和一個特定的進程關聯起來
以前直接訪問物理內存,如果有野指針的問題
可能直接訪問到其他進程
內存本身是隨時可以被讀寫
所以在老式的程序里面野指針是會直接改了其他進程的東西
結論:直接使用物理內存不安全
現代計算機的解決方式
每個進程有自己的PCB
操作系統給每個進程一個虛擬的地址空間
通過映射機制映射到物理內存
我們可能會有疑問,最終還是會訪問物理內存
萬一虛擬地址是一個非法地址呢
其實映射機制有一個檢查機制,萬一是非法地址
可以不讓你映射
虛擬地址空間究竟是什么?
每個進程都要有地址空間
就好比操作系統要給每個進程畫個餅
操作系統要給每個餅做管理
在內存中的地址空間本質是一種內核數據結構
它里面至少有各個區域的劃分
我們把如圖結構稱為地址空間
區域空間并不是死的,會有一定的變化
所謂的范圍變化,本質是對start 或end 標記值 + - 特定的范圍即可
所以一個地址為什么有兩個值
到這里就可以回答這個問題了
剛開始創建時只有父進程
然后創建子進程,子進程會繼承父進程的屬性
所以子進程的頁表、地址空間和父進程一樣
當子進程嘗試修改變量值時
因為要保證進程的獨立性
操作系統會重新為子進程,開辟一份物理內存
并修改子進程頁表的映射關系
但是虛擬地址并不受影響,還是一樣的地址
但映射到物理內存的不同區域
看到的值便不一樣
這種策略就叫作寫時拷貝
為什么要存在地址空間
- 保護物理內存
凡是非法的訪問或者映射,
os都會識別到,并且終止你這個進程
因為地址空間和頁表是os創建并維護的
也就意味著凡是想使用地址空間和頁表
進行映射,也一定要在OS的監管下進行訪問
- OS耦合度更低
因為有地址空間的存在
因為有頁表映射的存在
我們的物理內存就可以
對未來的數據進行任意位置的加載
物理內存的分配就可以和
進程的管理互不關聯
從而使內存管理模塊和進程管理模塊
完成解耦合
我們在C、C++語言上new、malloc空間時
本質是在虛擬地址空間申請的
因為有地址空間的存在,所以上層申請空間
物理內存可以甚至一個字節都不給你
當你真正訪問物理地址時,才執行
內存相關算法,幫你申請內存,構建
頁表映射關系,這樣空間使用率為100%
以此提高整機效率
- 保證進程的獨立性
因為有地址空間的存在,每一個進程
都認為自己擁有4GB的空間(32)
并且各個區域是有序的,進而
可以通過頁表映射到不同的區域
來實現進程的獨立性,每一個進程
不知道,也不需要知道其他進程的存在
重新理解什么是掛起?
加載的本質就是創建進程,但并不是
非得把所有程序的代碼和數據加載到
內存中,并創建內核數據結構建立映射關系
在極端情況下,只有內核結構被創建
此時就叫新建狀態
理論上,可以實現對程序的分批加載
既然可以分批加載,自然可以分批換出
一個進程短時間不會被執行,比如阻塞
而使進程的數據和代碼被換出就叫掛起
頁表不僅僅映射物理內存
磁盤位置也可以映射
所以當代碼掛起時,不用把數據
刷新到磁盤里。只要把空間直接釋放掉,
在頁表重新填上磁盤當中代碼和數據的
位置,就可以完成一次基本的掛起
擴展知識
在vim中注釋
Ctrl + v 進入視圖模式(V-BLOCK)
hjkl 選中需要注釋代碼
輸入大寫的i,左下角出現INSERT
輸入 // ,再按esc 自動注釋選中的代碼
取消注釋還是上面的操作
選中需要注釋的代碼,按d刪除
?????
本篇博客完,感謝閱讀🌹🌹🌹
如有錯誤之處可評論指出,博主會耐心聽取每條意見