x86分页机制详解
文章目錄
- 1. 為什么會有分頁機制?
- 2. 從虛擬地址到物理地址
- 3. 簡單的分頁模型
- 4. 頁表和頁目錄
- 4.1 層次化的分頁結(jié)構(gòu)
- 4.2 頁表
- 4.3 頁目錄
- 5. 地址變換的具體過程
1. 為什么會有分頁機制?
有些資料說是為了實現(xiàn)“虛擬內(nèi)存”,真的是這樣嗎?如果沒有分頁機制,能否實現(xiàn)“虛擬內(nèi)存”?答案是肯定的。
當同時運行的任務很多時,內(nèi)存可能就不夠用。
如圖所示,每個段描述符都有 AVL 位(簡稱 A 位),用于表示一個段最近是否被訪問過(準確地說是表明從上次操作系統(tǒng)清零該位后一個段是否被訪問過)。
當創(chuàng)建描述符的時候,應該把 A 位清零。之后,每當該段被訪問時,準確地說是處理器把這個段的段選擇符加載進段寄存器時,CPU 就將該位置“1”;對該位的清零是由操作系統(tǒng)負責的,通過定期監(jiān)視該位的狀態(tài),就可以統(tǒng)計出該段的使用頻率(比如,每 1 秒鐘查看一次,一旦置位就清零,統(tǒng)計 10 秒鐘內(nèi)被置位了多少次,次數(shù)越多說明使用越頻繁)。當內(nèi)存空間緊張時,可以把不經(jīng)常使用的段退避到硬盤上,從而實現(xiàn)虛擬內(nèi)存管理。
當某個段被換出到磁盤時,操作系統(tǒng)應該將這個段的描述符的 P 位清零。過上一段時間,當再次訪問這個段時,因為它的描述符的 P 位是 0,處理器就會引發(fā)段不存在異常(中斷號 11)。這類中斷通常是由操作系統(tǒng)處理的,它會用同樣的方法騰出空間,然后把這個段從磁盤調(diào)入內(nèi)存。當這類中斷返回時,處理器會再次執(zhí)行引發(fā)異常的那條指令,這時候段已經(jīng)在內(nèi)存中(P=1),于是程序又可以繼續(xù)執(zhí)行了。
由此可見,即使沒有分頁機制,利用“分段”也可以實現(xiàn)“虛擬內(nèi)存”。
但是,因為段的長度不固定,在段的換入換出時會產(chǎn)生外部碎片,這樣就浪費了很多內(nèi)存。為了解決這個問題,從 80386 處理器開始,引入了分頁機制。分頁機制簡單來說,是用長度固定的頁來代替長度不定的段,以解決因段的長度不同帶來的內(nèi)存空間管理變得復雜的問題。
盡管操作系統(tǒng)也可以利用純軟件來實施固定長度的內(nèi)存分配,但是過于復雜。由處理器固件來做這件事情,可以省去很多麻煩,速度也可以提高。
總結(jié)一下,引入分頁機制并不是為了實現(xiàn)虛擬內(nèi)存,而是為了解決內(nèi)存碎片的問題。
2. 從虛擬地址到物理地址
分頁機制是 80x86 內(nèi)存管理機制的第二部分。分段機制把邏輯地址轉(zhuǎn)換成線性地址,而分頁機制則把線性地址轉(zhuǎn)換成物理地址。
分頁機制會把線性地址空間(段已映射到其中)劃分成頁面,然后這些線性地址空間的頁面被映射到物理地址空間的頁面上。如下圖所示:
80x86 使用 4K(2 的 12 次方)字節(jié)固定大小的頁面。每個頁面均是 4KB,且對齊于 4K 地址邊界處(地址的低 12 位全是 0)。
3. 簡單的分頁模型
4GB(2 的 32 次方)的線性地址空間可以劃分為 1048576(= 2 的 20 次方,即 1M)個頁面。為了根據(jù)線性地址找到對應的物理地址,操作系統(tǒng)必須維護一張表(如下圖所示)。
這個表暫且叫做“頁映射表”,它一共有 1048576 個表項,每個表項占 4 個字節(jié),其內(nèi)容是某個頁的起始物理地址(共 32 比特,低 12 位全為 0)。
頁映射表是這樣使用的:因為頁的尺寸是 4KB,所以線性地址的低 12 位可以作為頁內(nèi)偏移,高 20 位可以用來索引一個表項,找到了這個表項,就找到了對應的物理頁。
具體可以參考我的博文:簡單的分頁模型
4. 頁表和頁目錄
4.1 層次化的分頁結(jié)構(gòu)
上文提到的頁映射表,一共有 1048576(=1M)個表項,每個表項占 4 個字節(jié),所以表的大小是 4MB,在當時看來要占用相當一部分內(nèi)存。考慮到在實踐中,沒有哪個任務會真的用到所有表項,充其量只是很小一部分,所以內(nèi)存中放一個 4MB 的表格確實很浪費。也許你會建議,能不能先劃出一小片內(nèi)存,只存表格用到的部分,然后根據(jù)需要動態(tài)擴展。的確,這個方法可行。但是因為特殊原因(任務的 4GB 地址空間包括兩個部分:局部空間和全局空間。頁目錄的前半部分指向任務自己的頁表,后半部分則指向內(nèi)核的頁表。整個映射表的前一半對應全局地址空間,后一半對應局部地址空間),這張表從一開始就必須完全定義,所以不可避免地要占用 4MB 的內(nèi)存空間。為了解決這個問題,同時又不會浪費寶貴的內(nèi)存空間,處理器設(shè)計了層次化的分頁結(jié)構(gòu)。
4.2 頁表
4GB 的線性地址空間可以劃分為 1048576(2 的 20 次方,即 1M,也可以看成是 1024*1024)個頁面,所以,可以隨機地抽取這些頁面,每 1024 個頁面是一組,可以分成 1024 組。對于每組中 1024 個頁面的物理地址,按某種順序排列可以構(gòu)成一張表(每個表項都是一個頁面的物理地址),這個表就是頁表。頁表的大小是 1024*4B=4KB,剛好是一個物理頁的大小。
4.3 頁目錄
因為已經(jīng)分成了 1024 組,每組都有一個頁表(大小為 4KB),所以這 1024 個頁表又可以用一張表來指向,這就是頁目錄。類似于頁表,頁目錄共有 1024 個表項(稱作頁目錄項),**每個頁目錄項的內(nèi)容是某個頁表的物理地址。**頁表的大小是 1024*4B=4KB,剛好是一個物理頁的大小。
有人說,這樣不是更占內(nèi)存嗎?原來需要 4MB,現(xiàn)在存放 1024 個頁表就要 4MB,再加上一個頁目錄,還要 4KB,何苦呢?
這樣的層次化分頁結(jié)構(gòu)是每個任務都有的,或者說每個任務都有自己的頁目錄。在處理器內(nèi)部,有一個控制寄存器叫 CR3,存放著當前任務的頁目錄的物理地址,故 CR3 又叫做頁目錄基址寄存器(Page Directory Base Register,PDBR).
每個任務都有自己的 TSS(Task-State Segment ,任務狀態(tài)段),其中就包括了 CR3 寄存器域,存放著任務自己的頁目錄的物理地址。當任務切換時,CR3 寄存器的內(nèi)容也會被更新,更新為新任務的頁目錄的物理地址。
頁目錄和頁表也是普通的頁,混跡于全部的物理頁中。它們和普通的頁沒有什么區(qū)別,無非就是功能不一樣。當任務被操作系統(tǒng)撤銷后,它們和任務所占用的普通的物理頁一樣會被回收。
頁目錄總是在物理內(nèi)存中,頁表可以在需要時再分配,這樣就大大節(jié)省了物理內(nèi)存。這就回答了前面的問題。
5. 地址變換的具體過程
具體怎么變換,還是用書上的例子來說明吧。
假設(shè)段部件輸出的線性地址是 0x00801050,如果沒有開啟分頁,那么這個地址就是物理地址;但是現(xiàn)在開啟了分頁,所以要經(jīng)過頁部件的轉(zhuǎn)換,才能得到物理地址。
處理器的頁部件專門負責線性地址到物理地址的轉(zhuǎn)換工作。它首先將 32 位的線性地址分成 3 段,分別是高 10 位,中間 10 位和低 12 位。高 10 位用來索引頁目錄,中間 10 位用來索引頁表,低 12 位作為頁內(nèi)偏移。
參考資料
【1】《x86匯編語言:從實模式到保護模式》(李忠,電子工業(yè)出版社)
【2】《Linux內(nèi)核完全剖析》(趙炯,機械工業(yè)出版社,2006)
總結(jié)
- 上一篇: 将一个数组a的行和列的元素互换,存到另一
- 下一篇: C 语言内联汇编介绍