內存管理筆記(分頁,分段,邏輯地址,物理地址與地址轉換方式)


本文內容參考自如下博客與書籍:

http://blog.csdn.net/windowseight/article/details/8279863

http://bbs.chinaunix.net/thread-2083672-1-1.html

http://blog.csdn.net/erazy0/article/details/6457626#comments

http://blog.csdn.net/drshenlei/article/details/4261909

http://duartes.org/gustavo/blog/post/memory-translation-and-segmentation

《操作系統概念》第六版中文版,高等教育出版社,第九章,鄭扣根譯。

《Operating System Concepts》7th Edition, 高等教育出版社。

文中很多處是根據自己理解所寫,若有錯誤,歡迎指出和探討。

 

1. 物理地址和邏輯地址

物理地址:加載到內存地址寄存器中的地址,內存單元的真正地址。在前端總線上傳輸的內存地址都是物理內存地址,編號從0開始一直到可用物理內存的最高端。這些數字被北橋(Nortbridge chip)映射到實際的內存條上。物理地址是明確的、最終用在總線上的編號,不必轉換,不必分頁,也沒有特權級檢查(no translation, no paging, no privilege checks)。

邏輯地址:CPU所生成的地址。邏輯地址是內部和編程使用的、並不唯一。例如,你在進行C語言指針編程中,可以讀取指針變量本身值(&操作),實際上這個值就是邏輯地址,它是相對於你當前進程數據段的地址(偏移地址),不和絕對物理地址相干。

為什么會有這兩種地址?

個人覺的原因在於邏輯地址分配更加靈活,可以允許不唯一,看起來也較為直觀,例如,一段代碼中分配數組,邏輯地址上是連續的,然而在物理地址上,這個數組所占用的頁可能分散開來,物理地址上就是不連續的,這樣對程序的可理解性上有影響。另外,有了邏輯地址這個概念,才能使用虛擬內存技術。

 

2. Paging,分頁內存管理方案

(1) 分頁的最大作用就在於:使得進程的物理地址空間可以是非連續的。

物理內存被划分為一小塊一小塊,每塊被稱為幀(Frame)。分配內存時,幀是分配時的最小單位,最少也要給一幀。在邏輯內存中,與幀對應的概念就是頁(Page)。

邏輯地址的表示方式是:前部分是頁碼后部分是頁偏移。

例如,已知邏輯空間地址為2^m個字節(也就是說邏輯地址的長度是m位),已知頁大小是2^n字節。那么一共可以有2^(m-n)個頁。因此頁碼部分會占m-n位,之后的n位,用來存儲頁偏移。

舉個例子, 頁大小為4B,而邏輯內存為32B(8頁),邏輯地址0的頁號為0,頁號0對應幀5,因此邏輯地址映射為物理地址5*4+0=20。邏輯地址3映射物理地址5*4+3=23。邏輯地址13(4*3+1,頁號為3,偏移為1,因此幀號為2),映射到物理地址9。


 

采用分頁技術不會產生外部碎片(內存都被划分為幀),但可能產生內部碎片(幀已經是最小單元,因此幀內部可能有空間沒有用到)。

按概率計算下來,每個進程平均可有半個幀大小的內部碎片。

(2) 頁表的硬件實現

上一小節中寫到頁表是邏輯地址轉化到物理地址的關鍵所在。那么頁表如何存儲?

每個操作系統都有自己的方法來保存頁表。絕大多數都會為每個進程分配一個頁表。現在由於頁表都比較大,所以放在內存中(以往是放在一組專用寄存器里),其指針存在進程控制塊(PCB)里,當進程被調度程序選中投入運行時,系統將其頁表指針從進程控制塊中取出並送入用戶寄存器中。隨后可以根據此首地址訪問頁表。

頁表的存儲方式是TBL(Translation look-aside buffer, 翻譯后備緩沖器)+內存。TBL實際上是一組硬件緩沖所關聯的快速內存。若沒有TBL,操作系統需要兩次內存訪問來完成邏輯地址到物理地址的轉換,訪問頁表算一次,在頁表中查找算一次。TBL中存儲頁表中的一小部分條目,條目以鍵值對方式存儲。

(3) 頁表的數據結構

a.

今年是2013年,現有的筆記本電腦,內存地址空間一般為2^32字節以上。對於具有32位邏輯地址空間的計算機系統,如果系統的頁大小為4KB(2^12B),那么頁表可以擁有2^(32-12)個,也就是一百多萬個條目,假設每個條目占有4B,那每個進程都需要4MB的物理地址空間來存放頁表本身。而且,頁表本身需要分配在連續內存中。

為此,Hierarchical Paging(層次化分頁)被提出,實際上就是將頁號分為兩部分,第一部分作為索引,第二部分作為頁號的偏移。

以一個4kb頁大小的32位系統為例。一個邏輯地址被分為20位的頁碼和12位的頁偏移。因為要對頁表進行再分頁,所以該頁號可分為10位的頁碼和10位的頁偏移。這樣一個邏輯地址就表示如下形式:

 

地址轉換過程如下:

 

地址由外向內轉換,因此此方法也被稱為forward-mapped page table(向前映射表)

b. Hashed Page Tables 哈希頁表

處理超過32位地址空間的常用方法是使用hashed page table(哈希頁表),並以虛擬頁碼作為哈希值。哈希頁表的每一條目都包括一個鏈表的元素,這些元素哈希成同一位置。每個元素有三個域:虛擬頁碼,所映射的幀號,指向鏈表中下一個元素的指針。

個人看來,哈希頁表的地址轉換方式,實際上是Chaining(鏈接)方式,也就是一種哈希函數的溢出處理方式(另一種溢出處理方式叫做Open Addressing,開放尋址),具體過程如下:

