1基於80x86的Linux的分段和分頁機制

0386的兩種工作模式:80386的工作模式包括實地址模式和虛地址模式(保護模式)。Linux主要工作在保護模式下。

在保護模式下,80386虛地址空間可達16K個段,每段大小可變,最大達4GB。邏輯地址到線性地址的轉換由80386分段機制管理。段寄存器CS、DS、ES、SS、FS或GS各標識一個段。這些段寄存器作爲段選擇器,用來選擇該段的描述符。

分段邏輯地址到線性地址轉換圖:

分段地址轉換圖

Linux對80386的分段機制使用得很有限,因爲Linux的設計目標是支持絕大多數主流的CPU,而很多CPU使用的是RISC體系結構,並沒有分段機制,所以2.6版內核只有在80x86結構下才使用分段,而且只是象徵性地使用了一下:

 

所有Linux進程僅僅使用四種段來對指令和數據尋址。運行在用戶態的進程使用所謂的用戶代碼段和用戶數據段。類似地,運行在內核態的所有Linux進程都使用一對相同的段對指令和數據尋址:它們分別叫做內核代碼段和內核數據段。下表顯示了這四個重要段的段描述符字段的值:

Base

G

Limit

S

Type

DPL

D/B

P

用戶代碼段

0x00000000

1

0xfffff

1

10

3

1

1

用戶數據段

0x00000000

1

0xfffff

1

2

3

1

1

內核代碼段

0x00000000

1

0xfffff

1

10

0

1

1

內核數據段

0x00000000

1

0xfffff

1

2

0

1

1

相應的段描述符由宏__USER_CS,__USER_DS,__KERNEL_CS,和__KERNEL_DS分別定義。例如,爲了對內核代碼段尋址,內核只需要把這個宏產生的值裝進cs段寄存器即可。 注意,與段相關的線性地址從0開始,達到232 -1的尋址限長。這就意味着在用戶態或內核態下的所有進程可以使用相同的邏輯地址。所有段都從0x00000000開始,這可以得出另一個重要結論,那就是在Linux下邏輯地址與線性地址是一致的,即邏輯地址的偏移量字段的值與相應的線性地址的值總是一致的。

 

如前所述,CPU的當前特權級(CPL)反映了進程是在用戶態還是內核態,並由存放在cs寄存器中的段選擇符的RPL字段指定。只要當前特權級被改變,一些段寄存器必須相應地更新。例如,當CPL=3時(用戶態),ds寄存器必須含有用戶數據段的段選擇符,而當CPL=0時,ds寄存器必須含有內核數據段的段選擇符。

類似的情況也出現在ss寄存器中。當CPL爲3時,它必須指向一個用戶數據段中的用戶棧,而當CPL爲0時,它必須指向內核數據段中的一個內核棧。當從用戶態切換到內核態時,Linux總是確保ss寄存器裝有內核數據段的段選擇符。

當對指向指令或者數據結構的指針進行保存時,內核根本不需要爲其設置邏輯地址的段選擇符,因爲cs寄存器就含有當前的段選擇符。例如,當內核調用一個函數時,它執行一條call彙編語言指令,該指令僅指定它邏輯地址的偏移量部分,而段選擇符不用設置,其隱含在cs寄存器中了。因爲“在內核態執行” 的段只有一種,叫做代碼段,由宏_KERNEL_CS定義,所以只要當CPU切換入內核態時足可以將__KERNEL_CS裝載入cs。同樣的道理也適用於指向內核數據結構的指針(隱含地使用ds寄存器)以及指向用戶數據結構的指針(內核顯式地使用es寄存器)。

 

2 基於80x86的Linux分頁機制


Linux分頁機制的作用:分頁機制是在段機制之後進行的,它進一步將線性地址轉換爲物理地址。我們先來看看硬件構造:

 

80386使用4K字節大小的頁,且每頁的起始地址都被4K整除。因此,早期80386把4GB字節線性地址空間劃分爲1M個頁面,採用了兩級表結構。

