windows内核(1)——分页
今天开始正式学习分页的相关知识
分页与物理地址
什么是物理地址
在学习二进制的时候就有区分过物理地址和虚拟地址这两个概念,其实就是内存条真正的地址,这里不再赘述。而学习保护模式我们知道,实际的线性地址 = 逻辑地址+段寄存器.base,在汇编和C指针层面所使用的地址都是逻辑地址。但是似乎它等同于虚拟地址(线性地址),这是因为通常情况下段寄存器的 base 都为 0。
10-10-12分页详解
基本结构
拿到一个32位的地址,将其结构进行拆分
这就是 10-10-12 分页的命名由来,而去寻找对应的物理页也是根据每个进程的 Cr3 寄存器的值去找到,整体分页结构如下图所示
同时我们也可以看到,不同的虚拟页是可以映射同一个物理页的,下面来介绍一下 PDE 和 PTE 的基本结构。
物理页的属性
下面描述一下各个位的作用:
- P 位:表示
PDE
或者PTE
是否有效,如果有效为1
,反之为0
。 - R/W 位:如果
R/W = 0
,表示是只读的,反之为可读可写。 - U/S 位:如果
U/S = 0
,则为特权用户(super user),即非3环权限。反之,则为普通用户,即为3环权限。 - PS位:这个位只对
PDE
有意义。如果PS == 1
,则PDE
直接指向物理页,不再指向PTE
,低22位是页内偏移。它的大小为4MB
,俗称“大页”。 - A 位:是否被访问,即是否被读或者写过,如果被访问过则置
1
。 - D 位:脏位,指示是否被写过。若没有被写过为
0
,被写过为1
。 - G 位:表示是否为全局页。它的作用是什么呢?举个例子,操作系统的进程的高
2G
映射基本不变,如果Cr3
改了,TLB
刷新重建高2G
以上很浪费。所以PDE
和PTE
中有个G
位,如果为1,刷新TLB
时将不会刷新它指向的页。 - PWT 位:当
PWT = 1
,写缓存的时候也要将数据写入内存中。 - PCD 位:当
PCD = 1
时,禁止某个页写入缓存,直接写内存。比如,做页表用的页,已经存储在TLB中了,可能不需要再缓存了。
需要注意的是
PTE
可以没有物理页,且只能对应一个物理页。- 多个
PTE
也可以指向同一个物理页。 PDE
和PTE
重合的属性共同决定着最终物理页的属性。比如 P 位,如果有一个是 0,那么最终的物理页就是无效的。但是PDE
和PTE
它们的属性的影响范围是不一样的。数值上:物理页的属性 = PDE属性 & PTE属性。
地址翻译
对于一个给定的线性地址,比如我们执行mov eax,ds:[0x12345678]
这句汇编指令的时候,0x12345678
这个线性地址会传递给CPU
,先查询TLB(页表缓存)和缓存有没有,有的话直接取出来返回;如果没有,经过 MMU(Memory Management Unit,内存管理单元)处理得到物理地址,通过固定的分页模式直接找到,取出数据返回。
TLB大概就是一张表,根据线性地址和 CR3 的值去找物理页地址,如果命中,MMU将可以快速完成翻译。如果 TLB 没有对应的项,则往后会查页表的缓存,查到了也会快速返回,否则只能够去一次一次访存查询页表(这里具体的过程还没有特别弄懂,mark一下,写了新的回来补)。
上面我们经历了一遍翻译的过程,具体我们需要给入几个参数:页表基址(物理地址),线性地址,数据,寄存器,读写信号,这也就解释了为什么 CPU 不允许直接交换两个位置的内存,如果允许则这方面处理将比较麻烦,何况并不是说不提供交换内存的指令就无法实现一些功能。
页表学习准备
笔者用的系统是 Win7 32 位的,虽然现在 32 位的系统几乎用的很少了,但是为了系统的学习还是从简单的开始。Win7 32 默认是 2-9-9-12 分页,先用几个指令配置为更简单的 10-10-12 分页。
1 | bcdedit /set pae ForceDisable |
如果要关闭那么用下面两个指令
1 | bcdedit /set pae forceEnable |
这样就回到了 2-9-9-12 分页。
- 如果是2-9-9-12分页运行的内核 ntkrnlpa.exe
- 如果是10-10-12分页运行的内核 ntoskrnl.exe
地址翻译练习
但是物理地址与之完全不一样,先可以尝试通过 windbg 练习寻找物理地址,这里使用记事本配合CE。
那么得到线性地址是 0029DFB0
,根据 10-10-12 分页规则,将页表拆开
1 | 0000000000 1010011101 111110110000 |
这三部分分别是,页目录项(PDE),页表项(PTE),页内偏移(offset)。
找到页表基址,获得页目录项的地址,即 DirBase+4*PDE
。在这里页表项地址就是 DirBase
,随后跟过去,这里需要注意,这个地址并不是真的地址,而是因为每个页表项它有一定的大小。可能装下一个页目录项就要完整的一个页,如果是这样就决定了页目录项的地址一定是页对齐的(最低三位十六进制地址为0),所以我们找到的地址再把最低三位十六进制置为0就是真实的页目录项了,而通过前面的学习也可以看到,最低的十二个位被赋予了特殊的含义。
我们找到的该内存页目录项地址实际上就是 ad688000
,然后再页目录项中找到页表项 1010011101
转为十六进制得到 29d
,同样的,页目录项地址+4*PTE 得到页的物理地址,也就是 ad688a74
。
同样的,这里低三位十六进制也不是真实的地址,需要清零得到该页的页表基址,最后再加上页内偏移,也就是 FB0。
成功找到对应的物理内存。
而我们的 CR3 寄存器存储的就是页目录表的物理地址,即 Page Director Table,那么所说的 PDE 当然就是 Page Director Entry了。PTE 就是 Page Table Entry,对应的每个目录项指向的是一个 PTT,Page Table Table。
页目录表基址与页表基址
由于物理地址对操作系统是不可见的,所以操作系统必须有线性访问页表的能力,唯一的办法就是将自身的页表挂在一个特殊的地址上,这个地址是 0xC0300000。
也可以发现,CR3 对应的物理地址与线性地址 0xC0300000 是一致的。
相同与页目录表基址,为了在程序内快速访问页表,也有一个页表基址,页表基址对应的线性地址是 0xC0000000。
这里记一下通过线性地址寻找PTE和PDE的公式。
- 访问页目录表的公式:
0xC0300000 + PDI * 4
- 访问页表的公式:
0xC0000000 + PDI * 4096 + PTI * 4
很有趣的是,你会发现如果你要寻找 0xC0300000 对应的页表,用第二个公式代入就是它本身。即
1 | 0xC0300000 |
代入第二个公式可得 0xC0000000 + 0x300 * 4096 + 0x300 * 4 = 0xC0300000