天天看點

分頁機制分頁機制概述

本文為<x86彙編語言:從實模式到保護模式> 第16章筆記

因為段的長度不定, 在配置設定記憶體時, 可能會發生記憶體中的空閑區域小于要加載的段, 或者空閑區域遠遠大于要加載的段. 在前一種情況下, 需要另外尋找合适的空閑區域; 在後一種情況下, 配置設定會成功, 但太過于浪費. 為了解決這個問題, 從80386處理器開始, 引入了分頁機制. 分頁功能從總體上來說, 是用長度固定的頁來代替長度不一定的段, 藉此解決因段長度不同而帶來的記憶體空間管理問題. 盡管作業系統也可以用軟體來實施固定長度的記憶體配置設定, 但太過于複雜, 由處理器固件來做這件事, 可以使速度和效率最大化.

分頁機制概述

簡單的分頁模型

處理器中有負責分段管理的段部件, 每個程式或任務都有自己的段, 這些段都用段描述符定義. 随着程式的執行, 當要通路記憶體, 就用段位址加上偏移量, 段部件就會輸出一個線性位址. 在單純的分段模式下, 線性位址就是實體位址.

一旦決定采用頁式記憶體管理, 就應當把4GB記憶體分成大小相同的頁. 頁的最小機關是4KB, 也就是4096位元組, 用十六進制表示就是0x1000. 是以, 第一個頁的實體位址就是0x00000000, 第2個頁的實體位址是0x00001000, 第3個頁的實體位址是0x00002000....最後一個頁的實體位址是0xfffff000. 這樣,  4GB記憶體劃分為1048576(0x100000)個頁. 很顯然, 頁的實體位址, 其低12位始終為0.

段管理機制對于Intel處理器來說是最基本的, 任何時候都無法關閉. 也就是說, 即使啟用頁管理功能, 分段機制依然是起作用的, 段部件依然工作.

分頁機制分頁機制概述

如上圖所示, 記憶體的配置設定設計段空間的配置設定和頁配置設定. 左邊是虛幻的, 或者說虛拟的4GB記憶體空間, 稱為虛拟記憶體; 右邊是實實在在的記憶體, 被分成1048576個4KB的頁面(每個方框4KB, 灰色代表已配置設定).

在分頁模式下, 作業系統可以建立一個為所有任務公用的4GB虛拟記憶體空間, 也可以為每一個任務建立獨立的4GB虛拟記憶體空間, 這都是可行的. 當一個程式加載時, 作業系統既要在左邊的虛拟記憶體中配置設定段空間, 又要在右邊的實體記憶體中配置設定相應的頁面. 是以, 第一步驟是尋找空閑的段空間, 該段空間既沒有被其他程式使用, 也沒有被同一程式内的其他段使用. 比如上圖, 假設已經成功找到并配置設定了一個段空間, 基位址為0x00200000, 長度為8200位元組.

頁的最小尺寸是4KB, 也就是4096位元組, 是以, 8200位元組的段, 需要占用3個頁面, 其中最後一個頁面隻用了8個位元組, 其餘都是浪費着, 但這無關緊要, 如果允許頁共享, 多個段或多個程式可以用同一個頁來存放各自的資料. 在分段之後, 作業系統的任務就是把段拆開, 并分别映射到實體頁. 注意, 段必須是連續的, 但不要求所配置設定的頁都是連續的, 挨在一起的.

就上圖中的列子來說, 該段有8200位元組, 需要配置設定3個頁面. 作業系統在實體記憶體中搜尋可用的空閑頁, 接下來, 要建立線性位址和也之間的對應關系, 在圖中, 0x200000~0x00200FFF對應着實體位址為0x00002000的頁, 0x00201000~0x00201FFF對應着0x00004000, 0x00202000~0x00202007對應着0x00007000的頁, 當然, 這裡隻是示例, 線性位址區間和頁的對應關系可以随意.

4GB虛拟記憶體空間不可能用來儲存任何資料, 因為它是虛拟的, 它隻是用來訓示記憶體的使用情況. 當作業系統加載一個程式并建立為任務時, 作業系統在虛拟記憶體空間尋找空閑的段, 并映射到空閑的頁, 然後, 到真正開始加載程式時, 再把原本屬于段的資料按頁的尺寸拆開, 分開寫入對應的頁中.