兩級表的第一級表稱爲頁目錄,存儲在一個4K字節的頁中,頁目錄表共有1K個表項,每個表項爲4個字節,線性地址最高的10位(22-31)用來產生第一級表索引,由該索引得到的表項中的內容定位了二級表中的一個表的地址,即下級頁表所在的內存塊號。

第二級表稱爲頁表,存儲在一個4K字節頁中,它包含了1K字節的表項,每個表項包含了一個頁的物理地址。二級頁表由線性地址的中間10位(12-21)位進行索引,定位頁表表項,獲得頁的物理地址。頁物理地址的高20位與線性地址的低12位形成最後的物理地址。

利用兩級頁錶轉換地址圖:

頁表地址轉換


80x86的分頁機制由CR0中的PG位啓用。如PG=1,啓用分頁機制,並使用本節要描述的機制,把線性地址轉換爲物理地址。如PG=0,禁用分頁機制,直接把前面段機制產生的線性地址當作物理地址使用。

 

80386使用4K字節大小的頁。每一頁都有4K字節長,並在4K字節的邊界上對齊,即每一頁的起始地址都能被4K整除(物理地址最低12位爲0)。因此,80386把4G字節的線性地址空間,劃分爲1G個頁面,每頁有4K字節大小。

 

分頁機制通過把線性地址空間中的頁,重新定位到物理地址空間來進行管理,因爲每個頁面的整個4K字節作爲一個單位進行映射,並且每個頁面都對齊4K字節的邊界,因此,線性地址的低12位經過分頁機制直接地作爲物理地址的低12位使用。

 

線性/物理地址的轉換,可將其意義擴展爲允許將一個線性地址標記爲無效,而不是實際地產生一個物理地址。有兩種情況可能使頁被標記爲無效:其一是線性地址是操作系統不支持的地址;其二是在虛擬存儲器系統中,線性地址對應的頁存儲在磁盤上,而不是存儲在RAM存儲器中。在前一種情況下,程序因產生了無效地址而必須被終止。

 

對於後一種情況,該無效的地址實際上是請求操作系統的虛擬存儲管理系統,把存放在磁盤上的頁傳送到物理存儲器中,使該頁能被程序所訪問。由於無效頁通常是與虛擬存儲系統相聯繫的,這樣的無效頁通常稱爲未駐留頁,並且用頁表屬性位中叫做存在位的屬性位進行標識。未駐留頁是程序可訪問的頁,但它不在主存儲器中。對這樣的頁進行訪問,形式上是發生異常,實際上是通過異常進行缺頁處理。

 

2.1 頁全局目錄

 

頁全局目錄表,最多可包含1024個頁目錄項,每個頁目錄項爲4個字節,算起來正好一個頁面,結構如圖所示:

頁全局目錄

 


·第0位是存在位,Present標誌:如果被置爲1,所指的頁(或頁表)就在主存中;如果該標誌爲0,則這一頁不在主存中,此時這個表項剩餘的位可由操作系統用於自己的目的。如果執行一個地址轉換所需的頁表項或頁目錄項中Present標誌被清0,那麼分頁單元就把該線性地址存放在控制寄存器cr2中,併產生14號異常:缺頁異常。(我們將在後面的一系列博客中重點討論Linux如何使用這個字段)。


·第1位是讀/寫位,第2位是用戶/管理員位,Read/Write標誌:含有頁或頁表的存取權限(Read/Write或Read);User/Supervisor標誌:含有訪問頁或頁表所需的特權級。這兩位爲頁目錄項提供硬件保護。當特權級爲3的進程要想訪問頁面時,需要通過頁保護檢查,而特權級爲0的進程就可以繞過頁保護,如圖所示:

頁面讀寫標誌

 

·第3位是PWT(Page Write-Through)位,表示是否採用寫透方式,寫透方式就是既寫內存(RAM)也寫高速緩存,該位爲1表示採用寫透方式

 

·第4位是PCD(Page Cache Disable)位,表示是否啓用高速緩存,該位爲1表示啓用高速緩存。

 

