保定企業(yè)自助建站搜索關(guān)鍵詞優(yōu)化排名
目錄
?前言:?地址空間回顧
?驗證:一個變量是否會有兩個值?
一. 什么是地址空間
虛擬地址與物理地址之間的關(guān)系
二. 地址空間是如何設(shè)計的?
1. 回答一個變量兩個值
2.擴展
繼續(xù)深入理解
三. 為什么要有地址空間
原因:
1. 使操作系統(tǒng)對訪問或者映射的合法性檢查,殺掉非法進程,從而保護數(shù)據(jù)安全。?
2. ?使物理內(nèi)存分配與進程管理,通過頁表進行解耦,在加載時確定映射關(guān)系后,相互獨立
3. 保證每個進程以統(tǒng)一的視角(有序的區(qū)域劃分)進行管理,完成進程獨立性的實現(xiàn)
頁表補充
?前言:?地址空間回顧
之前我們學(xué)習(xí)C是,對內(nèi)存分布的了解?
字符串常量在靜態(tài)常量區(qū)(靜態(tài)區(qū))?
那么這真的是內(nèi)存嗎?
答案是:不是
?那我們之前所了解的上圖又是什么? 那真正的內(nèi)存又是指什么?讓我們來解釋其中的奧秘
?驗證:一個變量是否會有兩個值?
我們運行下面的代碼
#include <iostream>2 #include <unistd.h>3 using namespace std;4 5 int _gar = 100;6 int main()7 {8 pid_t pd = fork();9 10 if (pd == 0)11 {// 子進程12 int cen = 0;13 while (1)14 {15 cout << "I am child ,Pid :" << getpid() << " PPid:" << getppi d() << " _gar = " << _gar << "地址:" << &_gar << endl;16 sleep(1);17 cen++;18 if (cen == 5)19 {20 _gar = 200; 21 cout << "100 -> 200 seccoss -gar = " << _gar << "++++++++++ +++++++++++" << endl; 22 }23 }24 }25 else26 {27 // 父進程28 while (1)29 {30 cout << "I am father ,Pid :" << getpid() << " PPid:" << getpp id() << " _gar = " << _gar << "地址:" << &_gar << endl;31 sleep(1);32 }33 }34 35 return 0;36 }
根據(jù)我們實驗出來的結(jié)果:
這其實是虛擬地址,也叫線性地址。
幾乎所有的語言,所說的“地址”指的并非物理地址,而是虛擬地址!!!!
這里對本文剛開始的內(nèi)存進行解釋:
內(nèi)存可以分為主存儲器(主內(nèi)存)和輔助存儲器(如硬盤、固態(tài)硬盤等)。主內(nèi)存是計算機中直接與CPU交互的存儲器,用于存儲當(dāng)前正在執(zhí)行的程序和數(shù)據(jù)。輔助存儲器則用于長期存儲數(shù)據(jù),當(dāng)程序或數(shù)據(jù)不再需要時,可以將其保存在輔助存儲器中。(來源:chatgpt)
一. 什么是地址空間
虛擬地址與物理地址之間的關(guān)系
任何數(shù)據(jù),都需要加載到內(nèi)存中,都有各自的物理地址。如果我們進程A,B同時運行,相互獨立,萬一出現(xiàn)錯誤,將A將B中的數(shù)據(jù)讀取了,這是極不安全的,而如果在虛擬地址上先操作,再通過一定的機制,確保訪問的安全性。因此虛擬地址的存在,保護了物理地址上的數(shù)據(jù)安全。
地址空間的本質(zhì)是:一種內(nèi)核的數(shù)據(jù)結(jié)構(gòu),里面至少有各個區(qū)域的劃分。
二. 地址空間是如何設(shè)計的?
我們知道每個進程都有自己的PCB,同時task_struct里面有進程地址空間數(shù)據(jù)的存儲結(jié)構(gòu),也就是mm_struct。虛擬地址經(jīng)過頁表映射,指向物理地址。
?
?注意:每個進程中,不只是地址空間,頁表也有自己私有一份。
?這樣我們只要保證每個進程之間經(jīng)過頁表映射后的物理地址不同,即可保證進程之間互不干涉。
1. 回答一個變量兩個值
解釋:
父進程通過自己的task_struct中地址空間數(shù)據(jù)訪問到在物理空間上的數(shù)據(jù),子進程則是共享父進程的代碼,但當(dāng)子進程想對數(shù)據(jù)進行修改,為了保證進程之間的獨立性,系統(tǒng)決定進行寫時拷貝,為子進程拷貝一份數(shù)據(jù),同時修改子進程頁表映射數(shù)據(jù)。這樣就在表面上看就是相同變量,不同的值,但本質(zhì)上是相同的虛擬地址,不同的頁表,不同的物理地址,不同的值。
具體的就體現(xiàn)在id的兩次寫入:
2.擴展
?結(jié)論:在可執(zhí)行程序,編譯時內(nèi)部就有地址了
?我們在分析地址空間時,一直使用的是OS的視角,而不只是操作系統(tǒng)要遵守,編譯器也要遵守! 即在編譯器編譯代碼時,就已經(jīng)在內(nèi)部形成了地址,而且有各個區(qū)域:代碼區(qū),數(shù)據(jù)區(qū)...并且采用與linux內(nèi)核一樣的編址方式,每一個變量,每一行代碼都有其虛擬地址。
繼續(xù)深入理解
? ? 我們知道執(zhí)行進程是通過虛擬地址+頁表的方式來訪問物理地址的,那可執(zhí)行程序的虛擬地址和頁表的數(shù)據(jù)又是從那里來的呢?
流程分析?:
所以CPU在讀取到變量a時,根據(jù)虛擬地址0x100在代碼段中尋找,在代碼段查詢頁表后,再跳轉(zhuǎn)0x169地址找到數(shù)據(jù)a,CPU獲取其內(nèi)部跳轉(zhuǎn)虛擬地址0x123,再從虛擬地址中尋找,通過頁表訪問到函數(shù)func。(物理內(nèi)存位置隨便加載)
三. 為什么要有地址空間
原因:
1. 使操作系統(tǒng)對訪問或者映射的合法性檢查,殺掉非法進程,從而保護數(shù)據(jù)安全。?
例:假設(shè)char* man = "雞你太美";? *man = ‘寄’ , 我們在C語言期間就清楚字符常量無法修改,其原因來自底層頁表中會記錄代碼段地址,并且記錄其讀寫權(quán)限,操作系統(tǒng)檢測到非法操作后就會中斷結(jié)束進程,保護數(shù)據(jù)。(在物理內(nèi)存我們是可以任意修改的,所以頁表上的判斷確保了操作的合法性,保護了數(shù)據(jù)的安全)
2. ?使物理內(nèi)存分配與進程管理,通過頁表進行解耦,在加載時確定映射關(guān)系后,相互獨立
問:是否可以提前加載未來的數(shù)據(jù)到物理內(nèi)存中呢?
答:可以解析: 物理內(nèi)存的分配和進程管理,之間可以說沒有關(guān)系。這種關(guān)系叫做解耦合,那什么是強耦合呢?就是之間關(guān)系緊密,不容易分開,例如:你把函數(shù)內(nèi)容寫在main函數(shù)里面。
?
我們在學(xué)習(xí)C,C++時,在語言層面上new,malloc等內(nèi)存分配上的操作都是在對虛擬地址上的操作。那疑問來了
問:在進行虛擬地址分配的時候是否在物理內(nèi)存上分配資源?
答:不會,只在你訪問時,才申請物理內(nèi)存空間,通過這延遲分配的策略,提高整機效率。(這個操作僅操作系統(tǒng)自動完成,用戶,進程0感知)
3. 保證每個進程以統(tǒng)一的視角(有序的區(qū)域劃分)進行管理,完成進程獨立性的實現(xiàn)
頁表補充
以現(xiàn)在的視角看進程,加載內(nèi)存就好像是創(chuàng)建進程,那是否會加載全部的數(shù)據(jù)到內(nèi)存上呢?
我們在玩游戲時有的游戲動則40G,甚至是100G,我們的電腦是絕對裝不下的,因此內(nèi)存有一種內(nèi)存換下機制,一些數(shù)據(jù)在一定時間內(nèi)不再使用則被換下,加載新數(shù)據(jù),(說到這里要對頁表進行補充,頁表不只映射物理內(nèi)存,也映射硬盤地址)再次使用被換下的數(shù)據(jù)直接查詢頁表,用磁盤地址快速訪問加載到內(nèi)存中。
結(jié)語
? ?本小節(jié)就到這里了,感謝小伙伴的瀏覽,如果有什么建議,歡迎在評論區(qū)評論,如果給小伙伴帶來一些收獲請留下你的小贊,你的點贊和關(guān)注將會成為博主創(chuàng)作的動力。