來(lái)源:入門小站
1 代碼感受
在正式講程序[地址空間]前我們先來(lái)看一段簡(jiǎn)單的代碼來(lái)分析分析:
?
??1?#include??2?#include ??3?using?namespace?std; ??4? ??5?int?g_val=100; ??6? ??7?int?main() ??8?{ ??9???pid_t?id=fork(); ?10???if(id==0) ?11???{ ?12?????//child ?13????while(true) ?14????{ ?15?????cout<<"我是一個(gè)子進(jìn)程,我的pid是:"< ?
大家可以自己先分析一下結(jié)果。
我們來(lái)運(yùn)行一下結(jié)果:
大家看前面幾行可能就會(huì)立馬發(fā)現(xiàn)問題:我們定義的 g_val 是全局變量,當(dāng)子進(jìn)程修改 g_val 的值時(shí)我們發(fā)現(xiàn)父進(jìn)程的 g_val 是不受影響的,那么說明父子進(jìn)程所用的 g_val 并不是同一個(gè)變量(這個(gè)很好理解,之前的我們說過父子進(jìn)程是相互獨(dú)立的,互相不受干擾的),但是問題出現(xiàn)在最后一列,我們驚奇的發(fā)現(xiàn)居然父子進(jìn)程的 g_val 變量的地址居然是相同的,前面不是說父子進(jìn)程的 g_val 不是同一個(gè)變量嗎?這里為啥打印出來(lái)的地址會(huì)是相同的呢?
這里就說明我們打印出來(lái)的地址并不是真正的[物理地址],我們語(yǔ)言層面打印出的地址叫做虛擬地址或者線性地址。我們?cè)谟肅/C++語(yǔ)言所看到的地址,全部都是虛擬地址!而物理地址,用戶一概看不到,由OS統(tǒng)一管理 。OS必須負(fù)責(zé)將虛擬地址轉(zhuǎn)化成物理地址。
2 進(jìn)程地址空間
首先我們來(lái)講一個(gè)故事:從前有一個(gè)企業(yè)家很有錢,他的家產(chǎn)大概有一億美金左右的樣子。他有 4 個(gè)私生子,并且這四個(gè)私生子互相并不知道對(duì)方的存在。第一個(gè)私生子是個(gè)學(xué)霸,在國(guó)內(nèi)頂尖學(xué)校上學(xué),這個(gè)富豪便對(duì)他說,你要好好讀書,將來(lái)我這一億美金全部都是你的;第二個(gè)私生子是一個(gè)三線演員,富豪便對(duì)他說,我?guī)湍愦蜷_你紅的渠道,你不要辜負(fù)了我對(duì)你的期望,好好努力,將來(lái)我這一億美金都是你的;第 3 個(gè)私生子是個(gè)女兒,當(dāng)?shù)氖切W(xué)老師,富豪便對(duì)他說,你也不用太過努力工作,我就你這一個(gè)女兒,等我老了這一億美金就是你的了;第四個(gè)私生子是一個(gè)初中的小混混,富豪對(duì)他說,你只要好好聽我的話,這一億美金就是你的了。
富豪給每個(gè)私生子都做出了承諾要將一億美金給他們,但是實(shí)際富豪并沒有那么多的錢給每個(gè)私生子一億美金,而這一億美金就是富豪給私生子們畫的一張大餅,但是它的私生子們卻信以為真。
?
那這個(gè)故事與我們講的知識(shí)有什么關(guān)系呢?其實(shí)操作系統(tǒng)就是那個(gè)富豪,私生子們就是一個(gè)一個(gè)的進(jìn)程,而那一億美金就是進(jìn)程地址空間。
PS: 我們?cè)谏钪幸M量少畫餅。
?
操作系統(tǒng)給進(jìn)程畫了一張大餅,操作系統(tǒng)的資源是有限的,所以他就得要好好的把這張餅給管理起來(lái),不讓這些進(jìn)程亂來(lái),而如何管理呢?
那就要先描述,再組織,Linux 中用的是一種叫做 mm_struct 的內(nèi)核數(shù)據(jù)結(jié)構(gòu)來(lái)管理的。
我們來(lái)用一張圖帶大家來(lái)看看程序地址空間:
這張圖相信大家多多少少也不會(huì)陌生,在 C 語(yǔ)言的學(xué)習(xí)中我們也見到了很多次。
那么程序地址空間如何編碼的呢?(32 位的平臺(tái)下[虛擬地址]空間大概是 4GB)
ps: 下面圖每個(gè)小空格代表著一個(gè)字節(jié)。
所以從這里我們也不難看出為啥虛擬地址也叫做線性地址。那么我們究竟是如何管理虛擬地址空間的每個(gè)區(qū)的呢?
我們可以用下面這種方式來(lái)描述管理:
?
struct?mm_struct { long?code?_start; long?code?_end; long?init?_start; long?init?_end; ………… long?brk?_start; long?brk?_end; long?stack?_start; long?stack?_end; }?
而_start 和_end 限定的區(qū)域就是叫做虛擬地址(線性地址)
那么問題來(lái)了,既然上面我們講了那么多虛擬地址,真正的物理地址又在哪里呢?
我們畫一個(gè)圖方便大家理解:
通過這張圖大家并不難發(fā)現(xiàn),我們?cè)谡Z(yǔ)言層面上的地址是地址空間的虛擬地址,而虛擬地址要與物理地址建立映射,就需要一張頁(yè)表(頁(yè)表的工作原理我們將放到后面來(lái)講)。
我們?cè)趯W(xué)習(xí) C 語(yǔ)言時(shí)大家在書上看到這樣的一句代碼:const char* str="hello world";
這時(shí)書上會(huì)告訴大家這句 str 指向的內(nèi)容是只讀的,不可修改的,但是這時(shí)為什么呢?這時(shí)我們就可以自己來(lái)分析分析:str 指向的內(nèi)容是在常量字符區(qū),當(dāng)常量字符區(qū)通過頁(yè)表與物理地址建立映射時(shí)在頁(yè)表中就將該數(shù)據(jù)設(shè)置為只讀,當(dāng)我們后續(xù)有修改操作時(shí)就會(huì)直接報(bào)錯(cuò)。
有了上面的基礎(chǔ)我們就可以來(lái)解釋解釋為啥開頭我們的 g_val 是同一個(gè)地址,但是指向的內(nèi)容卻不相同的問題了:
當(dāng)不修改數(shù)據(jù)時(shí)就不會(huì)發(fā)生寫時(shí)拷貝,父子進(jìn)程指向的是同一塊物理空間 (為了節(jié)約資源);當(dāng)要修改數(shù)據(jù)時(shí)就會(huì)發(fā)生寫時(shí)拷貝,父子進(jìn)程指向的是不同的物理空間,但是虛擬地址空間是相等的。
我們?cè)賮?lái)回答為啥 fork 會(huì)有兩個(gè)返回值的問題就很容易了,就是因?yàn)楦缸舆M(jìn)程的返回值是不同的,所以肯定會(huì)發(fā)生寫時(shí)拷貝將不同的返回值用相同的虛擬地址來(lái)進(jìn)行返回,雖然虛擬地址是相同的,但是他們通過頁(yè)表建立映射的關(guān)系卻是不一樣的。
到目前為止,程序地址空間的基本內(nèi)容已經(jīng) ok, 接下來(lái)給出一些擴(kuò)展。
3 擴(kuò)展
首先引出一個(gè)問題:假如沒有程序地址空間,OS 是如何工作的?
我們知道如果沒有了地址空間,那么 cpu 將直接跟物理地址打交道,這樣做的后果是什么?
我們不難知道假如 cpu 直接跟物理地址打交道的話那么當(dāng)我們從 cpu 中讀到非法地址時(shí)那就壞了,通過非法地址將我們程序中其他變量的值給修改了那不就扯淡了嗎。所以我們要通過一層屏障來(lái)保護(hù)數(shù)據(jù),而這一層保護(hù)就是通過程序地址空間來(lái)進(jìn)行的,當(dāng)我們?cè)L問的數(shù)據(jù)非法時(shí)通過頁(yè)表的映射就會(huì)拒絕你的非法操作。
所以我們得出了程序地址空間的第一個(gè)好處:防止地址隨意訪問,保護(hù)物理內(nèi)存和其他進(jìn)程。
在向大家提出一個(gè)小問題:當(dāng)我們?cè)诙焉?new 空間時(shí) OS 是立馬就把空間給你,還是等你需要的時(shí)候再給你?
這個(gè)問題大家應(yīng)該都能夠答對(duì),與我們想得一樣,OS 會(huì)在我們需要該空間的時(shí)候再去在堆上申請(qǐng)。
而頁(yè)表暫時(shí)沒有與物理內(nèi)存建立映射關(guān)系稱作頁(yè)表中斷,當(dāng)我們需要空間的時(shí)候再與 · 物理內(nèi)存建立映射。大家從這張圖看出來(lái)沒有,當(dāng)我們通過頁(yè)表建立映射時(shí)將進(jìn)程管理與內(nèi)存管理給解耦合了。我進(jìn)程管理不需要關(guān)心你是怎樣在內(nèi)存上申請(qǐng)空間的,內(nèi)存管理也不需要關(guān)心進(jìn)程是如何管理起來(lái)的,這樣下來(lái)維護(hù)成本就會(huì)變得更低,維護(hù)效率會(huì)更加高效一些。
所以我們得出了程序地址空間的第二個(gè)好處:將進(jìn)程管理與內(nèi)存管理進(jìn)行解耦合。
再提出一個(gè)問題:程序在被編譯的時(shí)候沒有被加載到內(nèi)存,那么程序內(nèi)有沒有地址呢?
答案是有的。源代碼再被編譯的時(shí)候就是按照虛擬地址空間的方式將對(duì)應(yīng)的代碼和數(shù)據(jù)進(jìn)行編制,編譯器也會(huì)遵守虛擬地址的規(guī)則。
當(dāng)我們把程序加載到內(nèi)存,程序里保存的地址(虛擬地址,并不是程序本身在內(nèi)存中的物理地址) 就會(huì)被 cpu 讀取, cpu 通過虛擬地址找到對(duì)應(yīng)的虛擬地址空間,然后虛擬地址空間又通過頁(yè)表映射到物理內(nèi)存中,這樣就將程序的整個(gè)運(yùn)轉(zhuǎn)給聯(lián)系起來(lái)了。
所以我們得出了程序地址空間的第三個(gè)好處:可以讓進(jìn)程以統(tǒng)一的視角看待自己的代碼和數(shù)據(jù)。
審核編輯:湯梓紅
評(píng)論