·第5位是訪問位,Accessed標誌:當對頁目錄項進行訪問時,A位=1。每當分頁單元對相應頁框進行尋址時就設置這個標誌。當選中的頁被交換出去時,這一標誌就可以由操作系統使用。分頁單元從不重置這個標誌;而是必須由操作系統去做。

 

·第6位Dirty標誌,對於頁全局目錄項,其始終爲1。


·第7位是Page Size標誌,只適用於頁目錄項。如果置爲1,頁目錄項指的是4MB的頁面,請看後面的擴展分頁。


·第8位是Global 標誌:只應用於頁表項。這個標誌是在Pentium Pro引入的,用來防止常用頁從TLB高速緩存中刷新出去。只有在cr4寄存器的頁全局啓用(Page GlobalEnable ,PGE)標誌置位時這個標誌才起作用。

 

·第9~11位由操作系統專用,Linux也沒有做特殊之用

 

2.2 頁表

80386的每個頁目錄項指向一個頁表,頁表最多含有1024個頁面項,每項4個字節,包含頁面的起始地址和有關該頁面的信息。頁面的起始地址也是4K的整數倍,所以頁面的低12位也留作它用,如圖所示。

頁目錄項

 

 

第31~12位是20位物理頁面地址,除第6位外第0~5位及9~11位的用途和頁目錄項一樣,第6位是頁面項獨有的,當對涉及的頁面進行寫操作時,D位被置1。

 

4GB的存儲器只有一個頁目錄,它最多有1024個頁目錄項,每個頁目錄項又含有1024個頁面項,因此,存儲器一共可以分成1024×1024=1M個頁面。由於每個頁面爲4K個字節,所以,存儲器的大小正好最多爲4GB。

2.3 線性地址到物理地址的轉換

 

當訪問一個操作單元時,如何由分段結構確定的32位線性地址通過分頁操作轉化成32位物理地址呢?過程如圖所示。

線性地址轉換

 

第一步,CR3包含着頁目錄的起始地址,用32位線性地址的最高10位A31~A22作爲頁目錄的頁目錄項的索引,將它乘以4,與CR3中的頁目錄的起始地址相加,形成相應頁表的地址。

 

第二步,從指定的地址中取出32位頁目錄項,它的低12位爲0,這32位是頁表的起始地址。用32位線性地址中的A21~A12位作爲頁表中的頁面的索引,將它乘以4,與頁表的起始地址相加,形成32位頁面地址。

 

第三步,將A11~A0作爲相對於頁面地址的偏移量,與32位頁面地址相加,形成32位物理地址。

 

下面,我們就通過一個實例來介紹一下常規分頁是如何工作的。我們假定內核已給一個正在運行的進程分配的線性地址空間範圍是0x20000000 到 0x2003ffff(3GB線性地址空間是一個上限,用戶態進程只是引用其中的一個子集)。這個空間正好由64頁面組成。其實我們並不必關心包含這些頁的頁框的物理地址,爲什麼呢?事實上,其中的一些頁甚至可能不在主存中。我們只關注頁表項中剩餘的字段。


讓我們從分配給進程的線性地址的最高10位開始。這兩個地址都以2開頭後面跟着0,因此高10位有相同的值,即0x080或十進制的128。因此,這兩個地址的頁目錄(Directory字段)都指向進程頁目錄的第129項。相應的目錄項中必須包含分配給該進程的頁表的物理地址。如果沒有給這個進程分配其它的線性地址,頁目錄的其餘1023項都填爲0。

 

中間10位的值(即Table字段的值)範圍從0到0x03f,或十進制的從0到63。因而只有頁表的前64個表項是有意義的,其餘960表項都填0。


假設進程需要讀線性地址0x20021406中的字節。這個地址由分頁單元按下面的方法處理:
1. Directory字段的0x80用於選擇頁目錄的第0x80目錄項,此目錄項指向和該進程的頁相關的頁表。
2. Table字段0x21用於選擇頁表的第0x21表項,此表項指向包含所需頁的頁框。
3. 最後,Offet字段0x406用於在目標頁框中讀偏移量爲0x406中的字節。


