計算機原理學習(7)-- x86-32 CPU和內存管理之分段管理

前言

 

前一篇我們介紹了內存管理中的分頁試內存管理,分頁的主要作用就是使得每個進程有一個獨立的,完整的內存空間,通過虛擬內存技術,使得程序可以在較小的內存上運行,而進程之間內存空間相互獨立,提高了安全性。這一篇將主要介紹內存管理中分段管理,以及兩種的結合,也是目前計算機普遍採用的段頁式內存管理。這也直接決定了的後面程序的編譯,加載以及允許時的內存佈局。

 

 

 

1. 內存分段

 

 

1.1 爲什麼分段?

 

 

在x86-16體系中,爲了解決16位寄存器對20位地址線的尋址問題,引入了分段式內存管理。而CPU則使用CS,DS,ES,SS等寄存器來保存程序的段首地址。當CPU執行指令需要訪問內存時,只會送出段內的偏移地址,而通過指令的類型類確定訪問那一個段寄存器。具體可以參考:計算機原理學習(5)-- x86-16 CPU和內存管理

 

到了IA-32,Intel引入了保護模式,所以在IA-32中爲了保持兼容性,所以同樣支持內存分段管理。另外我們討論過了內存分頁,頁面中包含了程序的代碼,數據等信息,它們都有各自的地址。這些地址是在編譯的時候就確定的,因爲每個進程都有獨立完整的內存空間,只需要把頁和物理頁映射就能運行,所以這個地址是可以在編譯時就決定的。在編譯時,編譯器會等程序進行語法詞法等分析,在編譯過程中會建立許多的表,來確定代碼和變量的虛擬地址:

  • 被保存起來供打印清單的源程序正文;
  • 符號表,包含變量的名字和屬性;
  • 包含所有用到的整形和浮點型數據的表;
  • 語法分析樹,包括程序語法分析的結果;
  • 編譯器內部過程調用的堆棧。

前面4張表會隨着編譯的進行不斷增大,而堆棧的數據也會變化,現在的問題就是,每一張表的大小都不確定,那麼如何指定每一張表在虛擬內存空間的地址呢?

如上圖,沒一張表都有自己的起始地址,但是當變量很多的時候,符號表需要的空間可能會超過程序正文的起始地址,這個時候就會把源程序的表的地址覆蓋掉。當然編譯器沒有這麼傻,它可以提示無法繼續編譯,當然這樣並不合適,另一個辦法就是拿出一部分沒有使用的空間給符號表。造成這個問題的原因就是分頁系統中的虛擬地址是一維的,所以在編譯過程中必須給變量,代碼分配虛擬地址。這個有點類似沒有采用分頁之前,進程之間使用物理地址導致相互覆蓋的問題。

所以我們可以爲不同的表分配自己的空間地址,也就是分段,這樣他們地址都是相對地址,全部編譯完成後確定了每張表的大小,就可以計算出實際的虛擬地址了。

 

 

 

1.2 分段的作用

 

 

分頁實際是一個純粹邏輯上的概念,因爲實際的程序和內存並沒有被真正的分爲了不同的頁面。而分段則不同,他是一個邏輯實體。一個段中可以是變量,源代碼或者堆棧。一般來說每個段中不會包含不同類型的內容。而分段主要有以下幾個作用:

  1. 解決編譯問題: 前面提到過在編譯時地址覆蓋的問題,可以通過分段來解決,從而簡化編譯程序。
  2. 重新編譯: 因爲不同類型的數據在不同的段中,但其中一個段進行修改後,就不需要所有的段都重新進行編譯。
  3. 內存共享: 對內存分段,可以很容易把其中的代碼段或數據段共享給其他程序,分頁中因爲數據代碼混合在一個頁面中,所以不便於共享。
  4. 安全性: 將內存分爲不同的段之後,因爲不同段的內容類型不同,所以他們能進行的操作也不同,比如代碼段的內容被加載後就不應該允許寫的操作,因爲這樣會改變程序的行爲。而在分頁系統中,因爲一個頁不是一個邏輯實體,代碼和數據可能混合在一起,無法進行安全上的控制。
  5. 動態鏈接: 動態鏈接是指在作業運行之前,並不把幾個目標程序段鏈接起來。要運行時,先將主程序所對應的目標程序裝入內存並啓動運行,當運行過程中又需要調用某段時,纔將該段(目標程序)調入內存並進行鏈接。可見,動態鏈接也要求以段作爲管理的單位。
  6. 保持兼容性

所以在現在的x86的體系結構中分段內存管理是必選的,而分頁管理則是可選的。

 

 

 

1.3 與x86-16分段管理的區別

 

 

Intel對分段內存管理邏輯地址的定義是【段號+段內地址】。在x86-16體系的分段管理中,CPU給出的內存地址是16位的段內偏移地址,段的基址從段寄存器中獲得,最後計算出24位的物理地址。 而在IA-32體系中引入了保護模式,每個進程有4G的獨立地址空間,CPU直接給出的是32位的段內偏移地址,段的基址從內存中獲得,最後計算出32爲的物理地址。他們最大的不同就在於獲取基址的方式以及計算方法。

 

 

IA-32爲了保持向前兼容,保留了CS/DS/ES/SS這4個寄存器,但因爲不在從段寄存器中獲得段價值,這4個段寄存器實際上已經失去了原本的作用(但不代表沒有使用)。IA-32在內存中使用一張段表來記錄各個段映射的物理內存地址(如下圖)。

