往期精選
『內(nèi)存中的操作系統(tǒng)』內(nèi)存虛擬化又是什么
思維模型如何實(shí)現(xiàn)從虛擬地址到物理地址
在上文中, 我們講解了如何虛擬化內(nèi)存的大致思路, 并講解了在設(shè)計(jì)之前的目標(biāo)和準(zhǔn)則. 那么接下來(lái)我們就要嘗試進(jìn)行詳細(xì)設(shè)計(jì)我們的虛擬化內(nèi)存方式.
我們最容易想到的就是基于硬件來(lái)直接做轉(zhuǎn)換, 也就是硬件對(duì)每次內(nèi)存訪問(wèn)進(jìn)行處理(即指令獲取、數(shù)據(jù)讀取或?qū)懭耄?,將指令中的虛擬(virtual)地址轉(zhuǎn)換為數(shù)據(jù)實(shí)際存儲(chǔ)的物理(physical)地址. 硬件對(duì)每次內(nèi)存訪問(wèn)進(jìn)行處理(即指令獲取、數(shù)據(jù)讀取或?qū)懭耄?,將指令中的虛擬(virtual)地址轉(zhuǎn)換為數(shù)據(jù)實(shí)際存儲(chǔ)的物理(physical)地址.
這樣的話, 我們同時(shí)還需要操作系統(tǒng)來(lái)設(shè)置好硬件, 以完成正確的地址轉(zhuǎn)換, 同時(shí)需要管理內(nèi)存, 記錄已經(jīng)使用和空閑的內(nèi)存位置, 防止進(jìn)程對(duì)內(nèi)存的不合理使用.
1.1 地址轉(zhuǎn)換這個(gè)就是在20世紀(jì)50年代后期出現(xiàn)的虛擬內(nèi)存的設(shè)計(jì)思路, 為了實(shí)現(xiàn)上面的目標(biāo), CPU增加了兩個(gè)硬件寄存器:基址(base)寄存器和界限(bound)寄存器,有時(shí)稱為限制(limit)寄存器。這組基址和界限寄存器,讓我們能夠?qū)⒌刂房臻g放在物理內(nèi)存的任何位置,同時(shí)又能確保進(jìn)程只能訪問(wèn)自己的地址空間。即:
實(shí)際物理地址 = 虛擬地址 + 基址(base)
進(jìn)程中使用的內(nèi)存引用都是虛擬地址(virtual address),硬件接下來(lái)將虛擬地址加上基址寄存器中的內(nèi)容,得到物理地址(physical address),再發(fā)給內(nèi)存系統(tǒng)。
而界限寄存器則可以保證超過(guò)界限的內(nèi)存訪問(wèn)都會(huì)被阻止, 系統(tǒng)會(huì)報(bào)錯(cuò), 設(shè)置會(huì)終止進(jìn)程, 從而可以有效的防止對(duì)內(nèi)存的不合理使用.
這種基址寄存器配合界限寄存器的硬件結(jié)構(gòu)是芯片中的(每個(gè)CPU一對(duì))。有時(shí)我們將CPU的這個(gè)負(fù)責(zé)地址轉(zhuǎn)換的部分統(tǒng)稱為內(nèi)存管理單元(MemoryManagement Unit,MMU)。這個(gè)在我們現(xiàn)在的硬件中都是有的, 不過(guò)它擁有的比我們這里說(shuō)的更復(fù)雜的內(nèi)容.
1.2 分段那么上面的設(shè)計(jì)有沒(méi)有什么問(wèn)題呢? 答案其實(shí)很清晰, 那就是邏輯過(guò)于簡(jiǎn)單了, 雖然很多時(shí)候設(shè)計(jì)越簡(jiǎn)單越好, 但是前提是要滿足復(fù)雜的現(xiàn)實(shí)場(chǎng)景. 根據(jù)上面的設(shè)計(jì), 我們可以很容易畫出我們目前的內(nèi)存結(jié)構(gòu):