如果頁表第0x21表項的Present標誌爲0,則此頁就不在主存中;在這種情況下,分頁單元在線性地址轉換的同時產生一個缺頁異常。無論何時,當進程試圖訪問限定在0x20000000到0x2003ffff範圍之外的線性地址時,都將產生一個缺頁異常,因爲這些頁表項都填充了0,尤其是它們的Present標誌都被清0。

 

當今,Linux採用了一種同時適用於32位和64位系統的普通分頁模型。前面我們看到,兩級頁表對32位系統來說已經足夠了,但64位系統需要更多數量的分頁級別。直到2.6.10版本,Linux採用三級分頁的模型。從2.6.11版本開始,採用了四級分頁模型:

 

Linux2.6分頁

 

圖中展示的4種頁表分別被稱作:
• 頁全局目錄(Page Global Directory)
• 頁上級目錄(Page Upper Directory)
• 頁中間目錄(Page Middle Directory)
• 頁表(Page Table)

頁全局目錄包含若干頁上級目錄的地址,頁上級目錄又依次包含若干頁中間目錄的地址,而頁中間目錄又包含若干頁表的地址。每一個頁表項指向一個頁框。線性地址因此被分成五個部分。圖中沒有顯示位數,因爲每一部分的大小與具體的計算機體系結構有關。

對於沒有啓用物理地址擴展的32位系統,兩級頁表已經足夠了。從本質上說Linux通過使“頁上級目錄”位和“頁中間目錄”位全爲0,徹底取消了頁上級目錄和頁中間目錄字段。不過,頁上級目錄和頁中間目錄在指針序列中的位置被保留,以便同樣的代碼在32位系統和64位系統下都能使用。內核爲頁上級目錄和頁中間目錄保留了一個位置,這是通過把它們的頁目錄項數設置爲1,並把這兩個目錄項映射到頁全局目錄的一個合適的目錄項而實現的。

啓用了物理地址擴展的32 位系統使用了三級頁表。Linux 的頁全局目錄對應80x86 的頁目錄指針表(PDPT),取消了頁上級目錄,頁中間目錄對應80x86的頁目錄,Linux的頁表對應80x86的頁表。

最終,64位系統使用三級還是四級分頁取決於硬件對線性地址的位的劃分。

那麼,爲什麼Linux是如此地熱衷使用分頁技術而對分段機制表現得那麼地冷淡呢,因爲Linux的進程處理很大程度上依賴於分頁。事實上,線性地址到物理地址的自動轉換使下面的設計目標變得可行:
• 給每一個進程分配一塊不同的物理地址空間,這確保了可以有效地防止尋址錯誤。
• 區別頁(即一組數據)和頁框(即主存中的物理地址)之不同。這就允許存放在某個頁框中的一個頁,然後保存到磁盤上,以後重新裝入這同一頁時又被裝在不同的頁框中。這就是虛擬內存機制的基本要素。

每一個進程有它自己的頁全局目錄和自己的頁表集。當發生進程切換時,Linux把cr3控制寄存器的內容保存在前一個執行進程的描述符中,然後把下一個要執行進程的描述符的值裝入cr3寄存器中。因此,當新進程重新開始在CPU上執行時,分頁單元指向一組正確的頁表。

把線性地址映射到物理地址雖然有點複雜,但現在已經成了一種機械式的任務。本章下面的幾節中列舉了一些比較單調乏味的函數和宏,它們檢索內核爲了查找地址和管理葉表所需的信息;其中大多數函數只有一兩行。也許現在你就想跳過這部分,但是知道這些函數和宏的功能是非常有用的,因爲在以後章節的討論中你會經常看到它們。

 

2.4 線性地址字段處理

 

下列宏簡化了頁表處理:

PAGE_SHIFT

指定Offset字段的位數;當用於80x86處理器時,它返回的值爲12。由於頁內所有地址都必須放在Offset字段, 因此80x86系統的頁的大小是212 =4096字節。 PAGE_MASK宏產生的值爲0xfffff000,用以屏蔽Offset字段的所有位。

PMD_SHIFT

