大家好,欢迎来到IT知识分享网。
前言:这篇作为转换后备缓冲区TLB(Translation Lookaside Buffer)的学习笔记
线性地址解析
这里我们拿10-10-12分页来举例子,下面来说明在10-10-12分页下CPU和操作系统是如何通过线性地址找到物理地址的
一个概念需要知道,比如一个程序要读取DWORD大小的内存,其实未必真正读的是4个字节,我们先读的PDE再读PTE 最后才读的4个字节的页。
不考虑PDE大页(PS=1)的情况下,就比如0x12345678,0x1234567C,那么这两个地址,先读0x1234678,那么在10-10-12分页下会先读取C0300000+PDI*4
获取PDE,再接着读取C0000000+PDI*4*1024+PTI*4
获取PTE,再接着继续读PTE对应的基址+偏移,最终才会找到数据,所以一共读了3次,那么如果在2-9-9-12分页下就会读4次。接着又去读0x1234567C,那么又要读3次
那么如果再考虑下特殊情况,如果跨页(就比如PTE不在同一个)可能更多。
知识点:什么是跨页?
我理解的就是 virtualalloc 分配的都是0x1000 0x1000 ,那是不是如果在0x40000FFF读取四个字节的时候 这种情况下就有另外三个字节会读取到40001000-40001FFF上了 这种就会跨页
所以为了提高效率,只能做记录。
CPU内部做了一个表,来记录这些东西,这个表格是CPU内部的,和寄存器一样快,这个表格:TLB(Translation Lookaside Buffer)。
TLB的结构
说明:
- ATTR(属性):属性是PDPE PDE PTE三个属性AND起来的. 如果是10-10-12就是PDE and PTE
- 不同的CPU(根据型号) 这个表的大小不一样.
- 只要CR3变了,TLB立马刷新,一核一套TLB。
G位知识点
操作系统的高2G映射基本不变,如果CR3改了,TLB刷新 重建高2G以上很浪费。
所以PDE和PTE中有个G标志位,如果G位为1刷新TLB时将不会刷新 PDE/PTE的G位为1的页,这就会导致进程A切换到进程B,而进程A的线性地址0x12345678还是保存到进程B中的TLB表中,那么下次进程B进行找0x12345678,就会去找到进程A线性地址中对应的物理地址
当TLB满了,根据统计信息将不常用的地址废弃,最近最常用的保留。
需要注意的:这个其实就可以之前在做读取高2G的时候,有时候你给的属性的167,原因就是167的G位为1,那么这个情况下刷新TLB的时候就不会刷新你修改过的属性,所以其实还是165(R/W为0)的情况,那么实验的时候10-10-12分页下就需要把这个PDE和PTE的G位修改为0,如果在2-9-9-12分页下只需要修改PTE的G位。
TLB的种类
TLB在X86体系的CPU里的实际应用最早是从Intel的486CPU开始的。
在X86体系的CPU里边,一般都设有如下4组TLB“
- 第一组:缓存一般页表(4K字节页面)的指令页表缓存(Instruction-TLB);
- 第二组:缓存一般页表(4K字节页面)的数据页表缓存(Data-TLB);
- 第三组:缓存大尺寸页表(2M/4M字节页面)的指令页表缓存(Instruction-TLB);
- 第四组:缓存大尺寸页表(2M/4M字节页面)的数据页表缓存(Data-TLB)
这个我们不能直接控制不了存储指令表还是数据页表的缓存,这个是由内部CPU来决定的,只能间接控制,就比如我们自己用mov eax,0x12345678
,这个就是数据缓存,那么CPU可能就会把这条0x12345678的线性地址进行缓存起来,下次读写的时候就直接在TLB读取对应的线性地址的物理地址即可
练习
这篇测试是在2-9-9-12分页模式下进行测试的
步骤0:给0线性地址挂上物理页
步骤1:体验TLB的存在
步骤2:全局页的意义
步骤3:INVLPG指令的意义
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
int g_value = 0;
// 0x40100A
__declspec(naked) void test()
{
__asm
{
push 0x30;
pop fs;
pushad;
pushfd;
// get 0x600000's pte
mov eax, 0x600000;
mov ebx, 0xC0000000;
shr eax, 9;
and eax, 7FFFF8h;
mov edx, eax;
add edx, ebx; // eax = pte
// edit 0's pte value
mov ecx, dword ptr ds:[edx]; // ecx = [0x600000's pte]
// or ecx, 0x100;
mov dword ptr ds:[ebx], ecx; // ds:[0] = ecx
// write 0's phy value
mov dword ptr ds:[0], 0x12345678;
xor eax, eax;
xor ebx, ebx;
xor ecx, ecx;
xor edx, edx;
// get 0x700000's pte
mov eax, 0x700000;
mov ebx, 0xC0000000;
shr eax, 9;
and eax, 7FFFF8h;
mov edx, eax;
add edx, ebx; // eax = pte
// edit 0's pte value
mov ecx, dword ptr ds:[edx]; // ecx = [0x700000's pte]
mov dword ptr ds:[ebx], ecx; // ds:[0] = ecx
popfd;
popad;
iretd;
}
}
int main()
{
// for secure, use virtualAlloc to apply memory
DWORD* p1 = (DWORD*)VirtualAlloc(0x600000, 0x1000, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
DWORD* p2 = (DWORD*)VirtualAlloc(0x700000, 0x1000, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
if(p1 == NULL || p2 == NULL)
{
printf("virtualAlloc Failed!\n");
system("pause");
return 0;
}
// for fresh phy addr;
memset(p1, 0, 0x1000);
memset(p2, 0, 0x1000);
*p1 = 0x100;
*p2 = 0x200;
printf("test func: %x\n", test);
__asm
{
// eq 8003f500 0040EE00`0008100A
int 0x20;
push 0x3b;
pop fs;
}
printf("%x", *(DWORD*)0);
system("pause");
return 0;
}
运行结果如下所示,因为TLB没有刷新,所以TLB中保存的还是p1指向的线性地址中的物理地址
接着模拟刷新CR3来实现线程切换
mov eax, cr3;
mov cr3, eax;
结果如下所示,因为G位不是1,所以CR3会导致整个TLB表刷新,那么次是TLB中保存的就是p2指向的线性地址中的物理地址了
接着设置将p1的PTE的G位设置为1,那么进程切换的时候也不会导致这个TLB表中的p1的线性地址记录被刷新
如下图所示,可以看到TLB没有刷新p1所指向的物理地址
那么强制刷新线性地址0对应的TLB,这里用到INVLPG指令
invlpg dword ptr ds:[0];
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/34306.html