本文只是個人的學習筆記,作為個人復習自用,侵刪!!!
1、段機制概述
段機制就是把虛擬地址空間中的虛擬內存組織成一些長度可變的內存塊單元。
2、段機制基礎——段寄存器
2.1 段
每個段由3部分參數定義:段基地址、段限長和段屬性
需要8個字節(64bit)存儲這些信息。
2.2 段寄存器
要想知道內存單元是如何組織的,就得先來認識段寄存器。
段寄存器其實因內存的分段管理機制而起,計算機需要對內存分段,以分配給不同的程序使用。如 2.1 所述的只需要64位存儲這些信息,而段寄存器只有16位,因此段寄存器中只能存儲段選擇符,再由段號映射到內存的GDT表中,讀取段的信息。
8個96位的段寄存器
1、CS 代碼段寄存器
存放當前正在運行的程序代碼所在段的段基址,表示當前使用的指令代碼可以從該段寄存器指定的存儲器段中取得,相應的偏移量則由IP提供。
2、DS 數據段寄存器
指出當前程序使用的數據所存放段的最低地址,即存放數據段的段基址。
3、SS 堆棧段寄存器
指出當前堆棧的底部地址,即存放堆棧段的段基址。
4、ES 附加段寄存器
指出當前程序使用附加數據段的段基址,該段是串操作指令中目的串所在的段。
5、FS 標志寄存器
在ring0中指向KPCR,在ring3中的程序,fs:[0] 指向TEB
6、GS 全局段寄存器
用於管理線程特定的內存
7、LDTR 段選擇子
記錄局部描述符表的起始位置,LDTR的內容是一個段選擇子。由於LDT本身同樣是一段內存,也是一個段,所以它也有個描述符描述它,這個描述符就存儲在GDT中,對應這個表述符也會有一個選擇子,LDTR裝載的就是這個一個選擇子。
LDTR可以在程序中隨時改變,通過使用lldt指令。
8、TR 任務寄存器
用於尋址一個特殊的任務狀態段(TSS),TSS包含當前執行任務的重要信息。
TR寄存器用於存放當前任務TSS段的16位段選擇符、32位基地址、16位段長度和描述符屬性值。
它引用GDT表中的一個TSS類型的描述符。指令LTR和STR分別用於加載和保存TR寄存器的段選擇符部分。
當使用LTR指令把選擇符加載進任務寄存器時,TSS描述符中的段基地址、段限長度以及描述符屬性會被自動加載到任務寄存器中。
當執行任務切換時,處理器會把新任務的TSS的段選擇符和段描述符自動加載進任務寄存器TR中。
段寄存器的結構
段寄存器總共有96位,其中80位是不可見的,16位是可見的,這就是為啥我們在OD中只能看到16位的原因。
Ø讀段寄存器
比如:mov ax, es 只能讀16位的可見部分
讀寫LDTR的指令為:SLDT/LLDT
讀寫TR的指令為:STR/LTR
Ø寫段寄存器
如:MOV DS,AX 寫時是寫96位
3、段寄存器——屬性探測
3.1 段寄存器成員
段寄存器 | Selector 16位 | Attribute 16位 | Base 32位 | Limit 32位 |
---|---|---|---|---|
ES | 0023 | 可讀、可寫 | 0 | 0xFFFFFFFF |
CS | 001B | 可讀、可執行 | 0 | 0xFFFFFFFF |
SS | 0023 | 可讀、可寫 | 0 | 0xFFFFFFFF |
DS | 0023 | 可讀、可寫 | 0 | 0xFFFFFFFF |
FS | 003B | 可讀、可寫 | 0x7FFDE000 | 0xFFF |
GS | - | - | - | - |
3.2 探測Attribute
int var = 0;
__asm
{
mov ax,ss //cs不行: cs是可讀 可執行 但不可寫
mov ds,ax
mov dword ptr ds:[var],eax
}
3.3 探測Base
int var = 1;
__asm
{
mov ax,fs
mov gs,ax
mov eax,gs:[0] //0地址不能讀也不能寫,這里對0地址進行讀的操作是gs.base+0=
mov dword ptr ds:[var],eax
//mov edx,dword ptr ds:[0x7FFDF000]
}
3.4 探測Limit屬性是否存在
int var = 1;
__asm
{
mov ax,fs
mov gs,ax
mov eax,gs:[0x1000]
// 訪問的地址相當於下面這行注釋的代碼 但DS的Limit是0xFFFFFFFF
// mov eax,dword ptr ds:[0x7FFDF000+0x1000]
mov dword ptr ds:[var],eax
}
寫段寄存器的時候只有16位,還有另外的80位從哪來?
64位段描述符
段描述符共有64位,如何擴充到80位?
P位
如果P=1段描述符有效,P=0無效
回顧一下這個段寄存器的結構,理清如何實現由64位擴充到80位的。
WORD Selector; //16位,已確定
WORD Atrribute; //16位
DWORD Base; //32位 ,3部分組成
DWORD Limit; //32位
Selector已經確定,主要是理清楚 身下的80位的對應關系
這里重點關注Limit,Limit最大為FFFFF,當G位為0時,Limit為000FFFFF;當G為1時,Limit為FFFFF000。
備注:為什么Limit最大為FFFFF呢?
我們來看看低4字節的015位,高4字節的1619位,總共為5字節,最大為5個F,即FFFFF。
由此就解釋了,如何由64位擴充到80位。
4、段權限檢查
CPU分為4級,我們主要用到的就是Ring3和Ring0。
4.1 如何查看程序在幾環
通過CS、SS中存儲的段選擇子后2位可知當前所屬的特權級(CPL)
4.2 如何進行權限檢查
DPL存出在段描述符中,規定了訪問該段所需要的特權級是什么
當CPL>DPL時,不允許訪問。
RPL請求特權級別,是針對端選擇子而言的,每個段的選擇子都有自己的RPL。
數據段的權限檢查
當CPL<=DPL & RPL<=DPL 時,即可實現數據段的訪問。
有了CPL為何還要RPL,有這樣一種情況:本可用“讀 寫”的權限去打開一個文件,但為了避免出錯,有些時候我們使用“只讀”的權限去打開。
5、代碼跨段跳轉流程
5.1 代碼間的跳轉(段間跳轉,非調用門之類的)
兩種情況:1、要跳轉的段是一致代碼段還是非一致代碼段
2、同時修改CS與EIP的指令
如:JMP FAR 、CALL FAR、RETF、INT、IRETED
執行流程:
1、段選擇子拆分
2、查表得到段描述符
3、權限檢查
4、加載段描述符
5、代碼執行
例子:CPU如何執行 JMP 0x20:0x004183D7這個指令?
1、拆分 0x20的段選擇子
0x20 = 0000 0000 0010 0000
RPL = 00 TI=0 Index=100
2、查表得到段描述符
TI=0 => 查GDT表
Index=4 => 找到對應的段描述符
3、權限檢查
如果是非一致代碼段,要求:CPL==DPL & RPL<=DPL
如果是一致代碼段,要求:CPL>=DPL
4、加載段描述符
通過上面的權限檢查后,CPU會將段描述符加載到CS段寄存器中。
5、代碼執行
CPU將CS.Base+Offset的值寫入EIP,然后執行CS:EIP處的代碼,段間跳轉結束。
總結
對於一致代碼段(共享的段):
1、特權級高的程序不允許訪問特權級低的數據,核心態不允許訪問用戶態的數據
2、特權級低的程序可以訪問到特權級高的數據,但特權級不會改變,用戶態還是用戶態
對於普通代碼段(非一致代碼段):
1、只允許同級訪問
2、絕對禁止不同級別的訪問
5.2 跨段調用
前面我們通過JMP FAR實現了段間的跳轉,如果要實現跨段的調用就必須用CALL FAR,實現長調用。
下面我們來分析一下CALL FAR(長調用)和CALL(段調用)指令的區別:
1、短調用
CALL指令執行前后堆棧的變化如下:在跳轉前,先將當前的ESP壓棧,進入到另一函數的堆棧領空,通過RET跳到CALL指令執行前的ESP,如此來實現短調用。這個過程中,發生改變的寄存器有 ESP EIP。
2、長調用(跨段不提權)
形如 CALL CS:EIP 執行前后的堆棧圖如下:
長調用會先將調用者CS壓棧,再將返回地址壓棧,ESP+8
3、長調用(跨段並提權)
形如 CALL CS:EIP 執行前后的堆棧圖如下:
隨着權限的變化堆棧會發生切換。
總結:
1)跨段調用時,一旦有權限切換,就會切換堆棧
2)CS的權限一旦改變,SS的權限也要隨着改變,CS與SS的等級必須一樣。
3)JMP FAR只能跳轉到同級非一致代碼段,但CALL FAR可以通過調用門提權,提升CPL權限。
6、調用門
6.1 調用門的執行流程
指令格式:CALL CS:EIP(EIP是廢棄的)
執行步驟:
1)根據CS的值查GDT表,找到對應的段描述符,這個描述符是一個調用門。
2)在調用門描述符中存儲另一個代碼段的段選擇子。
3)選擇子指向的段,段.Base+偏移地址=真正要執行的地址。
6.2 門描述符
在保護模式下,中斷描述符表(IDT)中的每個表項由8個字節組成,其中的每個表項叫做門描述符。當中斷發生時,必須先訪問這些門,判斷是否能夠"開門",需要對執行的指令進行權限檢查,符合設定的權限約束才可以進入相應的處理程序。
符號解析:
1、type字段用於標識門的類型,1100表示調用門。
2、P位位有效位,值為0時,調用這樣的門會導致處理器產生異常。
3、DPL字段指明調用門的特權級,從而指定通過調用門訪問特定過程所要求的的特權級。
6.3 構造一個調用門(無參 提權)
0040EC00 000810D0
6.4 代碼測試
步驟一:代碼測試,並觀察堆棧與寄存器的變化.
#include <windows.h>
#include<stdio.h>
void __declspec(naked) GetRegister()
{
__asm
{
int 3
retf
}
}
/***
通過中斷的方法進入內核
觀察CS、SS的變化
觀察ESP的值由低2G到高2G
****/
int main()
{
char buff[6];
*(DWORD*) &buff[0] = 0x12345678;
*(WORD*) &buff[4] = 0x48;
__asm
{
call fword ptr[buff]
}
// getchar();
return 0;
}
記錄執行前的寄存器值:
CS SS ESP
如何查看當前程序的CPL DPL
中斷到內核,進入0環。
步驟二:在測試代碼中加入特權指令並讀取高2G內存.
7、中斷門
7.1 什么是中斷門?
Windows沒有調用門,但是在系統調用和調試時使用了中斷門。
中斷描述符的結構:
中斷門通常是以 ----EE00 0008----的形式出現,第5個16進制數不一定是額,如果DPL=0,那第5個16進制數就是8。 0008是段選擇子,而左右兩側的占位符是要跳轉的地址,具體的跳轉地址如何查找,見下面的實驗部分。
查看中斷描述符表(IDT)的基址和長度:
r idtr
r idtl
IDT表可以包含3種門描述符:
1、任務門描述符
2、中斷描述符
3、陷阱門描述符
中斷門提權
入口函數
int g_high2G; // 在中斷門中讀取高2G內存保存進來。
int g_eflagsBefore; // 保存進入中斷門前的 EFLAGS 寄存器。
int g_eflagsAfter; // 保存進入中斷門里的 EFLAGS 寄存器。
int g_eax; // 待會在中斷門里要用到 eax ,先把舊的保存到這里。
__declspec(naked) void func() {
__asm {
/*
此時棧結構(當你執行 int 指令的時候,CPU 會自動在0環棧中壓入這些值):
| eip3 | <- esp0
| cs3 |
|eflags|
| esp3 |
| ss3 |
*/
mov g_eax, eax
// 保存當前 eflags
pushfd
pop g_eflagsAfter
// 保存原始 eflags
mov eax, [esp+0x08]
mov g_eflagsBefore, eax
// 讀取 8003f500 處的值
mov eax, ds:[0x8003f500]
mov g_high2G, eax
// 恢復 eax
mov eax, g_eax
// 中斷門返回
iretd
}
}
構造中斷門描述符
在IDE中通過查看反匯編代碼,查找fun函數的起始地址,通過斷點調試查看反匯編代碼定位到fun函數,可以看到地址是00401550 ,根據中斷門描述符構造規則,查找函數函數偏移地址:
0: kd> r idtr
idtr=8003f400
0: kd> dq 8003f400 L30
8003f400 80548e00`000831a0 80548e00`0008331c
8003f410 00008500`0058113e 8054ee00`00083730
8003f420 8054ee00`000838b0 80548e00`00083a10
8003f430 80548e00`00083b84 80548e00`000841fc
8003f440 00008500`00501198 80548e00`00084600
8003f450 80548e00`00084720 80548e00`00084860
8003f460 80548e00`00084ac0 80548e00`00084dac
8003f470 80548e00`000854a8 80548e00`000857e0
8003f480 80548e00`00085900 80548e00`00085a3c
8003f490 80548500`00a057e0 80548e00`00085ba4
8003f4a0 80548e00`000857e0 80548e00`000857e0
8003f4b0 80548e00`000857e0 80548e00`000857e0
8003f4c0 80548e00`000857e0 80548e00`000857e0
8003f4d0 80548e00`000857e0 80548e00`000857e0
8003f4e0 80548e00`000857e0 80548e00`000857e0
8003f4f0 80548e00`000857e0 806e8e00`0008710c
8003f500 00000000`00080000 00000000`00080000
0040EE00 00081550
EE00是中斷門特征,0008是中斷門跨入0008描述的代碼,0040是函數地址的前4個字節,1550是函數地址的后4個字節。
執行指令修改描述符
eq 8003f500 0040ee00`00081550
執行中斷門描述符后
0: kd> eq 8003f500 0040ee00`00081550
0: kd> dq 8003f400 L30
8003f400 80548e00`000831a0 80548e00`0008331c
8003f410 00008500`0058113e 8054ee00`00083730
8003f420 8054ee00`000838b0 80548e00`00083a10
8003f430 80548e00`00083b84 80548e00`000841fc
8003f440 00008500`00501198 80548e00`00084600
8003f450 80548e00`00084720 80548e00`00084860
8003f460 80548e00`00084ac0 80548e00`00084dac
8003f470 80548e00`000854a8 80548e00`000857e0
8003f480 80548e00`00085900 80548e00`00085a3c
8003f490 80548500`00a057e0 80548e00`00085ba4
8003f4a0 80548e00`000857e0 80548e00`000857e0
8003f4b0 80548e00`000857e0 80548e00`000857e0
8003f4c0 80548e00`000857e0 80548e00`000857e0
8003f4d0 80548e00`000857e0 80548e00`000857e0
8003f4e0 80548e00`000857e0 80548e00`000857e0
8003f4f0 80548e00`000857e0 806e8e00`0008710c
8003f500 0040ee00`00081550 00000000`00080000
中斷門對堆棧的影響
1、提權切換棧,會比調用門多切換一個堆棧
2、不提權,不會切換堆棧,沒有必要在棧中壓入棧段選擇子和棧頂指針。
3、使用INT進入中斷門
使用 INT [index]匯編進入各種類型的門,通過INT指令進入中斷門,會影響棧,如果從3環進入0環,必須要實現:
-
切換到0環的棧
-
在0環棧壓入ss3,esp3,eflags3,cs3,eip3
(注:上面所指的3表示的3環,即ss3為3環的棧,esp3 3環的棧頂指針,eflags3 ,cs3 3環的代碼段選擇子,eip3 3環的返回地址)
8、陷阱門
陷阱門描述符的結構
通常陷阱門是以 ----ef00 0008---- 的形式出現,第5個16進制數不一定是e,當DPL=0時,第5個16進制數就是8。0008是段選擇子,而左右兩側的占位符是要跳轉的地址。
對比陷阱門和中斷門的描述符結構,我們發現除了type域的第3位不一樣之外,其他的都相同。
9、任務狀態段 TSS
在調用門、中斷門、陷阱門中,一旦出現權限切換,就會有堆棧的切換,而且由於CS、CPL的改變,也引起了SS的切換。SS切換時,就會有新的ESP和SS,那就這兩個值是從哪里來的呢?
TSS的內存結構圖如下:
大小:104字節
TSS的本質:不要把TSS與任務切換聯系到一起,TSS的意義就在於可以同時切換掉一堆寄存器。
CPU是如何找到TSS的呢?
CPU通過TR寄存器來確定TSS的位置,TR是一個段寄存器,大小為96位,存放時描述了TSS段的相關信息,如TSS段的基址、大小和屬性。
可以通過ltr指令+TSS段描述符的選擇子來加載TSS段。由於該指令時特權指令,所以只能在特權級為0的情況下使用。
TSS段描述符
當 S=0,TYPE=1001 / TYPE=1011,表示這是一個TSS段描述符。
當TSS段沒被加載進TR寄存器時,TYPE=1001,一旦TSS被加載進TR寄存器,TYPE=1011.
那么,TSS有什么作用呢?
1、保存Ring0、Ring1、Ring2的棧段選擇子和棧頂指針。
在跨段提權時,需要切換棧,CPU會通過TR寄存器找到TSS,取出其中的SS0和ESP0復制到SS和ESP寄存器中。
(注:上面SS0、ESP0中的0表示Ring0)
2、一次切換一堆寄存器
TSS存儲了不同特權級下的SS、ESP、CS、SS等,可以通過call/jmp + TSS段選擇子 指令一次性把這些值加載到CPU'對應的寄存器中,同時舊值保存在舊的TSS中。
一個GDT表可以存放多個TSS描述符,即內存中可以同時存在多個TSS,TR總是指向當前使用中的TSS,當執行 call/jmp + TSS段選擇子時,CPU執行:
- 把當前所有寄存器的值填寫到當前TR寄存器指向的TSS中
- 把新的TSS段選擇子執行的段描述符加載到TR段寄存器中
- 把新的TSS段中的值覆蓋到當前所有的寄存器中。
以上就是簡單的段機制入門筆記,細節還可以自己去深究,重點參考Inter白皮書。
10、分頁機制
前面做的各種內核實驗都卡死,很是郁悶,准備還是先從理論上先過一篇,然后再來深究細節。
本節我們來了解一下頁的機制。
10.1 3個概念
先來看個例子: mov eax,dword ptr ds:[0x12345678]
-
邏輯地址:也叫有效地址,C語言中指針里存的地址就是有效地址。這里0x12345678就是這個地址。
-
線性地址:線性地址=ds.base+邏輯地址,這里 ds.Base+0x12345678 就是這個地址。
-
物理地址:通過某種映射的方法,把線性地址映射到真實的物理內存條上的某個地址。
每個進程都有一個CR3寄存器,CR3指向一個物理頁,一共4096個字節。
10.2 頁
頁是intel CPU提供的一種機制,頁其實就是有固定大小的內存塊。
CPU提供了兩種大小的頁,一種為4KB(212字節,也就是0x1000字節),這種機制將4GB的地址空間划分成了220 (1MB)個固定大小的內存塊。
頁的范圍:
第0頁 0x00000000~0x00000fff
第1頁 0x00001000~0x00001fff
第2^20 -1 頁 0xfffff000~0xffffffff
10.3 如何實現線性頁到物理頁的映射?
1、頁表
CPU把物理地址空間分成若干個頁面,每個頁面大小為4KB,每個頁面都有一個編號,假設物理內存為4GB,那么編號就是從0~2^20 - 1(0x000000xfffff);假設物理內存只有1MB,那么編號就是從02^8 - 1(0x00000~0x000ff)
只要知道了某個頁面頁面編號,就一定能計算出小頁面的真是物理地址。比如:
0號物理頁的物理地址就是 0x00000000,
1號物理頁的物理地址就是 0x00001000,
0xff號物理頁的物理地址就是 0x0000ff00。
計算方法:頁號*4KB
那么CPU是如何找到這些頁面的呢?
其實方法就是CPU和物理內存都要將頁面編號保存起來,接下來看看CPU和物理內存是如何保存這些頁號的:
-
CPU用32bit來保存一個頁面編號,高20bit保存編號,低12位保存頁面屬性。
這些保存編號的頁成為頁表,頁表中的每個元素占4字節,一個頁面可以保存1024個元素。
2、一級頁表、二級頁表、普通物理頁
一級頁表:保存頁表索引號的頁表,也叫頁目錄表
二級頁表:保存普通物理頁索引號的頁表
PDE:page directory entry頁目錄表項
PTE:page table entry 頁表項
3、10-10-12分頁
線性地址保存的就是頁號,一個32位的線性地址,結構如下:
舉個栗子:假設線性地址為 0x12345678
0x12345678 轉為二進制 0001 0010 0011 0100 0101 0110 0111 1000
做10-10-12的拆分:0001 0010 00 = 0x48
11 01 00 01 01 = 0x345
0110 0111 1000 = 0x678
即:第一個頁表索引號是0x48,第二個頁表的索引號是 0x345,最后一個頁內偏移是 0x678。
假設:一級頁表的基址為 page_dir_tables,根據索引查找第一個頁表索引號的值 page_dir_tables[0x48],假設page_dir_tables[0x48]=0x12FFFFFF,取高20位二級頁表編號 0x12FFF,則物理頁的基址為0x12FFF000。則page_tables=0x12FFF000.
再通過page_tables[0x345]=0x21991067的值。求得頁編號 0x21991,則頁基址為 0x21991000.
找到物理頁基址0x21991000,但是這是個普通物理頁,需要通過普通物理頁+頁內偏移,定位到最終的物理頁,即:0x21991000+0x678=0x21991678.
4、2-9-9-12分頁
10-10-12分頁方式下的物理地址最多可達4GB,但是4GB的物理地址范圍已經無法滿足要求,所以Intel又衍生出2-9-9-12分頁方式,成為PAE(物理地址拓展)分頁。
為什么是10-10-12???
- 首先確定了頁大小為4KB,所以后面的12是確定的。
- 當物理內存比較小時,4個字節的PTE就夠了,因為頁的尺寸是4K,所以一個頁能存儲1024(2^10)個PTE,所以第2個10也就確定了。
- 剩下的10為PDI
這樣就有了10-10-12的分頁方式。
那為什么是2-9-9-12???
-
首先確定了頁大小為4KB,所以后面的12是確定的。
-
如果要增大物理內存的訪問范圍,就需要增大PTE。那具體增大多少呢?
考慮對齊因素,將PTE增加到8字節。這樣一來,一個頁就能存儲512(2^9)個PTE,所以PTI為9
-
同理PDI也是2^9,在4GB內存空間,32位的尋址空間中,還差 2=32-9-9-12 就稱為PDPI。
10.4 2-9-9-12的分頁結構
PDPTE(page directory point table entry,頁目錄指針表項),每項占8個字節
如何開啟PAE模式?
[boot loader]
timeout=30
default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
[operating systems]
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /execute=optin /fastdetect
execute :為10-10-12分頁
noexecute :為2-9-9-12分頁
舉個栗子:假設線性地址為 0x00423024
0x12345678 轉為二進制 0000 0000 0100 0010 0011 后12位不分解
做2-9-9-12的拆分:00
00 0000 010 =0x2
0 0010 0011 =0x23
即:第一個頁表索引號是0x48,第二個頁表的索引號是 0x345,最后一個頁內偏移是 0x678。
假設:一級頁表的基址為 page_dir_tables,根據索引查找第一個頁表索引號的值 page_dir_tables[0x48],假設page_dir_tables[0x48]=0x12FFFFFF,取高20位二級頁表編號 0x12FFF,則物理頁的基址為0x12FFF000。則page_tables=0x12FFF000.
再通過page_tables[0x345]=0x21991067的值。求得頁編號 0x21991,則頁基址為 0x21991000.
找到物理頁基址0x21991000,但是這是個普通物理頁,需要通過普通物理頁+頁內偏移,定位到最終的物理頁,即:0x21991000+0x678=0x21991678.
11、TLB
TLB為塊表,那么塊表是怎么來的呢?
我們用熟悉的10-10-12分頁來舉個栗子:
當采用10-10-12分頁方式時,CPU需要訪問一個int類型的變量,需要執行以下操作:
1、讀取4字節的PDE
2、讀取4字節的PTE
3、讀取4字節的物理內存
那如果在2-9-9-12分頁方式下,會執行以下操作:
1、讀取8字節的PDPTE
2、讀取8字節的PDE
3、讀取8字節的PTE
4、讀取4字節的物理內存
在10-10-12分頁模式下,CPU每次要訪問額外的訪問的8字節內存數據才能讀取到數據,而在PAE分頁模式下,需要額外的訪問24字節內存數據才能讀取到數據。
對於需要頻繁進行內存訪問的CPU而言,這樣的訪問方式的效率是很低的,由此產生TLB。
TLB作為一種中間結構,能夠避免過多的內存訪問,使得CPU不經由頁目錄表和頁表,直接就能把線性地址映射成物理地址。
到底TLB的結構是怎樣的,使其能夠將線性地址映射成物理地址?
TLB是一個寄存器,存儲了線性地址對應的物理地址,以及頁屬性和統計。
當CPU要訪問某個地址時,先訪問TLB,若TLB中不存在這個地址,再去訪問PDE或PTE,同時將這個線性地址保存到TLB中。
當TLB滿時,根據LRU統計的值,把不經常使用的記錄刪除,添加新值。
12、中斷
12.1 什么是中斷?
中斷通常是由CPU外部的IO設備所觸發的,供外部設備通知CPU有事情需要處理,因此也叫中斷請求。
中斷請求的目的是希望CPU暫時停止執行當前正在執行的程序,轉去執行中斷請求所對應的中斷處理歷程。
80x86有兩個中斷請求線:
1、非屏蔽中斷線,稱NMI
2、可屏蔽中斷線,稱INTR
12.2 非可屏蔽中斷如何處理?
當非可屏蔽中斷產生時,CPU在執行完當前指令后hi進入中斷處理程序
非可屏蔽中斷不受EFLAG寄存器中IF位的影響,一旦發生,CPU必須處理
非可屏蔽中斷處理程序位於IDT表中的2號位置。
12.3 可屏蔽中斷
可屏蔽中斷由中斷控制器(專門的芯片)來管理,它負責分配中斷資源和管理各個中斷源發出的中斷請求。為了便於標識各個中斷請求,中斷管理器常用IRQ后面+數字來表示不同的中斷,如:IRQ0表示Windows中的時鍾中斷。
當可屏蔽中斷發生時,如何處理呢?
1、若自己的程序執行時,不希望CPU去處理這些中斷,可以用CLI指令清空EFLAG寄存器中的IF位,用STI指令設置EFLAG寄存器中的IF位。
2、硬件中斷與IDT表中的對應關系並非固定不變
12.4 異常
異常通常是CPU在執行指令時檢測到的某些錯誤,如:除0異常、訪問無效頁面等。
中斷與異常的區別:
1、中斷來自於外部設備,是中斷源發起的,CPU是被動的。
2、異常來自於CPU本身,是CPU主動產生的。
3、INT N 雖然被稱為軟中斷,但本質是異常。EFLAG的IF位對INT N無效。
那是如何處理異常的呢?
無論是由硬件設備觸發的中斷請求還是由CPU產生的異常,處理程序都在IDT表。
常見的異常處理程序如下:
我們來重點關注一下缺頁異常的產生:
比如:1、當PDE/PTE的P=0時,
2、當PDT/PTE的屬性為只讀但程序試圖寫入時。
一旦發生缺頁異常,CPU會執行IDT表中的0xE號中斷處理程序,由OS來接管。
13、控制寄存器
控制寄存器用於控制和確定CPU的操作模式。
13.1 有哪些控制寄存器?
Cr0 Cr1 Cr2 Cr3 Cr4
Cr1 保留
Cr3 頁目錄表基址
13.2 Cr0寄存器
說明:
1、PE:Cr0的為0是啟用保護標志
PE=1保護模式
PE=0實地址模式
這個標志僅開啟段級保護,而並沒有啟動分頁機制,若啟用分頁機制,那么PE和PG標志都要置位。
2、PG:當設置該為時,即開啟了分頁機制,在開啟這個標志之前必須已經或者同時開啟PE標志。
PG=0且PE=0 處理器工作在實地址模式下
PG=0且PE=1 處理器工作在沒有開啟分頁機制的保護模式下
PG=1且PE=0 在PE沒有開啟的情況下 無法開啟PG
PG=1且PE=1 處理器工作在開啟了分頁機制的保護模式下
3、WP:對於Intel 80486或以上的CPU,Cr0的位16是寫保護(Write Proctect)標志。當設置該標志時,處理器會禁止超級用戶程序(例如特權級0的程序)向用戶級只讀頁面執行寫操作。
當CPL<3時,若WP=0可以讀寫任意用戶級物理頁,只要線性地址有效。
若WP=1可以讀取任意用戶級物理頁,但對於只讀的物理頁,則不能寫。
13.3 Cr2寄存器
當CPU訪問某個無效頁面時,會產生缺頁異常,此時,CPU會將引起異常的線性地址存放在Cr2中。
13.4 Cr4寄存器
PAE/PSE說明:
PAE=1是2-9-9-12分頁
PAE=0是10-10-12分頁
PSE=1:
10-10-12 PS=1 4M頁
PS=0 4K頁
2-9-9-12 PS=1 2M頁
PS=0 4K頁
PSE=0:
10-10-12 PS=1 4K頁
PS=0 4K頁
2-9-9-12 PS=1 4K頁
PS=0 4K頁
14、PWT/PCD屬性
PWT:Page Write Through
PWT=1時,寫Cache的時也要講數據寫入內存中
PCD:Page Cache Disable
PCD=1時,禁止某個頁寫入緩存,直接寫內存。
比如,做頁表用的頁,已經存儲在TLB中了,可能不需要再緩存了。