指定線性地址的Offset和Table字段的總位數;換句話說,是頁中間目錄項可以映射的區域大小的對數。PMD_SIZE 宏用於計算由頁中間目錄的一個單獨表項所映射的區域大小,也就是一個頁表的大小。PMD_MASK宏用於屏蔽Offset字段與Table字段的所有位。當PAE 被禁用時,PMD_SHIFT 產生的值爲22(來自Offset 的12 位加上來自Table 的10 位),PMD_SIZE 產生的值爲222 或 4 MB, PMD_MASK產生的值爲 0xffc00000。相反,當PAE被激活時,PMD_SHIFT 產生的值爲21 (來自Offset的12位加上來自Table的9位), PMD_SIZE 產生的值爲221 或2 MB以及PMD_MASK產生的值爲 0xffe00000。大型頁不使用最後一級頁表,所以產生大型頁尺寸的LARGE_PAGE_SIZE 宏等於PMD_SIZE(2PMD_SHIFT),而在大型頁地址中用於屏蔽Offset字段和Table字段的所有位的LARGE_PAGE_MASK宏,就等於PMD_MASK。

PUD_SHIFT
確定頁上級目錄項能映射的區域大小的對數。PUD_SIZE宏用於計算頁全局目錄中的一個單獨表項所能映射的區域大小。PUD_MASK宏用於屏蔽Offset字段,Table字段,Middle Air字段和Upper Air字段的所有位。在80x86處理器上,PUD_SHIFT總是等價於PMD_SHIFT,而PUD_SIZE則等於4MB或2MB。

PGDIR_SHIFT

確定頁全局頁目錄項能映射的區域大小的對數。 PGDIR_SIZE宏用於計算頁全局目錄中一個單獨表項所能映射區域的大小。PGDIR_MASK宏用於屏蔽Offset, Table,Middle Air及Upper Air的所有位。當PAE 被禁止時,PGDIR_SHIFT 產生的值爲22(與PMD_SHIFT 和PUD_SHIFT 產生的值相同),PGDIR_SIZE 產生的值爲 222 或 4 MB,以及 PGDIR_MASK 產生的值爲 0xffc00000。相反,當PAE被激活時,PGDIR_SHIFT 產生的值爲30 (12 位Offset 加 9 位Table再加 9位 Middle Air), PGDIR_SIZE 產生的值爲230 或 1 GB以及PGDIR_MASK產生的值爲0xc0000000。

PTRS_PER_PTE, PTRS_PER_PMD, PTRS_PER_PUD以及PTRS_PER_PGD

用於計算頁表、頁中間目錄、頁上級目錄和頁全局目錄表中表項的個數。當PAE被禁止時,它們產生的值分別爲1024,1,1和1024。當PAE被激活時,產生的值分別爲512,512,1和4。

 

2.5 頁表處理

 

pte_t、pmd_t、pud_t和 pgd_t分別描述頁表項、頁中間目錄項、頁上級目錄和頁全局目錄項的類型格式。當PAE被激活時它們都是64位的數據類型,否則都是32位數據類型。pgprot_t是另一個64位(PAE激活時)或32位(PAE禁用時)的數據類型,它表示與一個單獨表項相關的保護標誌。

五個類型轉換宏(__ pte、__ pmd、__ pud、__ pgd和__ pgprot)把一個無符號整數轉換成所需的類型。另外的五個類型轉換宏(pte_val,pmd_val, pud_val, pgd_val和pgprot_val)執行相反的轉換,即把上面提到的四種特殊的類型轉換成一個無符號整數。

內核還提供了許多宏和函數用於讀或修改頁表表項:
• 如果相應的表項值爲0,那麼,宏pte_none、pmd_none、pud_none和 pgd_none產生的值爲1,否則產生的值爲0。
• 宏pte_clear、pmd_clear、pud_clear和 pgd_clear清除相應頁表的一個表項,由此禁止進程使用由該頁表項映射的線性地址。ptep_get_and_clear( )函數清除一個頁表項並返回前一個值。
• set_pte,set_pmd,set_pud和set_pgd向一個頁表項中寫入指定的值。set_pte_atomic與set_pte作用相同,但是當PAE被激活時它同樣能保證64位的值能被原子地寫入。
• 如果a和b兩個頁表項指向同一頁並且指定相同訪問優先級,pte_same(a,b)返回1,否則返回0。
• 如果頁中間目錄項指向一個大型頁(2MB或4MB),pmd_large(e)返回1,否則返回0。

