80386學習(五) 80386分頁機制與虛擬內存

一. 頁式內存管理介紹

  80386可以將內存分爲不一樣屬性的段,並經過段描述符、段表以及段選擇子等機制,經過段基址和段內偏移量計算出線性地址進行訪問,這一內存管理方式被稱爲段式內存管理算法

  這裏要介紹的是另外一種內存管理的方式:80386在開啓了分頁機制後,便可以將物理內存劃分爲一個個大小相同且連續的物理內存頁,訪問時經過物理內存頁號和頁內偏移計算出最終須要訪問的線性地址進行訪問,因爲內存管理單元由段變成了頁,所以這一內存管理方式被稱爲頁式內存管理編程

  80386的分頁機制只能在保護模式下開啓。緩存

爲何須要頁式內存管理?

  在介紹80386分頁機制前,須要先理解爲何CPU在管理內存時,要在段式內存管理的基礎上再引入一種有很大差別的頁式內存管理方式?頁式內存管理與純段式內存管理相比到底具備哪些優勢?數據結構

  一個很重要的緣由是爲了解決多任務環境下,段式內存管理中多任務的建立與終止時會產生較多內存碎片,使得內存空間使用率不高的問題ide

  內存碎片分爲外碎片和內碎片兩種。學習

外碎片

  對於指令和數據的訪問一般都是連續的,因此須要爲一個任務分配連續的內存空間。在段式內存管理中,一般爲任務分配一個完整的內存段,或是按照任務內段功能的不一樣,分配包括代碼段、數據段和堆棧段在內的多個完整連續段空間。支持多道任務的系統分配的內存空間,會在某些任務退出並釋放內存時,產生外部內存碎片。優化

  舉個例子,假設當前存在10MB的內存空間,存在A/B/C/D四個任務,併爲每一個任務分配一整塊的內存空間,其所佔用的內存空間分別爲3MB/2MB/4MB/1MB,以下圖所示(一個格子表明1MB內存)。spa

  當任務B和任務D執行完成後,所佔用的內存空間被釋放,10MB的內存空間中出現了3MB大小的空閒內存。若是此時出現了一個任務E,須要爲其分配3MB的內存空間,此時內存雖然存在3MB的內存空間,卻因爲空閒內存的不連續,碎片化,致使沒法直接分配給任務E使用。而這裏任務B、任務D結束後釋放的空餘內存空間就被視爲外碎片。操作系統

  這裏的例子任務數量少且內存空間也很小。而在實際的32位甚至64位的系統中,物理內存空間少則4GB,多則幾十甚至上百GB,因爲任務內存的反覆分配和釋放,致使出現的外碎片的數量及浪費的內存空間會不少,很大程度上下降了內存空間的利用率。設計

  雖然理論上可以經過操做系統當心翼翼的挪動內存,使得外碎片可以拼接爲連續的大塊,得以被有效利用(內存緊縮)。可是操做系統挪動、複製內存自己很佔用CPU資源,且存在對指令進行地址重定位、暫時暫停對所挪動內存區域的訪問等附加問題,形成的效率下降程度幾乎是不可忍受的,所以這一解決方案並無被普遍使用。

  

內碎片

  外碎片指的是不一樣任務內存之間的碎片,而內碎片指的是一個任務內產生的內存碎片。

  一般操做系統爲了管理多任務環境下的物理內存,會將內存分隔爲固定大小的分區,使用系統表記錄對應分區內存的使用狀況(如是否已分配等)。分區的大小必須適當,若是分區太小,則相同物理內存大小下,系統表項過多使得所佔用的空間過大;可若是分區過大,則會產生過大的內碎片,形成沒必要要的內存空間浪費。

  以上述介紹外碎片的數據爲例,系統中的內存分區固定大小爲1MB,其中爲任務C分配了4個內存分區,共4MB大小。可實際上任務C實際只須要3.5MB的空間便可知足需求,但因爲分區是內存管理的最小單元,只能爲任務分配整數個的內存分區。3個分區3MB並不知足任務C的3.5MB的內存需求,所以只能分配4個分區給任務C。而這裏任務C額外多佔用的0.5MB內存就是內碎片。 

  內碎片就是已經被分配出去,卻不能被有效利用的內存空間。

