Windows内核实验:数据TLB(DTLB) TLB(Translation Lookaside Buffer)是从虚拟地址到物理地址转换的缓存(一级一级查页表太耗费时间了),TLB命中后就不会再去访问物理内存去查找,对于tlb是对我们完全透明的(一核一套TLB),没有一条指令去看tlb的内容,调试器也不行
所以实验时一定要保证执行的指令是在一个页上,即不造成页面异常,不然页面异常会较大的影响tlb里的内容,对实验现象造成不可解释的结果
实验现象 #include <stdio.h> #include <stdlib.h> #include <windows.h> #define PDE(x) (DWORD64 *) (((x >> 21) << 3) + 0xc0600000) #define PTE(x) (DWORD64 *) (((x >> 12) << 3) + 0xc0000000) DWORD g_var; DWORD64 g_pte; #pragma section("new_page" ,read,write) __declspec(allocate("new_page" )) DWORD page1[1024 ]; __declspec(allocate("new_page" )) DWORD page2[1024 ]; void __declspec(naked) IdtEntry() { __asm { mov eax,ds:[0x0041c000 ] } g_pte= *PTE(0x0041c000 ); *PTE(0x0041c000 ) = *PTE(0x0041b000 ); g_var = page2[0 ]; *PTE(0x0041c000 ) = g_pte; __asm{ iretd } } void interrupt () { page1[0 ] = 1 ; page2[0 ] = 2 ; __asm { int 0x20 } } int main () { if (0x401040 != IdtEntry) { printf ("Idtentry address wrong" ); system("pause" ); exit (-1 ); } interrupt(); printf ("g_var:%d\n" ,g_var); system("pause" ); }
如果没有tlb,我们把page2的页表项变为page1的页表项,g_var = page2[0];就是把page1的数据放入g_var,打印出1
但是有了tlb,此时虽然页表换了但是tlb没有刷新,在访问page2虚拟内存时直接在tlb中命中了原来的物理地址,所以依旧打印出了2
在进程结束时,操作系统会回收虚拟内存映射的物理内存,这个时候如果不把page2的页表项换回来,就会造成page1的double free,从而蓝屏
刷新tlb TLB的刷新(使tlb无效,感觉像清除 )通常发生在以下情况下:
上下文切换:当操作系统切换进程时,TLB可能会被刷新,以确保新进程的虚拟地址能够正确地映射到物理地址。
内存管理单元(MMU)失效:如果操作系统更改了页表或地址映射方案,它可能会通知处理器刷新TLB,以便更新地址映射。
强制刷新:在某些特定的情况下,操作系统或处理器可能需要强制刷新TLB,以确保一致性和正确性。invlpg addr指令(Invalidate Page)可以无视g(global)标志位将该虚拟地址的TLB项标记为无效,下次会访问会更新tlb
所以我们把cr3重新装入就会刷新TLB
#include <stdio.h> #include <stdlib.h> #include <windows.h> #define PDE(x) (DWORD64 *) (((x >> 21) << 3) + 0xc0600000) #define PTE(x) (DWORD64 *) (((x >> 12) << 3) + 0xc0000000) DWORD g_var; DWORD64 g_pte; #pragma section("new_page" ,read,write) __declspec(allocate("new_page" )) DWORD page1[1024 ]; __declspec(allocate("new_page" )) DWORD page2[1024 ]; void __declspec(naked) IdtEntry() { __asm { mov eax, ds: [0x0041c000 ] } g_pte = *PTE(0x0041c000 ); *PTE(0x0041c000 ) = *PTE(0x0041b000 ); __asm { mov eax,cr3 mov cr3,eax } g_var = page2[0 ]; *PTE(0x0041c000 ) = g_pte; __asm { iretd } } void interrupt () { page1[0 ] = 1 ; page2[0 ] = 2 ; __asm { int 0x20 } } int main () { if (0x401040 != IdtEntry) { printf ("Idtentry address wrong" ); system("pause" ); exit (-1 ); } interrupt(); printf ("g_var:%d\n" , g_var); system("pause" ); }
G位,进一步提升性能 页目录项和页表项的第8位是G(gloabal)位
在tlb里会记录这个G位,如果是全局页,在刷新tlb(除了强制(invlpg)时,不会刷新带有G位的tlb项
这样做是因为内核中的多数数据是进程共享的,每次切换进程时刷新这些没必要刷新的内存浪费性能
kd> !process 0 0 test.exe Failed to get VadRoot PROCESS 8137ada0 SessionId: 0 Cid: 0430 Peb: 7ffdb000 ParentCid: 04f0 DirBase: 090c0360 ObjectTable: e24d1a98 HandleCount: 15. Image: test.exe kd> .process /i 8137ada0 You need to continue execution (press 'g' <enter>) for the context to be switched. When the debugger breaks in again, you will be in the new process context. kd> g Break instruction exception - code 80000003 (first chance) nt!RtlpBreakWithStatusInstruction: 80528bdc cc int 3 kd> !pte 0x0041b000 VA 0041b000 PDE at C0600010 PTE at C00020D8 contains 000000000FE30067 contains 8000000000938067 pfn fe30 ---DA--UWEV pfn 938 ---DA--UW-V kd> !pte gdtr VA 8003f000 PDE at C0602000 PTE at C04001F8 contains 0000000000B0A163 contains 000000000003F163 pfn b0a -G-DA--KWEV pfn 3f -G-DA--KWEV
可以看到内核共享数据gdtr是有G属性的,但是3环数据就没有
#include <stdio.h> #include <stdlib.h> #include <windows.h> #define PDE(x) (DWORD64 *) (((x >> 21) << 3) + 0xc0600000) #define PTE(x) (DWORD64 *) (((x >> 12) << 3) + 0xc0000000) DWORD g_var; DWORD64 g_pte; #pragma section("new_page" ,read,write) __declspec(allocate("new_page" )) DWORD page1[1024 ]; __declspec(allocate("new_page" )) DWORD page2[1024 ]; void __declspec(naked) IdtEntry() { g_pte = *PTE(0x0041c000 ); *PTE(0x0041c000 ) = *PTE(0x0041b000 ) | 0x100 ; __asm { mov eax,cr3 mov cr3,eax } __asm mov eax, ds: [0x0041c000 ] *PTE(0x0041c000 ) = g_pte; __asm { mov eax, cr3 mov cr3, eax } g_var = page2[0 ]; __asm { iretd } } void interrupt () { page1[0 ] = 1 ; page2[0 ] = 2 ; __asm { int 0x20 } } int main () { if (0x401040 != IdtEntry) { printf ("Idtentry address wrong" ); system("pause" ); exit (-1 ); } interrupt(); printf ("g_var:%d\n" , g_var); system("pause" ); }