宏pmd_bad由函數使用並通過輸入參數傳遞來檢查頁中間目錄項。如果目錄項指向一個不能使用的頁表,也就是說,如果至少出現以下條件中的一個,則這個宏產生的值爲1:
• 頁不在主存中(Present標誌被清除)。
• 頁只允許讀訪問(Read/Write標誌被清除)。
• Acessed或者Dirty位被清除(對於每個現有的頁表,Linux總是強制設置這些標誌)。

pud_bad宏和pgd_bad宏總是產生0。沒有定義pte_bad宏,因爲頁表項引用一個不在主存中的頁,一個不可寫的頁或一個根本無法訪問的頁都是合法的。

如果一個頁表項的Present標誌或者Page Size標誌等於1,則pte_present宏產生的值爲1,否則爲0。前面講過頁表項的Page Size標誌對微處理器的分頁部件來講沒有意義,然而,對於當前在主存中卻又沒有讀、寫或執行權限的頁,內核將其Present和Page Size分別標記爲0和1。這樣,任何試圖對此類頁的訪問都會引起一個缺頁異常,因爲頁的Present標誌被清0,而內核可以通過檢查Page Size的值來檢測到產生異常並不是因爲缺頁。

如果相應表項的Present標誌等於1,也就是說,如果對應的頁或頁表被裝載入主存,pmd_present宏產生的值爲1。pud_present宏和pgd_present宏產生的值總是1。

下表中列出的函數用來查詢頁表項中任意一個標誌的當前值;除了pte_file()外,其他函數只有在pte_present返回1的時候,才能正常返回頁表項中任意一個標誌。

 

函數名稱

說明

pte_user( )

讀 User/Supervisor 標誌。

pte_read( )

讀 User/Supervisor 標誌(表示 80x86 處理器上的頁不受讀的保護)。

pte_write( )

讀 Read/Write 標誌。

pte_exec( )

讀 User/Supervisor 標誌( 80x86 處理器上的頁不受代碼執行的保護)。

pte_dirty( )

讀 Dirty 標誌。

pte_young( )

讀 Accessed 標誌。

pte_file( )

讀 Dirty 標誌(當 Present 標誌被清除而 Dirty 標誌被設置時,頁屬於一個非線性磁盤文件映射)。

下表列出的另一組函數用於設置頁表項中各標誌的值:

函數名稱

說明

mk_pte_huge( )

設置頁表項中的 Page Size 和 Present 標誌。

pte_wrprotect( )

清除 Read/Write 標誌。

pte_rdprotect( )

清除 User/Supervisor 標誌。

pte_exprotect( )

清除 User/Supervisor 標誌。

pte_mkwrite( )

設置 Read/Write 標誌。

pte_mkread( )

設置 User/Supervisor 標誌。

pte_mkexec( )

設置 User/Supervisor 標誌。

pte_mkclean( )

清除 Dirty 標誌。

pte_mkdirty( )

設置 Dirty 標誌。

pte_mkold( )

清除 Accessed 標誌(把此頁標記爲未訪問)。

pte_mkyoung( )

設置 Accessed 標誌(把此頁標記爲訪問過)。

pte_modify(p,v)

把頁表項 p 的所有訪問權限設置爲指定的值 v 。

ptep_set_wrprotect()

與 pte_wrprotect( ) 類似,但作用於指向頁表項的指針。

ptep_set_access_flags( )

如果 Dirty 標誌被設置爲 1 則將頁的訪問權設置爲指定的值,並調用 flush_tlb_page() 函數。

ptep_mkdirty( )

與 pte_mkdirty( ) 類似,但作用於指向頁表項的指針。

ptep_test_and_clear_dirty( )

與 pte_mkclean( ) 類似,但作用於指向頁表項的指針並返回 Dirty 標誌的舊值。

ptep_test_and_clear_young( )