80386是如何解決內存碎片問題的?

外碎片的解決

  外碎片問題產生的主要緣由是程序所須要分配的內存空間是連續的。爲此,80386提供了分頁機制,使得最終分配給任務的物理內存空間能夠不連續。若是任務所使用的內存沒必要連續,前面外碎片例子中提到的任務E就可以在1MB+2MB的離散物理內存上正常運行,外碎片問題天然就獲得瞭解決。

內碎片的解決

  內碎片從本質上來講是很難徹底避免的(內存管理最小單元不能太小),主要的問題在於前面提到的內存分區管理單元大小的較優值很差肯定。開啓了分頁管理的80386,容許將物理內存分割最小爲4KB固定大小的管理單元,這個固定大小的內存管理單元被稱爲頁,並由專門的被稱爲頁表的數據結構來追蹤內存頁的使用狀況。

  對於頁表項過多的問題,80386的設計者提供了多級頁表機制,減小了頁表所佔用的空間。

  對於內碎片過大的問題,因爲80386所運行的任務所佔用的內存段通常遠大於一個內存頁的大小,所以頁機制下所產生的內部碎片是十分有限的,能夠達到一個使人滿意的內存使用率。

二. 虛擬內存簡單介紹 

  爲了解決應用程序高速增加的內存需求與物理內存增長緩慢的矛盾,計算機科學家們提供了虛擬內存的概念。使用了虛擬內存的系統,可使得系統內運行的程序所佔用的內存空間總量,遠大於實際物理內存的容量。

  可以實現虛擬內存的關鍵在於程序在特定時刻所須要訪問的內存地址是符合局部性原理的。經過操做系統和硬件的緊密配合,可以將任務暫時不須要訪問的內存交換到外部硬盤中,而將物理內存留給真正須要訪問的那部份內存(工做集內存)。

  虛擬內存和分頁機制是一對好搭檔,分頁機制提供了管理內存的基本單位:頁,80386的頁式虛擬內存實如今工做集內存調度時也依賴分頁機制提供的頁來進行。隨着程序的執行,程序的工做集內存在動態變化,當CPU檢測到當前所訪問的內存頁不在物理內存中時,便會通知操做系統(內存缺頁異常),操做系統的缺頁異常處理程序會將硬盤交換區中的對應內存頁數據寫回物理內存。若是物理內存頁已經滿了的狀況下,則還須要根據某種算法將另外一個物理內存頁替換,來容納這一換入的內存頁。

三. 80386分頁機制原理

  在介紹分頁機制原理以前,須要先理解關於80386保護模式下32位內存尋址時幾種地址的概念。

物理地址(Physical Address):

  物理地址就是32位的地址總線所對應的真實的硬件存儲空間。對於物理內存的訪問,不管中間會通過多少次轉換,最終必須轉換爲最終的物理地址進行訪問。

邏輯地址(Logical Address):

  在80386保護模式的程序指令中,對內存的訪問是由段選擇子和段內偏移決定的。段選擇子+段內偏移 --> 邏輯地址。

線性地址(Linear Address):

  CPU在內存尋址時,從指令中得到段選擇子和段內偏移,即邏輯地址。由段選擇子在段表(GDT或LDT)中找到對應的段描述符,獲取段基址。段基址+段內偏移決定線性地址。

  若是沒有開啓分頁,CPU就使用生成的線性地址直接做爲最終的物理地址進行訪問;若是開啓了分頁,則還須要經過頁表等機制,將線性地址進一步處理才能生成物理地址進行訪問。