從段部件輸出的是線性位址, 或者叫做虛拟位址. 為了根據線性位址找到頁的實體位址, 作業系統必須維護一張表, 把線性位址轉換成實體位址, 這是一個反過程.

分頁機制分頁機制概述

如上圖所示, 因為有1048576個頁, 故轉換表有1048576個表項. 這是個一維表格, 每個表項占4位元組, 内容為頁的實體位址. 這個表格的用法是這樣的: 因為頁的尺寸是4KB, 故, 線性位址的低12位可用于通路頁内偏移, 高20位可用于指定一個實體頁. 是以, 把線性位址的高20位當成索引, 乘以4, 作為表内偏移量, 從表中取出一個雙字, 那就是該線性位址做對應的頁的實體位址. 舉個例子: mov edx, [0x0002]    執行這條指令, 段部件用段位址0x00200000加上指令中給出的偏移量0x2002, 得到線性位址0x00200002. 線性位址的高20位是表格索引, 即0x00200, 将索引乘以4, 得到0x00800, 這就是表内偏移, 看圖, 從該單元可以取出一個雙字0x00007000, 這就是頁實體位址. 線性位址的低12位是頁内偏移, 用頁實體位址加上頁内偏移量, 就是最終的實體記憶體位址. 0x00007000加上0x0002, 得到0x00007002, 這就是實際要通路的實體記憶體位址. 這裡有個問題, 為什麼表内表内偏移為0x000800的地方, 會恰好是實體位址0x00007000, 而不是其他頁位址呢? 當程式加載時, 作業系統會首先在虛拟記憶體中配置設定段, 然後, 根據段需要分成多少頁, 來搜尋空閑頁面. 當段較大時, 要按頁的尺寸分成好幾個位址區段, 作業系統用每個區段的首位址, 取高20位, 乘以4, 作為偏移量通路表格, 并将配置設定給區段的頁的實體位址寫入該表項. 最後, 把原本需要寫入每個區段的程式資料, 寫到對應的頁中. 注意了, 在頁式記憶體管理中, 頁面的管理和配置設定是獨立的, 和分段以及段位址沒有關系.作業系統所要做的, 就是尋找空閑頁面, 把它配置設定給需要的段, 并将頁的實體位址填寫到映射表内. 很顯然, 也很重要的結論是, 線性位址, 包括線性位址空間, 和頁面配置設定機制沒有關系.

基于以上特點, 同時為了充分挖掘分頁記憶體管理的潛力, 一般來說, 每個任務都可以擁有4GB的虛拟記憶體空間; 同時, 每個任務都有自己的4GB虛拟記憶體空間, 但是, 很重要的是, 在整個系統中, 實體頁面是統一調配的. 考慮這樣一種情景: 任務A有一個段, 基位址為0x00050000, 長度為3000自己, 系統為它配置設定了實體位址0x08001000的頁. 過了一會, 任務B加載了, 它也有一個段, 基位址也是0x00050000, 長度為4096位元組, 此時, 作業系統為它配置設定了另外一個不同的, 實體位址為0x00700000的頁. 在這種情況下, 在任務A内通路線性位址0x00050006, 通路的其實是實體位址0x08001006; 在任務B内通路同樣的線性位址時, 通路的其實是實體位址0x00700006.

另一個問題是, 每個任務都有4GB虛拟記憶體空間, 而實體記憶體隻有一個, 最大也才4GB, 根本不夠分的. 事實上, 确實不夠分, 但是作業系統可以暫時将不用頁退避到磁盤, 調入馬上要使用的頁, 通過這種手段來實作分頁記憶體管理.

以上, 就是基本的段頁式記憶體管理機制. 基本的段頁式記憶體管理示意圖:

分頁機制分頁機制概述

頁目錄, 頁表和頁