與 pte_mkold( ) 類似,但作用於指向頁表項的指針並返回 Accessed 標誌的舊值。

現在,我們來討論下表中列出的宏,它們把一個頁地址和一組保護標誌組合成頁表項,或者執行相反的操作,從一個頁表項中提取出頁地址。請注意這其中的一些宏對頁的引用是通過 “頁描述符”的線性地址,而不是通過該頁本身的線性地址。

宏名稱

說明

pgd_index(addr)

找到線性地址 addr 對應的的目錄項在頁全局目錄中的索引(相對位置)。

pgd_offset(mm, addr)

接收內存描述符地址 mm 和線性地址 addr 作爲參數。這個宏產生地址 addr 在頁全局目錄中相應表項的線性地址;通過內存描述符 mm 內的一個指針可以找到這個頁全局目錄。

pgd_offset_k(addr)

產生主內核頁全局目錄中的某個項的線性地址,該項對應於地址 addr 。

pgd_page(pgd)

通過頁全局目錄項 pgd 產生頁上級目錄所在頁框的頁描述符地址。在兩級或三級分頁系統中,該宏等價於 pud_page() ,後者應用於頁上級目錄項。

pud_offset(pgd, addr)

參數爲指向頁全局目錄項的指針 pgd 和線性地址 addr 。這個宏產生頁上級目錄中目錄項 addr 對應的線性地址。在兩級或三級分頁系統中,該宏產生 pgd ,即一個頁全局目錄項的地址。

pud_page(pud)

通過頁上級目錄項 pud 產生相應的頁中間目錄的線性地址。在兩級分頁系統中,該宏等價於 pmd_page() ,後者應用於頁中間目錄項。

pmd_index(addr)

產生線性地址 addr 在頁中間目錄中所對應目錄項的索引(相對位置)。

pmd_offset(pud, addr)

接收指向頁上級目錄項的指針 pud 和線性地址 addr 作爲參數。這個宏產生目錄項 addr 在頁中間目錄中的偏移地址。在兩級或三級分頁系統中,它產生 pud ,即頁全局目錄項的地址。

pmd_page(pmd)

通過頁中間目錄項 pmd 產生相應頁表的頁描述符地址。在兩級或三級分頁系統中, pmd 實際上是頁全局目錄中的一項。

mk_pte(p,prot)

接收頁描述符地址 p 和一組訪問權限 prot 作爲參數,並創建相應的頁表項。

pte_index(addr)

產生線性地址 addr 對應的表項在頁表中的索引(相對位置)。

pte_offset_kernel(dir,addr)

線性地址 addr 在頁中間目錄 dir 中有一個對應的項,該宏就產生這個對應項,即頁表的線性地址。另外,該宏只在主內核頁表上使用。

pte_offset_map(dir, addr)

接收指向一個頁中間目錄項的指針 dir 和線性地址 addr 作爲參數,它產生與線性地址 addr 相對應的頁表項的線性地址。如果頁表被保存在高端存儲器中,那麼內核建立一個臨時內核映射,並用 pte_unmap 對它進行釋放。 pte_offset_map_nested 宏和 pte_unmap_nested 宏是相同的,但它們使用不同的臨時內核映射。

pte_page( x )

返回頁表項 x 所引用頁的描述符地址。

pte_to_pgoff( pte )

從一個頁表項的 pte 字段內容中提取出文件偏移量,這個偏移量對應着一個非線性文件內存映射所在的頁。

pgoff_to_pte(offset )

爲非線性文件內存映射所在的頁創建對應頁表項的內容。

下面我們羅列最後一組函數來簡化頁表項的創建和撤消。當使用兩級頁表時,創建或刪除一個頁中間目錄項是不重要的。如本節前部分所述,頁中間目錄僅含有一個指向下屬頁表的目錄項。所以,頁中間目錄項只是頁全局目錄中的一項而已。然而當處理頁表時,創建一個頁表項可能很複雜,因爲包含頁表項的那個頁表可能就不存在。在這樣的情況下,有必要分配一個新頁框,把它填寫爲 0 ,並把這個表項加入。

