网站承建如何开发微信小程序
TLB
TLB(Translation Lookaside Buffer)是 CPU 内部用于缓存线性地址与物理地址映射关系的表。
TLB结构
- ATTR:属性
- 在10-10-12分页模式下:ATTR = PDE属性 & PTE属性
- 在2-9-9-12分页模式下:ATTR = PDPTE属性 & PDE属性 & PTE属性
- LRU:统计信息
由于TLB的大小有限,因此当TLB被写满、又有新的地址即将写入时,TLB就会根据统计信息来判断哪些地址是不常用的,从而将不常用的记录从TLB中移除。
注意:
- 不同的CPU,TLB大小不同
- 只要Cr3发生变化,TLB立即刷新,一核一套TLB
- 由于操作系统的高2G映射基本不变,因此如果Cr3改了,TLB刷新的话,重建高2G以上很浪费。所以PDE和PTE中有个G标志位(当PDE为大页时,G标志位才起作用),如果G位为1,刷新TLB时将不会刷新PDE/PTEG位为1的页,当TLB写满时,CPU根据统计信息将不常用的地址废弃,保留最常用的地址
TLB种类
TLB在X86体系的CPU中的实际应用最早是从Intel的486CPU开始的,在X86体系的CPU中,一般都设有如下4组TLB:
- 第一组:缓存一般页表(4K字节页面)的指令页表缓存(Instruction-TLB);
- 第二组:缓存一般页表(4K字节页面)的数据页表缓存(Data-TLB);
- 第三组:缓存大尺寸页表(2M/4M字节页面)的指令页表缓存(Instruction-TLB);
- 第四组:缓存大尺寸页表(2M/4M字节页面)的数据页表缓存(Data-TLB)
实验
体验TLB的存在
实验在 2-9-9-12 分页的环境下进行。代码如下:
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>void *p1, *p2;unsigned int val1, val2, val3;
unsigned int plt1, plt2;
const unsigned int plt0 = 0xC0000000;__declspec(naked) void callGate() {__asm {push 0x30pop fspushadpushfd}//将 p1 对应物理页挂到 0 地址处plt1 = ((unsigned int) p1 >> 9 & 0x7FFFF8) | 0xC0000000;*(unsigned int *) plt0 = *(unsigned int *) plt1;*(unsigned int *) (plt0 + 4) = *(unsigned int *) (plt1 + 4);//读取 0 地址处内存val1 = *(unsigned int *) 0;//将 p2 对应物理页挂到 0 地址处plt2 = ((unsigned int) p2 >> 9 & 0x7FFFF8) | 0xC0000000;*(unsigned int *) plt0 = *(unsigned int *) plt2;*(unsigned int *) (plt0 + 4) = *(unsigned int *) (plt2 + 4);//读取 0 地址处内存val2 = *(unsigned int *) 0;//刷新 TLB__asm {mov eax, cr3mov cr3, eax}//读取 0 地址处内存val3 = *(unsigned int *) 0;__asm {popadpopfdretf}
}int main() {unsigned char buf[] = {0, 0, 0, 0, 0x48, 0};p1 = VirtualAlloc((void *) 0x600000, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);p2 = VirtualAlloc((void *) 0x700000, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);printf("%X %X\n", (int) p1, (int) p2);if (p1 == NULL || p2 == NULL) {printf("[-] VirtualAlloc failed.");return 0;}*(unsigned int *) p1 = 0x123;*(unsigned int *) p2 = 0x456;printf("callGate: %X\n", callGate);system("pause");__asm {pushadpushfdcall fword ptr bufpopfdpopadpush 0x3bpop fs}printf("val1: %X\nval2: %X\nval3: %X\n", val1, val2, val3);return 0;
}
运行结果:
可以发现:
- 在 val1 被赋值完成后,即使 0 地址被挂上了新的物理页,再对 val2 进行赋值,val1 和 val2 输出的值是相同的。
- 在 Cr3 刷新后,0地址没有被挂上新的物理页,对 val3 进行赋值后,val3 却输出了新的值。
这是因为 Cr3 刷新前,0 地址第一次被val1访问时,线性地址与物理地址的对应关系被写入了 TLB 中,因此在对 val2 赋值时,TLB 的记录没有被刷新,访问的还是原来的物理页。
体验全局页的意义
将前面的代码做如下改动:
由于 p1 的 PLT 写入 0 地址对应 PLT 时将 G 位 置 1,刷新 TLB 时不会更新该数据。
INVLPG指令的意义
在上一实验代码的基础上使用 INVLPG 指令刷新 0 地址对于的 TLB 数据,结果在 PLT 的 G 位置 1 的情况下依然能刷新。
控制寄存器
描述
控制寄存器用于控制和确定CPU的操作模式。控制寄存器有五个,分别是:Cr0 Cr1 Cr2 Cr3 Cr4。其中Cr1保留,Cr3为页目录表基址。
Cr0寄存器
结构图:
-
PE位:启用保护(Protecction Enable)标志
- PE=1:保护模式
- PE=0:实地址模式
这个标志仅开启段级保护,而没有启用分页机制
若要启用分页机制,那么PE和PG标志都要置位 -
PG位:分页机制标志
- PG=1:开启了分页机制
- PG=0:未开启分页机制
在开启这个标志位之前必须已经或者同时开启PE标志
- PG=0且PE=0:处理器工作状态为实地址模式
- PG=0且PE=1:处理器工作状态为没有开启分页机制的保护模式
- PG=1且PE=0:不存在。在PE没有开启的情况下无法开启PG
- PG=1且PE=1:处理器工作状态为开启了分页机制的保护模式
-
WP位:写保护(Write Proctect)标志
对于Intel 80486或以上的CPU,CR0的16位是写保护标志
当设置该标志时,处理器会禁止超级用户程序(例如特权级0的程序)向用户级只读页面执行写操作当CPL<3的时候:
- 如果 WP=0 可以读写任意用户级物理页,只要线性地址有效
- 如果 WP=1 可以读取任意用户级物理页,但对于只读的物理页,则不能写
Cr2寄存器
描述:
当CPU访问某个无效页面时,会产生缺页异常,此时,CPU会将引起异常的线性地址存放在CR2中。
结构图:
当CPU访问某个无效页面时,会产生缺页异常,此时,CPU会将引起异常的线性地址存放在CR2中。
Cr4寄存器
结构图:
- PAE:
- PAE=1:2-9-9-12分页
- PAE=0:10-10-12分页
- PSE:大页(PS=1)是否有效