在譯地的過程中,x86-16是通過16位的段基址和16位的段內偏移不是簡單的相加,而是通過 段值*0x10 + 偏移地址 對基址重定向的方式計算得到物理地址,而IA-32中則相對簡單,不需要對基址重定向,這一點和前面分頁內存管理是相似的。而CPU只需要爲這個段表提供一個記錄其首地址的寄存器就可以了。 同樣也可以使用TLB來加速。

與x86-16中分段管理另一個不同是,在IA-32中,因爲有了獨立的地址空間,對多程序也支持的非常好。而分段可以很好的支持進程間數據的共享。

 

 

 

 

2. 分段內存管理

 

 

2.1 段選擇器

 

在IA-32中保留的CS/DS/ES/SS這4個16位段寄存器不再被解釋爲段的基地址,Intel爲了保持兼容性將這些寄存器的16個位分成3個用於不同功能的域,稱爲段選擇器

其中3-15是選擇子,存放的是段描述符的索引(可以理解爲段號),該描述符爲64bit用於描述存儲器段的位置、長度和訪問權限。而段描述符可以分爲兩種,全局描述符(GDT)局部描述符(LDT),對應着第2位,而0,1兩位是表示CPU的權限級別(0-4級)。在IA-32中一共有6個段選擇器

 

  • CS保存了代碼段描述符的索引;
  • DS保存了數據段描述符的索引;
  • SS保存堆棧段描述符索引;
  • ES、FS、GS則作爲一般用途,可以指向任意的數據段,實現自定義尋址。

 

 

2.2 段描述符

 

段描述符就是前面說到的段表中的每一個項目的,一個段描述符由8個字節組成。它描述了段的特徵。前面提到段描述符可以分爲GDT和LDT兩類。通常來說系統只定義一個GDT,而每個進程如果需要放置一些自定義的段,就可以放在自己的LDT中。IA-32中引入了GDTR和LDTR兩個寄存器,就是用來存放當前正在使用的GDT和LDT的首地址。

 

上面的圖是Linux中不同段的描述符,結構基本是一致的,只有少數字段有差別。其中最重要的就是BASE字段,一共32位,保存的是當前段的首地址。 在Linux系統中,每個CPU對應一個GDT。一個GDT中有18個段描述符和14個未使用或保留項。其中用戶和內核各有一個代碼段和數據段,然後還包含一個TSS任務段來保存寄存器的狀態。其他的段則包括局部線程存儲,電源管理,即插即用等多個段。而Linux系統中,大多數用戶態的程序都不使用LDT。

 

 

 

2.3 段地址轉換

 

 

在IA-32中,邏輯地址是16位的段選擇符+32位偏移地址,段寄存器不在保存段基址,而是保存段描述符的索引。

 

 

  1. IA-32首選確定要訪問的段(方式x86-16相同),然後決定使用的段寄存器。
  2. 根據段選擇符號的TI字段決定是訪問GDT還是LDT,他們的首地址則通過GTDR和LDTR來獲得。
  3. 將段選擇符的Index字段的值*8,然後加上GDT或LDT的首地址,就能得到當前段描述符的地址。(乘以8是因爲段描述符爲8字節)
  4. 得到段描述符的地址後,可以通過段描述符中BASE獲得段的首地址。
  5. 將邏輯地址中32位的偏移地址和段首地址相加就可以得到實際要訪問的物理地址。

 

 

2.4 緩存段描述符

 

爲了加速地址轉換的過程,根據程序的局部性原理,我們可以講當前段寄存器指向的段描述符緩存在特定的寄存器中。這裏爲6個段寄存器準備了6個用來緩存段描述符的非編程寄存器。這樣就能加快地址轉換的過程。而僅當段寄存器內容變化時,纔有必要去訪問內存中的GDT或LDT。

 

 

 

3. 段頁式內存管理

 

 

分段內存管理的優勢在於內存共享和安全控制,而分頁內存管理的優勢在於提高內利用率。他們之間並不是相互對立的競爭關係,而是可以相互補充的。也就是可以把2種方式結合起來,也就是目前計算機中最普遍採用的段頁式內存管理。段頁式管理的核心就是對內存進行分段,對每個段進行分頁。這樣在擁有了分段的優勢的同時,可以更加合理的使用內存的物理頁。

 

 

2.1  段頁式內存管理結構

 

對於段頁式管理來說,我們需要通過段表來保存每一個段的信息,通過頁表保存每個段中虛擬頁的信息。在段頁管理的系統中,CPU給出的不再是分頁系統中的虛擬地址,而是給出的邏輯地址。(前面2篇有介紹邏輯地址和虛擬地址,簡單的說邏輯地址是二維的,而虛擬地址是一維的,平坦的)。

 

 

 

2.2 段頁式地址轉換

 

 

 

上面的圖簡單的描述了在段頁式內存管理的系統中,地址轉換的過程。實際上就是我們前面介紹的分段和分頁地址轉換的結合。

  1. CPU給出要訪問的邏輯地址;
  2. 通過分段內存管理的地址轉換機制,將邏輯地址轉換爲線性地址,也就是分頁系統中的虛擬地址;
  3. 通過分頁內存管理的地址轉換機制,將虛擬地址轉換爲物理地址;

 

 

 

 4. 總結

 

 

這一篇文章主要介紹了IA-32系統中分段式內存管理是如何工作的。而目前主流的系統中都採用了段頁相結合的內存管理方式,當然不同的系統具體實現起來是不同的。比如在Linux中,所有段首地址都是從0x0000000開始,所以邏輯地址和轉換得到的線性地址完全是一樣的。到這一篇,有關x86的內存管理方式以及介紹晚了。內存的分頁和分段也決定了程序的編譯,可執行文件的結構,程序的內存佈局等等。這個將在後面介紹到。

 

 

 

參考

 

《深入理解Linux內核第三版》

 

《現代操作系統》

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章