我們知道, 為了完成從虛拟位址(線性位址)到實體位址的轉換, 作業系統應當為每個任務準備一張頁映射表. 因為任務的虛拟位址空間為4GB, 可以分出1048576個頁, 是以, 映射表需要1048756個表項, 又因為每個表項4位元組, 故映射表總大小為4MB. 沒錯, 這張表很大, 要占用相當一部分空間, 考慮到在實踐中, 沒有哪個任務會真的用到所有表項, 充其量隻是很小一部分, 這就很浪費了.  為了解決這個問題, 處理器設計了階層化的分頁結構.

分頁結構階層化的主要手段是不采用單一的映射表, 取而代之的是頁目錄表和頁表. 如下圖所示:

分頁機制分頁機制概述

首先, 因為4GB的虛拟記憶體空間對應着1048576個4KB頁, 可以随機的抽取這些頁, 将它們組織在1024個頁表内, 每個頁表可以容納1024個頁. 頁表内的每個項目叫做頁表項, 占4位元組, 存放的是頁的實體位址, 故每個頁表的大小是4KB, 正好是一個标準頁的長度. 注意, 頁在頁表内的分布是随機的, 哪個頁位于哪個頁表中, 這是沒有規律的.

如圖所示, 在将1048576個頁歸攏到1024個頁表之後, 接着, 再用一個表來指向1024個頁表, 這就是頁目錄表(Page Directory Table: PDT), 和頁表一樣, 頁目錄項的長度為4位元組, 填寫的是頁表的實體位址, 共指向1024個頁表, 是以頁目錄表的大小是4KB, 正好一個标準頁的長度.

這樣的階層化分頁結構是每個任務都擁有的, 或者說, 每個任務都有自己的頁目錄和頁表. 如下圖所示, 在處理器中有個控制寄存器CR3, 存放着目前任務頁目錄的實體位址, 故又叫做頁目錄基址寄存器(Page Directory Base Register: PDBR). 每個任務都有自己的TSS, 其中就包括了CR3寄存器域, 存放了任務自己的頁目錄實體位址. 當任務切換時, 處理器切換到新任務開始執行, 而CR3寄存器的内容也被更新, 以指向新任務的頁目錄位置. 相應的, 頁目錄又指向一個個的頁表, 這就使得每個任務都隻在自己的位址空間内運作. 從下圖可以看出, 頁目錄和頁表也是普通的頁, 混迹于全部的實體頁中. 它們和普通頁的不同支援僅僅在于功能不一樣. 當任務撤銷之後, 它們和任務所占用的普通頁一樣會被回收, 并配置設定給其他任務.

分頁機制分頁機制概述

位址變換的具體過程

對于Intel處理器來說, 有關分頁, 最簡單和最基本的機制就是這些; CR3寄存器給出了頁目錄的實體位址; 頁目錄給出了所有頁表的實體位址, 而每個頁表給出了它所包含的頁的實體位址. 好了, 該清楚的都清楚了, 唯一還不明白的, 應該是如何用這種層次性的分頁結構把線性位址轉換成實體位址? 這裡舉個例子, 某任務加載後, 在4GB虛拟位址空間建立了一個段, 起始位址為0x00800000, 段界限為0x5000, 位元組粒度. 目前任務執行時, 段寄存器DS指向該段. 又假設執行了下面一條指令

mov edx, [0x1050]
           

此時, 段部件會輸出線性位址0x00801050. 在沒有開啟分頁機制時, 這就是要通路的實體位址. 但現在開啟了分頁機制, 是以這是一個下虛拟位址, 要經過頁部件轉換, 才能得到實體位址.

如下圖所示, 處理器的頁部件專門負責線性位址到實體位址的轉換工作. 它首先将段部件送來的32位線性位址分為3段, 分别是高10位, 中間10位, 低12位. 高10位是頁目錄的索引, 中間10位是頁表的索引, 低12位則作為頁内偏移量來用.

分頁機制分頁機制概述

目前任務頁目錄的實體位址在處理器的CR3寄存器中, 假設它的内容為0x00005000. 段管理部件輸出的線性位址是0x00801050, 其二進制的形式如圖中給出. 高10位是十六進制的0x002, 它是頁目錄表内的索引, 處理器将它乘以4(因為每個目錄項4位元組), 作為偏移量通路頁目錄. 最終處理器從實體位址00005008處取得頁表的實體位址0x08001000.