這里你可能已經(jīng)看到問(wèn)題在哪了, 那就是棧和堆之間,有一大塊“空閑”空間。如果我們將整個(gè)地址空間放入物理內(nèi)存,那么棧和堆之間的空間并沒(méi)有被進(jìn)程使用,卻依然占用了實(shí)際的物理內(nèi)存。因此,簡(jiǎn)單的通過(guò)基址寄存器和界限寄存器實(shí)現(xiàn)的虛擬內(nèi)存很浪費(fèi)。
另外,如果剩余物理內(nèi)存無(wú)法提供連續(xù)區(qū)域來(lái)放置完整的地址空間,進(jìn)程便無(wú)法運(yùn)行。這種基址加界限的方式看來(lái)并不像我們期望的那樣靈活。
所以只有基址寄存器和界限寄存器并不能滿足實(shí)際的內(nèi)存使用需求. 我們還需要更為復(fù)雜的設(shè)計(jì), 也就是分段(segmentation). 分段并不是一個(gè)新概念,它甚至可以追溯到20世紀(jì)60年代初期。這個(gè)想法很簡(jiǎn)單,在MMU中引入不止一個(gè)基址和界限寄存器對(duì),而是給地址空間內(nèi)的每個(gè)邏輯段(segment)一對(duì)。
一個(gè)段只是地址空間里的一個(gè)連續(xù)定長(zhǎng)的區(qū)域,在典型的地址空間里有 3 個(gè)邏輯不同的段:代碼、棧和堆。分段的機(jī)制使得操作系統(tǒng)能夠?qū)⒉煌亩畏诺讲煌奈锢韮?nèi)存區(qū)域,從而避免了虛擬地址空間中的未使用部分占用物理內(nèi)存。
那么根據(jù)我們的設(shè)計(jì), 內(nèi)存結(jié)構(gòu)可能就是如下的樣子:

從圖中可以看到,只有已用的內(nèi)存才在物理內(nèi)存中分配空間,因此可以容納巨大的地址空間,其中包含大量未使用的地址空間(有時(shí)又稱為稀疏地址空間,sparseaddress spaces)。你會(huì)想到,需要MMU中的硬件結(jié)構(gòu)來(lái)支持分段:在這種情況下,需要一組3對(duì)基址和界限寄存器。下面是一組示例:

根據(jù)我們的示例, 我們來(lái)看一個(gè)例子:
假設(shè)現(xiàn)在要引用虛擬地址100(在代碼段中),MMU將基址值加上偏移量(100)得到實(shí)際的物理地址:100 + 32KB = 32868。然后它會(huì)檢查該地址是否在界限內(nèi)(100小于2KB),發(fā)現(xiàn)是的,于是發(fā)起對(duì)物理地址32868的引用。
如果我們有學(xué)過(guò)Java虛擬機(jī)JVM的內(nèi)存結(jié)構(gòu), 就會(huì)發(fā)現(xiàn)其中的: 堆內(nèi)存, 方法區(qū)和棧的設(shè)計(jì)思路就是我們這里的設(shè)計(jì)思路.
有趣的知識(shí): segmentation fault
如果我們?cè)噲D訪問(wèn)非法的地址,例如7KB,它超出了堆的邊界呢?你可以想象發(fā)生的情況:硬件會(huì)發(fā)現(xiàn)該地址越界,因此陷入操作系統(tǒng),很可能導(dǎo)致終止出錯(cuò)進(jìn)程。這就是每個(gè)C程序員都感到恐慌的術(shù)語(yǔ)的來(lái)源:段異常(segmentationviolation)或段錯(cuò)誤(segmentation fault)。
來(lái)看一個(gè)堆中的地址,虛擬地址4200(同樣參考圖16.1)。如果用虛擬地址4200加上堆的基址(34KB),得到物理地址39016,這不是正確的地址。我們首先應(yīng)該先減去堆的偏移量,即該地址指的是這個(gè)段中的哪個(gè)字節(jié)。因?yàn)槎褟奶摂M地址4K(4096)開始,4200的偏移量實(shí)際上是4200減去4096,即104,然后用這個(gè)偏移量(104)加上基址寄存器中的物理地址(34KB),得到真正的物理地址34920。
總結(jié)到這里, 我們已經(jīng)有了虛擬化內(nèi)存的基本思路, 沿著這條思路, 讓我們?cè)诮酉聛?lái)的文章繼續(xù)細(xì)化我們的設(shè)計(jì). 來(lái)達(dá)到高效,靈活的效果.