頁式虛擬內存實現原理

  程序要求訪問一個段時,其線性地址必須是連續的。在純粹的段式內存管理中,線性地址等於物理地址的狀況下,就會出現外碎片的問題。而在段式內存管理的基礎上,80386若是還開啓了頁機制,就能經過抽象出一層線性地址到物理地址的映射,使得最終分配給程序的物理內存段沒必要連續。

  80386中的內存頁大小爲4KB,在32位的內存尋址空間中(4GB),存在着0x10000 = 1048576個頁。每一個頁對應的起始地址低12位都爲0,第一個物理內存頁的物理地址爲0x00000000,第二個物理內存頁的物理地址爲0x00001000,依此類推,最後一個物理頁的物理地址是0xFFFFF000。

頁表

  在80386的分頁機制的實現中,是經過頁表來實現線性地址到物理地址映射轉換的。每一個任務都有一個本身的頁表記錄着任務的線性地址到物理地址的映射關係。

  開啓了頁機制後的線性地址也被稱爲虛擬地址,這是由於線性地址已經再也不直接對應真實的物理地址,而是一個不承載真實數據的虛擬內存地址。開啓了分頁機制後,一個任務的虛擬地址空間依然是連續的,但所佔用的物理地址空間卻能夠不連續

  頁表保存着被稱爲頁表項的數據結構集合,每個頁表項都記載着一個虛擬內存頁到物理內存頁的映射關係。開啓了頁機制以後,CPU在內存尋址時,在經過段表計算出了線性地址(虛擬地址)後,即可以在連續排布的虛擬地址空間中找到對應的頁表項,經過頁表項獲取虛擬內存頁所對應的物理內存頁地址,進行物理內存的訪問。虛擬地址到物理地址映射的細節會在後面進行展開。

  因爲是將不斷變化的虛擬內存頁裝載進相對不變的物理內存頁中,就像畫廊中展現的畫會不斷的更替,但畫框基本不變同樣。爲了更好的區分這二者,頁一般特指虛擬內存頁,而物理內存頁則被稱爲頁框。

頁表項介紹

  頁表項是32位的,其結構以下圖所示。

  

P位:

  P(Present),存在位。標識當前虛擬內存頁是否存在於物理內存頁中。當P位爲1時,表示當前虛擬內存頁存在於物理內存中,能夠直接進行訪問。當P位爲0時,表示對應的物理內存頁不存在,須要新分配物理內存頁或是從磁盤中將其調度回物理內存。

  分頁模式下的內存尋址,若是CPU發現對應的頁表項P位爲0,會引起缺頁異常中斷,操做系統在缺頁異常處理程序中進行對應的處理,以實現虛擬內存。

RW位:

  RW(Read/Write)位,讀寫位。標識當前頁是否可以寫入。當RW爲1時,表明當前頁可讀可寫;當RW爲0時,表明當前頁是隻讀的。

US位:

  US(User/Supervisor)位,用戶/管理位。當US爲1時,標識當前頁是用戶級別的,容許全部當前特權級的任務進行訪問。當US爲0時,表示當前頁是屬於管理員級別的,只容許當前特權級爲0、一、2的任務進行訪問,而當前特權級爲3的用戶態任務沒法進行訪問。

PWT位/PCD位:

  PWT(Page-level Write Through)位,頁級通寫位。PWT爲1時,表示當前物理頁的高速緩存採用通寫法;PWT爲0時,表示當前物理頁的高速緩存採用回寫法。

  PCD(Page-level Cache Disable)位,頁級高速緩存禁止位。PCD爲1時,表示訪問當前物理頁禁用高速緩存;PCD爲0時,表示訪問當前物理頁時容許使用高速緩存。

  PWT與PCD位的使用,涉及到了80386高速緩存的工做原理與內存一致性問題,限於篇幅不在這裏展開。