線性位址的中間10位為0x001, 處理器用它作為頁表索引取得頁的實體位址. 将該值乘以4, 作為偏移量通路頁表. 最終, 處理器又從實體位址08001004處取得頁的實體位址, 這就是我們一直努力尋找的那個頁.

頁的實體位址是0x0000c000, 而線性位址的低12位是資料所在的業内偏移量. 故處理器将它們相加, 得到實體位址0x0000C050, 這就是線性位址0x00801050所對應的實體位址, 要通路的資料就在這裡.

注意, 這種變換不是無緣無故的, 而是事先安排好的. 當任務加載時, 作業系統先建立虛拟的段, 并根據段位址的高20位決定它要用到哪些頁目錄項和頁表項. 然後, 尋找空閑的也, 将原本應該寫入段中的資料寫到一個或者多個頁中, 并将頁的實體位址填寫到相對應的頁表項中. 隻有這樣做了, 當程式運作的時候, 才能以相反的順序進行位址變換, 并找到正确的資料.

頁目錄項, 頁表項, CR3和打開分頁

頁目錄項和頁表項

頁目錄和頁表中分别存放為頁目錄項和頁表項, 它們的格式如下:

分頁機制分頁機制概述

可以看出, 在頁目錄和頁表中, 隻儲存了頁表或者頁實體位址的高20位. 原因很簡單, 頁表或者頁的實體位址, 都要求必須是4KB對齊的, 以便于放在一個頁内, 故其低12位全是0. 在這種情況下, 可以隻關心其高20位, 低12位安排其他用途.

  • P 是存在位, 為1時, 表示頁表或者頁位于記憶體中. 否則, 表示頁表或者頁不在記憶體中, 必須先予以建立, 或者從磁盤調入記憶體後方可使用.
  • RW 是讀/寫位. 為0時表示這樣的頁隻能讀取, 為1時可讀可寫
  • US 是使用者/管理位. 為1時, 允許所有特權級别的程式通路; 為0時, 隻允許特權級别為0, 1和2的程式通路.
  • PWT(Page-level Write-Through) 是頁級通寫位, 和高速緩存有關. "通寫"是處理器高速緩存的一種工作方式, 這一位用來間接決定是否采用此種方式來改善頁面的通路效率.
  • PCD(Page-level Cache Disable)是頁級高速緩存禁止位, 用來間接決定該表項所指向的那個頁是否使用高速緩存政策.
  • A 是通路位. 該位由處理器固件設定, 用來訓示此表項所指向的頁是否被通路過.
  • D(Dirty) 是髒位. 該位由處理器固件設定, 用來訓示此表項所指向的頁是否寫過資料
  • PAT(Page Attribute Table) 頁屬性表支援位. 此位涉及更複雜的分頁系統, 和頁高速緩存有關, 可以不予理會, 在普通的4KB分頁機制中, 處理器建議将其置0.
  • G 是全局位. 用來訓示該表項所指向的頁是否為全局性質的. 如果頁是全局的, 那麼, 它将在高速緩存中一直儲存(也就意味着位址轉換速度會很快). 因為頁高速緩存容量有限, 隻能存放頻繁使用的那些表項. 而且, 當因任務切換等原因改變CR3寄存器的内容時, 整個頁高速緩存的内容都會被重新整理.
  • AVL位卑處理器忽略, 軟體可以使用.

CR3(PDBR)和開分頁機制

分頁機制分頁機制概述

控制寄存器CR3, 也就是頁目錄表基位址寄存器PDBR, 該寄存器如上圖所示.

由于頁目錄表必須位于一個自然頁内(4KB對齊), 故其實體位址的低12位是全0. 低12位除了PCD和PWT外, 都沒有使用. 這兩位用于控制頁目錄的高速緩存特性, 參見上面解釋.

分頁機制分頁機制概述

控制寄存器CR0的最高位PG位, 用于開啟分頁或者關閉頁功能. 當該位清0時, 頁功能關閉, 從段部件來的線性位址就是實體位址. 當它置位時, 頁功能開啟. 隻能在保護模式下才能開啟分頁功能, 當PE位清0時(實模式), 設定PG位将導緻處理器産生一個異常中斷.

繼續閱讀