物理記憶體虛擬記憶體以及段頁表

  • 2022 年 11 月 10 日
  • 筆記

物理記憶體(物理地址)

這個是我們大家最能理解的,就是實實在在存在的記憶體空間。我們對記憶體的訪問現在一般通過記憶體控制器。我們這裡先要能夠區別這裡的記憶體空間並不是如外掛儲存設備的nand/nor_flash這樣設備中的存儲空間,而是我們cpu直接能夠按地址訪問的空間。每一個位元組有唯一的內存地址。物理上的地址匯流排的條數,決定著我們能夠訪問的大小。這應該好理解吧,因為當我們訪問記憶體空間時,地址我們是通過地址匯流排一次傳輸過去的,比如我們有八位的地址匯流排,實際我們能夠給出的地址只能是0-255,那每一個位元組有唯一的地址,我們實際上也就最多只能訪問256Byte的空間。簡單來說,你可以把記憶體空間看作是一個大的網格,每個網格里裝著1Byte,然後每個格子一個挨著一個,並且有唯一的位置資訊。

 

虛擬記憶體(虛擬地址)

虛擬記憶體是針對物理記憶體的不足產生的,虛擬記憶體是物理記憶體的一種映射。要講虛擬記憶體,不如我們講講它為什麼會被引入,這樣自然就清晰明了啦。

物理記憶體不足

當我們運行很多程式時,我們是將我們的程式放在記憶體空間執行的。也就是從硬碟中存儲的可執行文件,把它通過執行器或者解釋器放在了我們的記憶體空間中,官方一點說這叫做裝載。假設一種情況,當運行的程式過大時,甚至大於我們的運行記憶體大小時,如果我們使用物理記憶體的方式,能不能實現將這段程式運行起來呢。是有可能可以的,因為運行程式不一定需要在同一時間全部載入到記憶體中去。比如2M大小的運行程式在1M大小的記憶體空間中,可能最開始程式的執行只依賴1M甚至不到1M的空間。我們完全可以只將即將運行和現在運行所必須的載入到記憶體中,待後面的內容需要運行時,我們再把後半部分的內容載入到記憶體中。那可能這跟虛擬記憶體又有什麼關係呢?實際上,我們就是從可執行程式的角度提出了這一個概念。對於可執行程式而言,它是感知不到我們這樣的操作的,在它的視角里,它跟擁有2M的記憶體空間是一樣的,因為它完完全全運行在了記憶體空間中。簡單來說,就好比一個小孩子(小明)需要人抱著,爸爸抱完,媽媽抱,但從小孩的視角來看,它只是知道它一直都有人抱著他而已。我們不想知道也不用程式去處理這樣的發生在硬碟與記憶體之間的替換和載入,於是我們乾脆給它說,你就是擁有2M的空間,所以我們引入了虛擬地址這個概念,程式使用我們的2M虛擬記憶體空間就可以了,它不用去關心這2M的虛擬地址是如何映射到我們的1M物理記憶體的,也不用知道何時發生替換的。(作業系統會處理好這件事情)

訪問許可權和保護

虛擬地址的引入帶來的好處遠不於此,而且還有其他場景下很重要的作用。我們通過剛剛上面看到了,通過虛擬地址的引入,對於程式而言我們實際上是不知道自己真實所訪問物理空間地址的,但有趣的是我們絕對且實實在在地肯定是發生了對某個物理記憶體空間的訪問的。所以這裡相當於在物理地址與虛擬地址抽象出了一層,也就是我們作業系統,它負責將程式所訪問的虛擬地址轉換為某個物理地址,然後取得想要的東西再返回給我們的程式。通常情況下是這樣的,但是當我們考慮一些特殊的時候,比如我們訪問了一些作業系統也不知道的虛擬地址時,或者我們訪問的虛擬地址所對應的物理地址已經被某段程式所使用時,麻煩就來了。如果我們直接使用物理地址,也無需作業系統,高度的自由就帶來不可估量的後果了。所以這裡虛擬地址,或者說被作業系統所妥善管理的虛擬地址就發揮作用了。作業系統清楚的知道它所給出的虛擬地址的情況,哪些是可被訪問的,哪些是非法訪問的,它就像一個檢查官一樣,它有權去拒絕或者轉移我們無理的訴求。當不合法的虛擬地址並未被作業系統接納並且翻譯成物理地址訪問時,我們只是想錯的人,並不是犯錯的人,因為我們的錯誤並未得到實施。所以這就是引入虛擬地址的好處,當然這個地方我想過,如果作業系統有一套針對物理地址的檢查機制呢,那是不是其實也能做到這一點呢,這裡我是這麼想的,大家可能有更好的見解。我們都知道每個進程的虛擬地址空間是可以相同的,兩個進程都可以訪問同一個虛擬地址而不衝突,不互相影響,因為作業系統會把兩個進程映射在兩塊不同的物理空間上。那比如我們同時運行50個進程,那當我們將程式從硬碟中載入到記憶體中時,有一個地址的重定位,即我們要確定程式載入到記憶體中時,各個部分的地址位置,虛擬地址的引入對這個的幫助是非常大的。因為我們不同進程在記憶體空間的虛擬地址分配可以是一致的。比如從哪裡開始,每部分的位置。而多任務,多進程又是作業系統引入的至關重要的原因和核心功能。

段表