A位:

  A(Access)位,訪問位。A位爲1時,表明當前頁曾經被訪問過;A位爲0時,表明當前頁沒有被訪問過。

  A位的設置由CPU固件在對應內存頁訪問時自動設置爲1,且能夠由操做系統在適當的時候經過程序指令重置爲0,用以計算內存頁的訪問頻率。經過訪問頻率,操做系統可以以此做爲虛擬內存調度算法中評估的依據,在物理內存緊張的狀況下,能夠選擇將最少使用的內存頁換出,以減小沒必要要的虛擬內存頁調度時的磁盤I/O,提升虛擬內存的效率。

D位:

  D(Dirty)位,髒位。當D位爲1時,表示當前頁被寫入修改過;D位爲0時,表明當前頁沒有被寫入修改過。

  髒位由CPU在對應內存頁被寫入時自動設置爲1。操做系統在進行內存頁調度時,若是發現須要被換出的內存頁D位爲1時,則須要將對應物理內存頁數據寫回虛擬頁對應的磁盤交換區,保證磁盤/內存數據的一致性;當發現須要被換出的物理內存頁的D位爲0時,表示當前頁自從換入物理內存以來沒有被修改過,和磁盤交換區中的數據一致,便直接將其覆蓋,而不進行磁盤的寫回,減小沒必要要的I/O以提升效率。

PAT位:

  PAT(Page Attribute Table),頁屬性表支持位。PAT位的存在使得CPU可以支持更復雜的,不一樣頁大小的分頁管理。當PAT=0時,每一頁的大小爲4KB;當PAT=1時,每一頁的大小是4MB,或是其它大小(分CPU的狀況而定)。

G位:

  G(Global),全局位。表示當前頁是不是全局的,而不是屬於某一特定任務的。G=1時,表示當前頁是全局的;G=0時,表示當前頁是屬於特定任務的。

  爲了加速頁表項的訪問,80386提供了TLB快表,做爲頁表訪問的高速緩存。當任務切換時,TLB內全部G=0的非全局頁將會被清除,G=1的全局頁將會被保留。將操做系統內核中關鍵的,頻繁訪問的頁設置爲全局頁,使得其可以一直保存在TLB快表中,加速對其的訪問速度,提升效率。

AVL位:

  AVL(Avaliable),可用位。和段描述符中的AVL位功能相似,CPU並不使用它,而是提供給操做系統軟件自定義使用。

頁物理基地址字段:     

  頁物理基地址字段用於標識對應的物理頁,共20位。

  因爲32位的80386的頁最小是4KB,而4GB的物理內存被分解爲了最多0x10000個4KB的物理頁。20位的頁物理基地址字段做爲物理頁的索引標號與每個具體的物理頁一一對應。經過頁物理基地址字段,便能找到惟一對應的物理內存頁。

多級頁表

  在32位的CPU中,操做系統能夠給每一個程序分配至多4GB的虛擬內存空間,若是一個內存頁佔4KB,那麼對應的每一個程序的頁表中最多須要存放着0x10000個頁表項來進行映射。即便每一個頁表項只佔小小的32位共4個字節(4Byte),這依然是一個不小的內存開銷(0x10000個頁表項的大小爲4MB)。

  一個應用程序雖然能夠被分配4GB的虛擬內存空間,但實際上可能只使用其中的一小部分,例如40MB的大小。一般程序的堆棧段和數據段都分別位於虛擬內存空間的高低兩端,並隨着程序的執行慢慢的向中間擴展,因爲頁表項對應與虛擬地址空間的連續性,這就要求任務在執行時必須完整的定義整張頁表。

  能夠看到,一級的平面頁表結構存在着明顯的頁表空間浪費的問題。雖然能夠要求應用程序不要一會兒就以4GB的內存規格進行編程,而是一開始用較小的內存,並在須要更大內存時梯度的申請更大的內存空間,並從新構造數據段和堆棧段以減小每一個任務的無用頁表項空間的浪費。但這將頁表空間優化的繁重任務強加給了應用程序,並非一個好的解決辦法。

  爲此,計算機科學家們提出了多級頁表的方案來解決頁表項過多的問題。多級頁表顧名思義,頁表的結構再也不是一個一級的平面結構(一級頁表),而是像一顆樹同樣,由頁目錄項節點頁表項節點組成。目錄節點中保存着下一級節點的物理頁地址等信息,葉子節點中則包含着真正的頁表項信息。查詢頁表項時,從一級頁目錄節點(根目錄)出發,按照必定的規則能夠找到對應的下一級子目錄節點,直到查詢出對應的葉子節點爲止。

  