如果 PAE 被激活,內核使用三級頁表。當內核創建一個新的頁全局目錄時,同時也分配四個相應的頁中間目錄;只有當父頁全局目錄被釋放時,這四個頁中間目錄才得以釋放。當使用兩級或三級分頁時,頁上級目錄項總是被映射爲頁全局目錄中的一個單獨項。與以往一樣,下表中列出的函數描述是針對 80x86 構架的。

函數名稱

說明

pgd_alloc( mm )

分配一個新的頁全局目錄。如果 PAE 被激活,它還分配三個對應用戶態線性地址的子頁中間目錄。參數 mm( 內存描述符的地址 ) 在 80x86 構架上被忽略。

pgd_free( pgd)

釋放頁全局目錄中地址爲 pgd 的項。如果 PAE 被激活,它還將釋放用戶態線性地址對應的三個頁中間目錄。

pud_alloc(mm, pgd, addr)

在兩級或三級分頁系統下,這個函數什麼也不做:它僅僅返回頁全局目錄項 pgd 的線性地址。

pud_free(x)

在兩級或三級分頁系統下,這個宏什麼也不做。

pmd_alloc(mm, pud, addr)

 

定義這個函數以使普通三級分頁系統可以爲線性地址 addr 分配一個新的頁中間目錄。如果 PAE 未被激活,這個函數只是返回輸入參數 pud 的值,也就是說,返回頁全局目錄中目錄項的地址。如果 PAE 被激活,該函數返回線性地址 addr 對應的頁中間目錄項的線性地址。參數 mm 被忽略。

pmd_free(x)

該函數什麼也不做,因爲頁中間目錄的分配和釋放是隨同它們的父全局目錄一同進行的。

pte_alloc_map(mm, pmd, addr)

接收頁中間目錄項的地址 pmd 和線性地址 addr 作爲參數,並返回與 addr 對應的頁表項的地址。如果頁中間目錄項爲空,該函數通過調用函數 pte_alloc_one( ) 分配一個新頁表。如果分配了一個新頁表, addr 對應的項就被創建,同時 User/Supervisor 標誌被設置爲 1 。如果頁表被保存在高端內存,則內核建立一個臨時內核映射,並用 pte_unmap 對它進行釋放。

pte_alloc_kernel(mm, pmd, addr)

如果與地址 addr 相關的頁中間目錄項 pmd 爲空,該函數分配一個新頁表。然後返回與 addr 相關的頁表項的線性地址。該函數僅被主內核頁表使用。

pte_free(pte)

釋放與頁描述符指針 pte 相關的頁表。

pte_free_kernel(pte)

等價於 pte_free( ) ,但由主內核頁表使用。

clear_page_range(mmu, start,end)

從線性地址 start 到 end 通過反覆釋放頁表和清除頁中間目錄項來清除進程頁表的內容。

 

3 擴展分頁

 

   從奔騰處理器開始,Intel微處理器引進了擴展分頁,它允許頁的大小爲4MB,如圖所示:

PAE

 

   在擴展分頁的情況下,分頁機制把32位線性地址分成兩個域:最高10位的目錄域和其餘22位的偏移量。

 

4 頁面高速緩存

 

由於在分頁情況下,每次存儲器訪問都要存取兩級頁表,這就大大降低了訪問速度。所以,爲了提高速度,在386中設置一個最近存取頁面的高速緩存硬件機制,它自動保持32項處理器最近使用的頁面地址,因此,可以覆蓋128K字節的存儲器地址。當進行存儲器訪問時,先檢查要訪問的頁面是否在高速緩存中,如果在,就不必經過兩級訪問了,如果不在,再進行兩級訪問。平均來說,頁面高速緩存大約有98%的命中率,也就是說每次訪問存儲器時,只有2%的情況必須訪問兩級分頁機構。這就大大加快了速度,頁面高速緩存的作用如圖所示。有些書上也把頁面高速緩存叫做“聯想存儲器”或“轉換旁視緩衝器(TLB)”。

 

tbl


發佈了0 篇原創文章 · 獲贊 1 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章