一、引入
對(duì)于C/C++程序,我們眼中的內(nèi)存是這樣的:
我們利用這種對(duì)于與內(nèi)存的理解看一下下面這段代碼:
運(yùn)行結(jié)果:
觀察父子進(jìn)程中 val 變量的值,以及 val 的地址,我們發(fā)現(xiàn)父子進(jìn)程中 val 的地址都是同一個(gè)地址 但是 val 的值并不相同,這是什么意思????jī)?nèi)存中同一個(gè)地址卻存放了兩個(gè)不同的變量值?這顯然是不可能的!地址具有唯一性,地址處存放的數(shù)據(jù)也具有唯一性!
一種合理的解釋是:我們?cè)贑程序中所用到的地址的一個(gè)虛擬地址,并不是真正的物理內(nèi)存地址!
在C/C++程序中我們所使用到的地址都是虛擬地址,這些虛擬地址組合起來(lái)就形成了虛擬地址空間,在Linux中虛擬地址空間被一個(gè)叫struct mm_struct
的結(jié)構(gòu)體所管理。
那么我們想知道這個(gè)程序的虛擬地址空間到底虛擬了多大的內(nèi)存呢?
答案是:每個(gè)進(jìn)程所能訪問(wèn)的最大的虛擬地址空間由計(jì)算機(jī)的硬件平臺(tái)決定,具體地說(shuō)是由 CPU 的位數(shù)決定的。
比如 32 位的 CPU 決定了虛擬地址空間的大小為 [ 0 , 2 32 ? 1 ] [0 ,2^{32}-1] [0,232?1],即 0x00000000 - 0xFFFFFFFF,也就是我們常說(shuō)的 4 GB 虛擬內(nèi)存空間。如果是 64 位的CPU,那么尋址范圍是 [ 0 , 2 64 ? 1 ] [0 ,2^{64}-1] [0,264?1],即 0x0000000000000000 - 0xFFFFFFFFFFFFFFFF,共有 17 179 864 184 GB。
所以開(kāi)頭我們看到的內(nèi)存其實(shí)是操作系統(tǒng)為我們程序虛擬的虛擬地址空間,這個(gè)虛擬地址空間的分配如下:
二、虛擬地址與物理內(nèi)存的聯(lián)系
經(jīng)過(guò)上面的介紹我們知道了原來(lái)我們認(rèn)為內(nèi)存其實(shí)是虛擬地址空間,但是我們的代碼與數(shù)據(jù)是要真真實(shí)實(shí)的存儲(chǔ)在物理內(nèi)存中,虛擬地址空間里面存放的僅僅是一些代碼和數(shù)據(jù)的虛擬地址,那么我們是怎么通過(guò)虛擬地址來(lái)找到物理內(nèi)存中的代碼與數(shù)據(jù)呢?
答案是:通過(guò)一種數(shù)據(jù)結(jié)構(gòu)——頁(yè)表和一種硬件——MMU(內(nèi)存管理單元),通過(guò)頁(yè)表來(lái)進(jìn)行虛擬地址與物理地址一 一對(duì)應(yīng)從而找到相應(yīng)的代碼與數(shù)據(jù)
明白了這些后我們?cè)賮?lái)看開(kāi)頭的問(wèn)題,為什么同一個(gè)地址存放的是不同的數(shù)據(jù)?
在父進(jìn)程剛開(kāi)始創(chuàng)建子進(jìn)程時(shí),子進(jìn)程是的大多數(shù)數(shù)據(jù)(如:task_struct , 虛擬程地址空間等數(shù)據(jù))都是以父進(jìn)程為模板創(chuàng)建而來(lái)的,因此在最初時(shí)父進(jìn)程與子進(jìn)程的虛擬地址空間和頁(yè)表是相同的,然后父進(jìn)程嘗試去修改變量 val 的值,由于進(jìn)程具有獨(dú)立性,操作系統(tǒng)不能讓父進(jìn)程的修改影響到子進(jìn)程,于是發(fā)生了寫(xiě)時(shí)拷貝,操作系統(tǒng)先在物理內(nèi)存中重新找一塊空間保存了父進(jìn)程修改后的 val 值,然后將父進(jìn)程頁(yè)表中對(duì)應(yīng)物理地址進(jìn)行更改。
注意:虛擬地址不更改,只改變物理地址!因?yàn)槲锢淼刂穼?shí)實(shí)在在的變化了,虛擬地址沒(méi)有必要更改。
三、為什么要有虛擬地址空間
可能你會(huì)覺(jué)得為什么要有虛擬地址空間呢?我們的進(jìn)程為什么不直接使用物理地址呢?直接使用物理地址還能減少中間層提高運(yùn)行的效率。
要回答這個(gè)問(wèn)題我們可以從下面幾個(gè)的角度來(lái)回答:
- 防止地址隨意訪問(wèn),保護(hù)物理內(nèi)存與其他進(jìn)程
如果我們沒(méi)有虛擬地址空間,當(dāng)我們?cè)趦?nèi)存中運(yùn)行兩個(gè)程序時(shí),如果其中在一個(gè)進(jìn)程中發(fā)生了越界訪問(wèn),那么就有可能訪問(wèn)到其他的進(jìn)程,這樣進(jìn)程之間就會(huì)互相影響了,進(jìn)程的獨(dú)立性就無(wú)法保證了
有了虛擬地址空間以后我們便可以通過(guò)頁(yè)表來(lái)進(jìn)行判斷越界后的地址與接下來(lái)的操作是否統(tǒng)一的,如果是統(tǒng)一的便進(jìn)行映射。
-
將進(jìn)程管理和內(nèi)存管理進(jìn)行解耦合!
這里我們先來(lái)談一談malloc
的本質(zhì),malloc
函數(shù)是在調(diào)用后向OS申請(qǐng)內(nèi)存,操作系統(tǒng)立馬給你,還是需要的時(shí)候在給你呢 ?
答案是:在你需要的時(shí)候給你?。?!
那么為什么會(huì)是這樣的呢?因?yàn)椴僮飨到y(tǒng)要管理好所有的軟硬件資源,OS一般不允許任何的浪費(fèi)或者不高效的行為出現(xiàn)!
我們?cè)谏暾?qǐng)完內(nèi)存以后并不一定立馬使用,在你申請(qǐng)成功之后,和你使用之前,就以一段小小的時(shí)間窗口,這個(gè)空間沒(méi)有被正常使用,但是別人用不了,于是這塊空間就處于了閑置狀態(tài)! 這是OS不允許的!
于是我們?cè)谟?code>malloc申請(qǐng)空間時(shí)OS其實(shí)是先通過(guò)task_struct
找到虛擬地址空間和管理虛擬地址空間的mm_struct
結(jié)構(gòu)體,然后對(duì)于虛擬地址空間的內(nèi)容進(jìn)行修改并將mm_struct
里關(guān)于堆區(qū)的范圍進(jìn)行修改,然后頁(yè)表中關(guān)于虛擬地址的部分的內(nèi)容會(huì)進(jìn)行增加,但是新增的虛擬地址去沒(méi)有與實(shí)際的物理地址建立映射關(guān)系,只有當(dāng)你要使用你申請(qǐng)的空間時(shí),OS才會(huì)真正的為你分配空間,并將頁(yè)表中新增的虛擬地址對(duì)應(yīng)的物理地址的映射關(guān)系建立起來(lái)!這時(shí)OS才真正的完成了內(nèi)存分配!
這個(gè)時(shí)候,我們將頁(yè)表從中間一分為二,左邊就是進(jìn)程管理,右邊就是內(nèi)存管理,進(jìn)程管理發(fā)生錯(cuò)誤不影響內(nèi)存管理,內(nèi)存管理出現(xiàn)錯(cuò)誤不影響進(jìn)程管理,這樣我們就實(shí)現(xiàn)了進(jìn)程與內(nèi)存管理的解耦合!
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-404697.html
-
可以讓進(jìn)程以統(tǒng)一的視角,看待自己的代碼和數(shù)據(jù)!
在這里我們來(lái)討論一下我們的程序在被編譯的時(shí)候,沒(méi)有被加載到內(nèi)存,我們的程序內(nèi)部有沒(méi)有地址呢?
答案是:有的!
我們的C/C++程序在被編譯時(shí)就采用了虛擬地址空間的方式進(jìn)行編譯,并將數(shù)據(jù)按照代碼段,已初始化數(shù)據(jù)段,未初始化數(shù)據(jù)段等方式進(jìn)行分類存儲(chǔ)。當(dāng)我們的程序加載進(jìn)內(nèi)存時(shí),我們的程序可以分批式的將程序的數(shù)據(jù)段,代碼段,分批的加載進(jìn)地址空間中。
注意:編譯好的程序并沒(méi)有堆區(qū)和棧區(qū),只有加載進(jìn)內(nèi)存時(shí)形成進(jìn)程時(shí)才有堆區(qū)與棧區(qū)!
有了地址空間之后我們的進(jìn)程便不再關(guān)心代碼與數(shù)據(jù)究竟在內(nèi)存中的哪里,我們每個(gè)進(jìn)程都是以統(tǒng)一的視角——虛擬地址空間的方式來(lái)待自己的代碼與數(shù)據(jù)。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-404697.html
到了這里,關(guān)于【Linux】虛擬地址空間的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!