80386頁目錄項介紹

  80386採用的是二級頁表的設計,二級頁表由頁目錄表和頁表共同組成。頁目錄表中存放的是頁目錄項,頁目錄項的大小和頁表項一致,爲4字節。

  經過80386指令獲得的32位線性地址,其中高20位做爲頁表項索引,低12位做爲頁內偏移地址(4KB大小的物理頁)。若是採用的是一級頁表結構,20位的頁表項索引能直接找到4MB頁表中的對應頁表項。

  而對於80386二級頁表的設計來講,因爲一個物理頁大小爲4KB,最多能夠容納1024(2^10)個頁表項或者頁目錄項,因此將頁表項索引的高10位做爲根目錄頁中頁目錄項的索引值,經過頁目錄項中的頁表項物理頁號能夠找到對應的頁表物理頁;再根據頁表項索引的後10位找到頁表中對應的頁表項。

  

80386頁目錄項結構圖

   80386的二級頁表的頁目錄項佔32位,其低12位的含義與頁表項一致。主要區別在於其高20位存放的是下一級頁表的物理頁索引,而不是虛擬地址映射的物理內存頁地址。

  

頁表基址寄存器

  前面提到過,和LDT同樣,每一個任務都擁有着本身獨立的頁表。爲此80386CPU提供了一個專門的寄存器用於追蹤定位任務本身的頁表,這個寄存器的名稱叫作頁表基址寄存器(Page Directory Base Register,PDBR),也就是控制寄存器CR3。

  因爲80386分頁機制使用的是二級頁表,所以PDBR指向的是二級頁表結構中的頁目錄,經過頁目錄表便可以間接的訪問整個二級頁表。爲了效率其中存放的直接就是頁目錄表的32位物理地址,通常由操做系統負責在任務切換時將新任務對應的頁目錄表預先加載進物理內存。

  因爲PDBR是和當前任務有關的,在任務切換時會被新任務TSS中的PDBR字段值所替換,指向新任務的頁目錄表,而舊任務的PDBR的值則在保護現場時被存入對應的TSS中。

多級頁表是如何解決頁表項浪費問題的?

  以80386的二級頁表設計爲例,最大4GB的虛擬內存空間下,不管如何一級頁目錄表是必須存在的。當不須要爲應用程序分配過多的內存時,頁目錄表中的頁目錄項所指向的對應頁表能夠不存在,即頁目錄項的P位爲0,實際不使用的虛擬內存空間將沒有對應的二級頁表節點,相比一級頁表的設計其浪費的內存會少不少。

  假設須要爲一個虛擬地址首尾各須要分配20MB,共佔用40MB內存的任務構建對應的頁表。

  1. 若是使用一級頁表,4GB的虛擬內存空間下須要提供0x10000個頁表項,共4MB,頁表的體積達到了任務自身所需40MB內存的10%,但其中絕大多數的頁表項都是沒用的(P位爲0),不會對應實際的物理內存,空間效率很低。

  2. 若是使用二級頁表,除了佔一個物理頁4KB大小的頁目錄表是必須存在的外,其頁目錄表中只有首尾兩項的P位爲1,分別指向一個實際存在的頁表(二級節點),頁目錄表中間其它的頁目錄項P位都爲0,不須要爲這些不會使用到的虛擬地址分配頁表。對於這個40MB的程序來講,其頁表只佔了3個物理頁面,共12KB,空間效率相比一級頁表高不少。

