1. 程式人生 > >linux記憶體定址解析

linux記憶體定址解析

1.記憶體地址

1.邏輯地址:每一個邏輯地址都有一個段和偏移量組成。

2.線性地址:也叫虛擬地址,是一個32位無符號整數,可以用來表示高達4GB的地址,值得範圍從0x000000000xffffffff

3.實體地址:用於記憶體晶片級記憶體單元定址

記憶體控制單元(MMU)將邏輯地址(通過段對映)轉化成線性地址,再(通過頁對映)轉化成實體地址。

2.硬體中的分段

1.段選擇符(段暫存器 )

   (1.包括:索引、TIRPL

   (2.存放段描述符在段表中的索引號(段編號),TI標誌用於說明是是GDT還是LDT表,RPL標誌許可權。

2.段描述符:段描述符表中的一項,表示一段的資訊:

   (1.段的基地址(Base Address)

:線上性地址空間中段的起始地址。

   (2.段的界限(Limit):即段大小,在虛擬地址空間中,段內可以使用的最大偏移量。

   (3.段的保護屬性(Attribute): 表示段的特性。例如,該段是否可被讀出或寫入,或者該段是否作為一個程式來執行,以及段的特權 級等等。

3.段描述符放在全域性描述符表GDT,或區域性描述符表LDT中。能夠儲存在GDT中的段描述符的最大數目是8191個。

4.段定址過程(得到線性地址

   (1.判斷指令型別,確定使用哪個段暫存器

   (2.讀段暫存器的內容,找到存放段描述資訊的資料結構。(GDTR、LDTR)

①通過TI標誌,判斷本次操作所用段是到全域性段描述表中找,還是到區域性段描述表中找

②讀GDTRLDTR暫存器中存放的地址,找到描述表的首地址

③根據段暫存器中記錄的索引號從描述表首址處偏移,找到第n個描述符,既是要找的段資訊。

   (3.得到基地址

   (4.指令地址做偏移,判斷是否長度越界

   (5.根據指令性質及段描述符中的訪問許可權判斷是否越權

   (6.將基地址與指令中的偏移地址相加得到實際的記憶體地址,完成地址對映。

 

5.保護的表現

1.界限保護

       越界判斷:段長引數

       特權指令:新增的GDTR或LDTR暫存器不存在與舊指令相容問題,訪問他們的指令設定為特權指令,段暫存器的訪問屬性仍然同以往一樣無特權,既保持了相容,又保證了程式無法故意修改段描述進行越界。

2.許可權保護

      根據段的保護屬性判斷是否具有訪問許可權。如,只讀段中不允許寫入。

      系統態、使用者態分離。段暫存器中的後兩位RPL表示請求者操作要求的特權級。GDTR或LDTR中的dpl欄位設定了段的訪問許可權。指令段rpl標誌要求的許可權應不低於dpl規定的級別。特權指令只能在系統態執行。

3.Linux中的分段

1.linux中的四個段

執行在使用者態的所有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

注:Linux中只有4個記憶體段 

2.linux段機制

他們都是從0開始,即意味著使用者態和核心態下的所有程序使用相同的邏輯地址。因為段機制就是將邏輯地址轉化為線性地址,也就是說在linux中分段機制並沒有起到實際的作用,只是走一下過程,而linux下的邏輯地址就等於線性地址

3.GDT和LDT

每個cpu對應一個GDT,所有的GDT都存放在cpu_gdt_table陣列中,而所有GDT的地址和他們的大小被存放在cpu_gdt_descr陣列中。每個GDT包含18個段描述符;其中14個是空的。

Linux中基本不使用LDT

4.硬體中的分頁

為了效率起見,線性地址分成以固定長度為單位的組,稱為頁。

分頁單元把所有的RAM分成固定長度的頁框(也叫物理頁),每個頁框包含一個頁,也就是說一個頁框的長度與一個頁的長度一致。頁框是主存的一部分,也是一個儲存區域。頁只是一個數據塊,可以存放在任何頁框或磁碟中。分頁單元把線性地址轉化成實體地址。

線性地址對映到實體地址的資料結構稱為頁表。頁表放在主存中,並在啟用分頁單元之前必須由核心對頁表進行適當的初始化。

1.常規分頁:

分頁單元處理4KB的頁。32位的線性地址分成3個域:目錄(最高10位)、頁表(中間10位)、偏移量(最低12位)。

每個活動的程序必須有一個分配給它的頁目錄。但沒有必要馬上為程序所有的頁表都分配RAM。正在使用的頁目錄的實體地址存在暫存器cr3中。線性地址中的目錄欄位決定頁目錄中的目錄項,而目錄項指向適當的頁表。地址的頁表字段又決定頁表中的表項,而表項含有頁所在頁框的實體地址。偏移量欄位決定頁框內的相對位置。每一頁含有4096個位元組。

頁目錄和頁表都可以多大1024項,所以一個頁目錄可以定址高達1024*1024*4096=2^32.這和32位地址所期望的一樣。

或者下圖:

2.擴充套件分頁:

它允許頁框大小為4MB。目錄欄位10位,偏移量欄位為22位。

3.實體地址擴充套件(PAE)分頁機制:

32位的實體地址,由於使用者程序線性地址空間的需要,核心不能直接對1GB以上的RAM進行定址。而許多伺服器需要大於4GBRAM來執行數以千計的程序。所以Intel將管教數增加到36,現在處理器的定址能力為2^36=64GB.

4.硬體快取記憶體:

快取記憶體單元插在分頁單元和記憶體之間。

linux設定中,對於所有的頁框都啟用快取記憶體,對於寫操作總是採用回寫策略。

5.轉換後援緩衝器TLB:

除硬體快取外,還採用TLB的快取記憶體來用於加快線性地址的轉換。

5.Linux中的分頁

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

80386使用4K位元組大小的頁,且每頁的起始地址都被4K整除。因此,早期803864GB位元組線性地址空間劃分為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)。因此,803864G位元組的線性地址空間,劃分為1G個頁面,每頁有4K位元組大小。

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

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

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

5.1頁全域性目錄

頁全域性目錄表,最多可包含1024個頁目錄項,每個頁目錄項為4個位元組,算起來正好一個頁面,結構如圖所示:

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


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

·3位是PWTPage Write-Through)位,表示是否採用寫透方式,寫透方式就是既寫記憶體(RAM)也寫快取記憶體,該位為1表示採用寫透方式

·4位是PCDPage Cache Disable)位,表示是否啟用快取記憶體,該位為1表示啟用快取記憶體。

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

·6Dirty標誌,對於頁全域性目錄項,其始終為1
·
7位是Page Size標誌,只適用於頁目錄項。如果置為1,頁目錄項指的是4MB的頁面,請看後面的擴充套件分頁。
·
8位是Global標誌:只應用於頁表項。這個標誌是在Pentium Pro引入的,用來防止常用頁從TLB快取記憶體中刷新出去。只有在cr4暫存器的頁全域性啟用(Page GlobalEnablePGE)標誌置位時這個標誌才起作用。

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

5.2頁表

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

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

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

5.3線性地址到實體地址的轉換

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

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

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

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

下面,我們就通過一個例項來介紹一下常規分頁是如何工作的。我們假定核心已給一個正在執行的程序分配的線性地址空間範圍是0x200000000x2003ffff3GB線性地址空間是一個上限,使用者態程序只是引用其中的一個子集)。這個空間正好由64頁面組成。其實我們並不必關心包含這些頁的頁框的實體地址,為什麼呢?事實上,其中的一些頁甚至可能不在主存中。我們只關注頁表項中剩餘的欄位。程序的線性地址的最高10位開始。這兩個地址都以2開頭後面跟著0,因此高10位有相同的值,即0x080或十進位制的128。因此,這兩個地址的頁目錄(Directory欄位)都指向程序頁目錄的第129項。相應的目錄項中必須包含分配給該程序的頁表的實體地址。如果沒有給這個程序分配其它的線性地址,頁目錄的其餘1023項都填為0

    位的值(即Table欄位的值)範圍從00x03f,或十進位制的從063。因而只有頁表的前64個表項是有意義的,其餘960表項都填0。需要讀線性地址0x20021406中的位元組。這個地址由分頁單元按下面的方法處理:
1. Directory
欄位的0x80用於選擇頁目錄的第0x80目錄項,此目錄項指向和該程序的頁相關的頁表。
2. Table
欄位0x21用於選擇頁表的第0x21表項,此表項指向包含所需頁的頁框。
3.
最後,Offet欄位0x406用於在目標頁框中讀偏移量為0x406中的位元組。


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

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

如上圖所示:

圖中展示的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的程序處理很大程度上依賴於分頁。事實上,線性地址到實體地址的自動轉換使下面的設計目標變得可行:
?
給每一個程序分配一塊不同的實體地址空間,這確保了可以有效地防止定址錯誤。
?
區別頁(即一組資料)和頁框(即主存中的實體地址)之不同。這就允許存放在某個頁框中的一個頁,然後儲存到磁碟上,以後重新裝入這同一頁時又被裝在不同的頁框中。這就是虛擬記憶體機制的基本要素。每一個程序有它自己的頁全域性目錄和自己的頁表集。當發生程序切換時,Linuxcr3控制暫存器的內容儲存在前一個執行程序的描述符中,然後把下一個要執行程序的描述符的值裝入cr3暫存器中。因此,當新程序重新開始在CPU上執行時,分頁單元指向一組正確的頁表。把線性地址對映到實體地址雖然有點複雜,但現在已經成了一種機械式的任務。本章下面的幾節中列舉了一些比較單調乏味的函式和巨集,它們檢索核心為了查詢地址和管理葉表所需的資訊;其中大多數函式只有一兩行。也許現在你就想跳過這部分,但是知道這些函式和巨集的功能是非常有用的,因為在以後章節的討論中你會經常看到它們。

5.4線性地址欄位處理

下列巨集簡化了頁表處理:

PAGE_SHIFT

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

PMD_SHIFT

指定線性地址的OffsetTable欄位的總位數;換句話說,是頁中間目錄項可以對映的區域大小的對數。PMD_SIZE巨集用於計算由頁中間目錄的一個單獨表項所對映的區域大小,也就是一個頁表的大小。PMD_MASK巨集用於遮蔽Offset欄位與Table欄位的所有位。當PAE被禁用時,PMD_SHIFT產生的值為22(來自Offset12位加上來自Table10位),PMD_SIZE產生的值為2224 MBPMD_MASK產生的值為0xffc00000。相反,當PAE被啟用時,PMD_SHIFT產生的值為21 (來自Offset12位加上來自Table9)PMD_SIZE產生的值為2212 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則等於4MB2MB

PGDIR_SHIFT

確定頁全域性頁目錄項能對映的區域大小的對數。PGDIR_SIZE巨集用於計算頁全域性目錄中一個單獨表項所能對映區域的大小。PGDIR_MASK巨集用於遮蔽Offset, TableMiddle AirUpper Air的所有位。當PAE被禁止時,PGDIR_SHIFT產生的值為22(與PMD_SHIFTPUD_SHIFT產生的值相同),PGDIR_SIZE產生的值為2224 MB,以及PGDIR_MASK產生的值為0xffc00000。相反,當PAE被啟用時,PGDIR_SHIFT產生的值為30 (12Offset9Table再加9Middle Air), PGDIR_SIZE產生的值為2301 GB以及PGDIR_MASK產生的值為0xc0000000

PTRS_PER_PTE, PTRS_PER_PMD, PTRS_PER_PUD
以及PTRS_PER_PGD

用於計算頁表、頁中間目錄、頁上級目錄和頁全域性目錄表中表項的個數。當PAE被禁止時,它們產生的值分別為1024111024。當PAE被啟用時,產生的值分別為51251214

5.5頁表處理

pte_t、pmd_tpud_tpgd_t分別描述頁表項、頁中間目錄項、頁上級目錄和頁全域性目錄項的型別格式。當PAE被啟用時它們都是64位的資料型別,否則都是32位資料型別。pgprot_t是另一個64位(PAE啟用時)或32位(PAE禁用時)的資料型別,它表示與一個單獨表項相關的保護標誌。五個型別轉換巨集(__ pte__ pmd__ pud__ pgd__ pgprot)把一個無符號整數轉換成所需的型別。另外的五個型別轉換巨集(pte_valpmd_valpud_valpgd_valpgprot_val)執行相反的轉換,即把上面提到的四種特殊的型別轉換成一個無符號整數。核心還提供了許多巨集和函式用於讀或修改頁表表項:
?
如果相應的表項值為0,那麼,巨集pte_nonepmd_nonepud_nonepgd_none產生的值為1,否則產生的值為0
?
巨集pte_clearpmd_clearpud_clearpgd_clear清除相應頁表的一個表項,由此禁止程序使用由該頁表項對映的線性地址。ptep_get_and_clear( )函式清除一個頁表項並返回前一個值。
? set_pte
set_pmdset_pudset_pgd向一個頁表項中寫入指定的值。set_pte_atomicset_pte作用相同,但是當PAE被啟用時它同樣能保證64位的值能被原子地寫入。
?
如果ab兩個頁表項指向同一頁並且指定相同訪問優先順序,pte_same(a,b)返回1,否則返回0
?
如果頁中間目錄項指向一個大型頁(2MB4MB),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標誌對微處理器的分頁部件來講沒有意義,然而,對於當前在主存中卻又沒有讀、寫或執行許可權的頁,核心將其PresentPage Size分別標記為01。這樣,任何試圖對此類頁的訪問都會引起一個缺頁異常,因為頁的Present標誌被清0,而核心可以通過檢查Page Size的值來檢測到產生異常並不是因為缺頁。如果相應表項的Present標誌等於1,也就是說,如果對應的頁或頁表被裝載入主存,pmd_present巨集產生的值為1pud_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 SizePresent標誌。

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)

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

6.Linux中物理頁面的對映

上圖反映瞭如下資訊:

1、 程序的4G線性空間被劃分成三個部分:程序空間(0-3G)、核心直接對映空間(3G – high_memory)、核心動態對映空間(VMALLOC_START - VMALLOC_END

2、 三個空間使用同一張頁目錄表,通過CR3可找到此頁目錄表。但不同的空間在頁目錄表中頁對應不同的項,因此互相不衝突

3、核心初始化以後,根據實際實體記憶體的大小,計算出high_memoryVMALLOC_STARTVMALLOC_END的值。併為核心直接對映空間建立好對映關係,所有的實體記憶體都可以通過此空間進行訪問。

4程序空間核心動態對映空間的對映關係是動態建立的(通過缺頁異常)

假設在有三個線性地址addr1, addr2, addr3,分別屬於三個線性空間,但是最終都對映到物理頁面1

1、 三個地址對應不同的頁表和頁表項

2、 但是頁表項的高20bit肯定是1,表示物理頁面的索引號是1

3、 同時,根據高20 bit,可以從mem_map[]中找到對應的struct page結構,struct page用於管理實際的物理頁面(紅線)

4、 從線性地址,根據頁目錄表,頁表,可以找到實體地址

5、 和實體地址之間很容易互相轉換

6、 從實體地址,可以很容易的反推出在核心直接對映空間的線性地址(藍線)。核心空間的