段表,我談一點自己的理解,段表其實是對記憶體空間出於邏輯上的一個切分和管理。比如我們各個段,程式碼段,數據段,等等,這些劃分其實跟記憶體分布沒有任何的聯繫,它們的大小也不同,但是每個段都有各自所不同的屬性,這對於記憶體訪問是重要的。比如我們不希望改變我們程式碼段的東西,我們可以將其設置為只讀的。段表的管理,其實較為簡單,因為我們管理的段的數量是有限的。採用查詢段表的形式,我們的邏輯地址(段描述資訊+虛擬地址)中的前幾位,是查表的索引,也就是所謂的段選擇符,後面的位(一般後32位)為具體的段內偏移,通過段表+段選擇符,我們能夠找到該段的段描述符,通過段描述符我們能夠得到一些該段的基本資訊,其中就包括了段的訪問特權級,段的基地址(該段第一個位元組開始的位置),我們通過段基地址+偏移就可以得到該邏輯地址所對應的線性地址/虛擬地址。所謂的線性地址,再經過我們的頁表處理轉換後便能訪問到我們真實的物理地址,所以這裡也能看出來,我們的頁表主要的作用是把線性地址管理起來,從而使之正確安全地能夠訪問到物理地址。

頁表以及多級頁表

頁表管理除去訪問保護等功能外,最重要的作用就是完成線性地址到物理地址的轉換。最簡單的思路肯定是一個線性地址對應一個物理地址,就像一個簡單的一維數組,你給出索引,對應一個值。但是實際上,這個訪問關係會隨著數據量的擴張而變的誇張。因為我們地址的數太多了,比如32G,所以維護這樣的一個數據結構是不現實的。我們採用頁表,即我們把記憶體劃分的更大一點,4KB而不是原來的1B(記憶體中一個唯一地址對應1B數據)作為一個記憶體塊,也就是一頁,我們的大小就迅速縮小了4K倍,在這樣的方式下我們採用頁基地址+偏移的方式去找到某個具體的地址,這個地址存放著我們的該線性地址所對應的物理地址。這是個好思路而且也能看出前人是真的聰明啊,但是即使這樣,還是有很大的問題,32位的線性地址,一頁4K,那頁內偏移肯定需要12位,那還剩下20位就是我們所謂的頁號,也就是某一頁的索引。那也就是2的20次方個成員大小為4B(因為每一個索引對應著一個頁地址,地址都是32位(默認是這樣的情況,有些系統肯定也不只32位,但是不影響我們分析這個問題)也就是4B的嘛)的數組,那也就是4M的數據。我們每一個進程都是需要有自己的一個頁表。(因為不同的進程實際是運行在不同的物理地址的,仔細想想你就會明白不可能多個進程共用一個頁表)通常情況下,在系統中運行的進程數量是很多的,所以這樣的方式也並不是很可行,當我們有200個進程時,我們需要花800M的記憶體空間去維護一個這樣的東西西,amazing……)。於是有了多級頁表,多級頁表,即我們需要再多一次的訪問才能取得頁地址,即我們把二十位拆分為兩個十位,第一個十位作為頁表目錄號,我們以它作為索引,建立一個目錄表,它的每一項指向一個頁表的基地址,第二個十位作為頁號,以它作為索引建立一個頁表,指明該頁在該頁表中的偏移,用來找到頁表中具體的頁基地址。我個人感覺有點把一維數組變成二維數組的感覺。但是這裡我們發現我們從維護一個4M的一維數據結構,變成兩個4k(2的十次乘以4B)一維的數據結構,這樣的方式,雖然多了一次訪存查表,但是使我們整體的思路變的可行了,我們一個進程若需要維護8K的數據結構,那1000個進程這樣的情況下,所佔用的記憶體資源也就是最早方案1個進程運行的兩倍而已。(單獨從地址轉換這個角度來講)。這裡有一些時間換空間的感覺,但是想想還是很划算的。

TLB

這裡我一直對它的概念就是像Cache,不過他加速或者優化的不是訪存過程,而是查表。這裡詳細點說,我們cpu訪問物理地址之前,需要先得到物理地址,而物理地址的獲得就是我們這所有討論的目的,它就安靜地躺在某一頁的某一個偏移的位置處。TLB則是快取了我們最近訪問的虛擬頁對應的物理頁的位置,當我們訪問某個線性地址/虛擬地址時,我們會先對比我們所要訪問的虛擬地址與TLB所快取的虛擬地址的某些位是否一致,若一致則命中,就能直接從TLB中拿到該物理頁地址,再加上偏移我們就能直接得到所要的物理地址,而無需訪存,反之,未命中時,cpu也會從記憶體中的得到具體的物理地址,更新TLB表項。這裡我們不做深入分析,我們就從訪存的角度來看一下整個過程,首先CPU用的是虛擬地址,MMU需要把虛擬地址轉換為物理地址,這裡會如果採用單頁表,也就是我們提到的類似於一維數組的情況,會發生一次訪存(原因是該頁表在記憶體中,而且我認為這個頁表的物理地址我們是知道的而且必須是物理地址,不然我們怎麼找到這個表的地址(那不就俄羅斯套娃了嘛),果然X86的CR3暫存器存的就是這個東西),如果我們採用多級頁表,那就是上面我們提到 的兩個小的表,那就是兩次訪存,如果TLB命中了那就是沒有訪存發生。當我們拿到了物理地址後,我們才會對物理記憶體進行訪問,這裡也是一次訪存,這個地方又會引入Cache進行優化,類似於TLB查表,它會檢查該物理地址是否最近訪問過,然後………。

 

總結:

感覺還是很有收穫,自己想著自己去做這些時的思路,再去參考現在這些系統實現的思路發現豁然開朗,也不禁感嘆前人的智慧,但是更多的是覺得,當我們面臨這些難題時,我們能不能自己先去想我們會怎麼去解決呢,比如頁表,多級頁表,TLB,Cache,它們每一個的引入都是解決了我們電腦運行時的一個又一個問題。轉變思路,接受理解別人的想法是很難的事情。因為自己都沒有首先考慮過自己面臨這些問題時自己的分析和答案。如果我們能自己先去思考,再去看他人,必定才能站的更高,看得更遠!