TLB快表

  前面提到了多級頁表所帶來的好處:經過頁表分層,能夠減小順序排列的無效頁表項數量,節約內存空間;頁表的層級越多,空間效率也越高。

  計算機領域中,一般並無免費的午飯,一個問題的解決,每每會帶來新的問題:多級頁表本質上是一個樹狀結構,每個節點頁都是離散的,所以每一層級訪問都須要進行一次內存尋址操做,頁表的層級越多,訪問的次數也就越多,虛擬頁地址映射過程也越慢。在32位的80386中,2級頁表下問題還不算特別嚴重;但64位CPU的出現帶來了更大的尋址空間,也須要更多的頁表項,頁表的層級也漸漸的從2級變成了3級、4級甚至更多。頁機制開啓以後,全部的內存尋址都須要通過CPU的頁部件進行轉化才能得到最終的物理地址,所以這一過程必需要快,不能由於頁表的離散層次訪問就嚴重影響虛擬地址空間到物理地址空間的轉換速度。

  要加快本來相對耗時的查詢操做,一個經常使用的辦法即是引入緩存。爲了加速通用內存的訪問,80386利用局部性原理提供了高速緩存;爲了加速多級頁表的頁表項訪問,80386提供了TLB。

  TLB(Translation Lookaside Buffer)直譯爲地址轉換後援緩衝器,根據其做用也被稱爲頁表緩存或是快表(快速頁表)。TLB中存放着一張表,其中的每一項用於緩存當前任務虛擬頁號和對應頁表項中的關鍵信息,被稱爲TLB項。

  TLB的工做原理和高速緩存相似:當CPU訪問某一虛擬頁時,經過虛擬頁號先在TLB中尋找,若是發現對應的TLB項存在,則直接以TLB項中的數據進行物理地址的轉換,這被稱爲TLB命中;當發現對應的TLB項不存在時(TLB未命中),則進行內存的訪問,在獲取內存中頁表項數據的同時,也將對應頁表項緩存入TLB中。若是TLB已滿則須要經過某種置換算法選出一個已存在的TLB項將其替換。

  TLB的查詢速度比內存快,但容量相對內存小不少,所以只能緩存數量有限的頁表項。但因爲內存訪問的局部性,只要經過合理的設計提升TLB的命中率(一般能夠達到90%以上),就能達到很好的效果。 

四. 80386分頁機制下的內存尋址流程

  下面總結一下開啓了分頁機制的80386是如何進行內存尋址的。

  1. CPU首先從內存訪問指令中獲取段選擇子和段內偏移地址

  2. 根據段選擇子從段表(GDT或LDT)中查詢出對應的段描述符

  3. 根據段描述符中的段基址和指令中的段內偏移地址生成32位的線性地址(頁機制下的虛擬地址)

  4. 32位的線性地址根據80386二級頁表的設計,拆分紅三個部分:高10位做爲頁目錄項索引,中間次高10位做爲頁表項索引,低12位做爲頁內偏移地址。

  5. 經過高10位的頁目錄項索引從一級頁目錄表中獲取二級頁表的物理頁地址(經過物理頁框號可得),再根據中間10位的頁表項索引找到對應的物理頁框。根據物理頁框號與頁內偏移地址共同生成最終的物理地址,進行物理內存的訪問。

五. 總結

  想要經過學習操做系統來更好的理解計算機程序底層的工做原理,基礎的硬件知識是必需要了解的。紙上得來終覺淺,絕知此事要躬行,在理解了基礎原理後,還須要經過實踐來加深對原理知識的理解,而閱讀相關操做系統的實現源碼就是一個很好的將實踐與原理緊密結合的學習方式。

  但願經過對硬件和操做系統的學習能幫助我打開計算機程序底層運行的神祕黑盒子一窺究竟,在思考問題時可以換一個角度從底層的視角出發,去更好的理解和掌握上層的應用技術,以免迷失在快速發展的技術浪潮中。

相關文章
相關標籤/搜索