邏輯地址需要大於32bit的地址空間來表示,但是操作系統仍只有32bit來表示地址。此時人們便想到虛擬頁地址,虛擬地址可以在32bit表示范圍之內,然后利用哈希函數完成邏輯地址到虛擬地址的映射,由於虛擬地址更少,哈希函數會出現溢出,這里使用Chaining來解決溢出。

邏輯地址中的頁號(下圖中的p)經過哈希函數的計算,算出虛擬地址中的頁號,根據虛擬頁號可以在哈希表中以O(1)方式尋址,用p與鏈表中的每一個元素的第一個域相比較。如果匹配,那么相應的幀號就用來形成物理地址。如果不匹配,就對鏈表中的下一個節點進行比較,以尋找一個匹配的頁號。

 

c. Inverted page table 反向頁表

時間關系,這段暫時略過。

3. Segmentation,分段內存管理方案

采用分頁內存管理有一個不可避免的問題:用戶視角的內存和實際內存的分離。設想一段main函數代碼,里面包含Sqrt函數的調用。按照編寫者的理解,這段代碼運行時,操作系統應該分配內存給:符號表(編譯時使用),棧(存放局部變量與函數參數值),Sqrt代碼段,主函數代碼段等。這樣,編寫者就可以方便地指出:"函數sqrt內存模塊的第五條指令",來定位一個元素。而實際上,由於采用Paging的管理方式,所有的一切都只是散落在物理內存中的各個幀上,並不是以編寫者的理解來划分模塊。

Segmentation的內存管理方式可以支持這種思路。邏輯地址空間由一組段組成。每個段都有名字和長度。地址指定了段名稱和段內偏移。因此用戶通過兩個量來指定地址:段名稱和偏移。段是編號的,通過段號而非段名稱來引用。因此邏輯地址由有序對構成:

 <segment-number,offset>(<段號s, 段內偏移d>)

段偏移d因該在0和段界限之間,如果合法,那么就與基地址相加而得到所需字節在物理內存中的地址。因此段表是一組基地址和界限寄存器對。

 

例如下圖,有5個段,編號0~4,例如段2為400B開始於位置4300,對段2第53字節的引用映射成位置4300+53=4353。而段0字節1222的引用則會觸發地址錯誤,因為該段的僅為1000B長(界限為1000)。

4. 合並分段和分頁的管理方案

在現有的Intel兼容計算機(x86)上,采用的內存管理方案是分段和分頁合並的管理方案。

在這個方案中,邏輯地址,如前一節中所說,是由一個段標識符加上一個指定段內相對地址的偏移量,表示為 [段標識符:段內偏移量]。

這樣的邏輯地址轉換的過程是怎樣呢?如下圖所示:

CPU要執行一條引用了內存地址的指令時,轉換過程就開始了。第一步是把邏輯地址轉換成線性地址。但是,為什么不跳過這一步,而讓軟件直接使用線性地址(或物理地址呢?)原因主要是因為:

(1) Intel的更新是漸進式而非革命式,新的處理器需要兼容和保留過往的設置。具體的原因,博文Memory Translation and Segmentation (http://blog.csdn.net/drshenlei/article/details/4261909) 中講的較為清楚。

(2) 如上節所說,采用段內存管理,可以跟方便地進行地址保護(同一類型的地址邏輯地址在一起)。

下面講邏輯地址到線性地址的部分。

在IBM OS/2 32位版本的操作系統,和Intel 386的環境下。操作系統采用的內存分配方式就是分段和分頁合並的方式。

邏輯地址的實際上是一對<選擇符,偏移>。

選擇符的內容如下:

從左開始,13位是索引(或者稱為段號),通過這個索引,可以定位到段描述符(segment descriptor),而段描述符是可以真正記載了有關一個段的位置和大小信息, 以及訪問控制的狀態信息。段描述符一般由8個字節組成。由於8B較大,而Intel為了保持向后兼容,將段寄存器仍然規定為16-bit(盡管每個段寄存器事實上有一個64-bit長的不可見部分,但對於程序員來說,段寄存器就是16-bit的),那么很明顯,我們無法通過16-bit長度的段寄存器來直接引用64-bit的段描述符。因此在邏輯地址中,只用13bit記錄其索引。而真正的段描述符,被放於數組之中。

這個內存中的數組就叫做GDT(Global Descriptor Table,全局描述表),Intel的設計者門提供了一個寄存器GDTR用來存放GDT的入口地址。程序員將GDT設定在內存中某個位置之后,可以通過LGDT指令將GDT的入口地址裝入此寄存器,從此以后,CPU就根據此寄存器中的內容作為GDT的入口來訪問GDT了。

除了GDT之外,還有LDT(Local Descriptor Table,本地描述表),但與GDT不同的是,LDT在系統中可以存在多個,每個進程可以擁有自己的LDT。LDT的內存地址在LDTR寄存器中。

在之前圖中的TI位,就是用來表示此索引所指向的段描述符是存於全局描述表中,還是本地描述表中。=0,表示用GDT,=1表示用LDT。

RPL位,占2bit,是保護信息位,還沒有仔細了解過這一塊,暫時先不寫。

找到,段描述符后,加上偏移量,便是線性地址。轉換過程如下:

在Intel 386的環境下,線性地址轉換為物理地址的過程,和第二節分頁式內存管理中,層次分頁中,邏輯地址轉換為物理地址的方法類似。如下圖。

 

Intel 80386的地址轉換全過程如下圖:

 

內存管理部分是操作系統的核心功能之一,這次將理論部分整理出來,一是為了復習,二也是為了提綱挈領地為深入學習操作系統做准備。

文中的圖片均非本人原創,主要來自文章開頭所引用的博文,以及參考書籍中的圖片。若有侵權行為,請指出,博主將盡快移除。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM