[386分页机制]
一、 页
页也就是指内存中的一块区域,在80386中页的大小是4KB,这个是可以计算出来的(线性地址的低12位0FFFh 是页的最大偏移。所以4KB是也页的最大界限)。
在Pentuim 系列后页的大小可以是2MB或者4MB,并且可以用页访问多于4GB的空间。
1、在未开启分页机制之前的寻址方式 :
根据保护模式的分段机制,[段选择子:EA] =》(检查) =》 线性地址 =》物理地址。
如上可以直接通过分段机制获得物理地址。
2、在开启分页机制后的寻址方式:
开启分页机制需要将cr0的PG位置1.。支持分页机制的是cr3指向的一个页目录表。
它的结构是这样的:
PDEs(Page Directory Entry)
_________________________||______________________________
| | | | |
D1 D2 D3 ..... D1024
____||____ ____||____ ____||____ ____||____
PTE1s PTE2s PTE3s PTE4s
| | | | | | | |
T1_1 ... T1_1024 T2_1 ... T2_1024 T3_1 ... T3_1024 T4_1 ... T4_1024
来说说这个结构图:
1、 PDEs(Page Directory Entry)
页目录表总大小是4KB,因为它有1024个页目录项,并且每个页目录项的大小是4个字节。
它的每一个页目录项分别指向一个页表。
2、 PTEs(Page Table Entry)
页表含有1024个表项,其中每个表项对应一个物理页面。
最主要还是要得到表的信息,因为其他的都是表的目录。PDEs是根目录。D1-D1024是根目录下的子目录。D1-D1024各对应着表PTE1s-PTE1024s。PTE1s-PTE1024s每个目录都存放着1024个表项。在表项里面才可以得到真正需要的物理基地址。 要得到表项里的数据必需:
得到D(n)---->>得到PTE(n)s----->>得到T(m)---->得到物理基址。
那么在转换成线性地址后:
1、CPU先取出线性地址的22-31位,高10位相当于一个页目录项的索引。用这个索引在cr3指定的页目录表找到该目录项,页目录项对应一个页表。即得到该线性地址的页表位置。
2、然后取出线性地址的12-21位,中间的10位相当于一个表项索引。
即可以得到线性地址的表项。 而表项里面正存着物理基地址呢。
3、 最后取出0-11位,低12位也就是偏移(EA)。可以认为是物理段偏移。
那么用得到 物理页首地址 + 偏移 = 正确的物理地址。
二、 分页机制
在打开分页机制后, CPU会将最近常用到的页目录和页表项保存在TLB(Translation Lookaside Buffe)
中,只有在TLB中找不到被请求的页的转换信息时,才会去内存中寻找。
当页目录或页表项被更改时,操作系统应该马上使TLB中对应的条目失效。以便下次用到此条目时能从内存
中获取更新的信息。
当cr3被加载时,所有的TLB都会自动无效,除非页或者页表条目的G位被设置.
cr3寄存器指向页目录表,它的高20位就是页目录表首地址的高20位,cr3的低12位会是零,因为页目录表基
址是以4KB对齐的。
说了这么多目目录、页表,那么到底页目录与页表是一个怎么样的关系呢?下面就来详细的介绍他们之间的
关系:
页目录表简称:PDEs,页表简称:PTEs.
cr3中的高20位*4KB也就是目录表(PDEs)的基址了,得到页目录表基址后。所有的页目录就都出来了
PDEs页目录表,它包含了1024个PDE目录项,其中每个目录项对应一个PTEs.(页表)
先看看第一个页目录项PDE,它的12-31位值* 4KB指向的是第一个PTEs(页表)的位置。
第一个PTEs里面也包含1024个页表项,第一个PTE(页表项)的12-31位值*4KB指向的就是页基地址的位置.这
个时候的值就是物理基址。
如果想线性地址是0.,而物理地址是其他的.。那么可以把第0页目录项指向的页表中的第0个页表项的12 -31
位数据改成非0的物理基地址就可以了。。。
而PDE与PTE的关系是:
PEDs下的每一个PDE的12-31位*4KB分别指向对应一个PTEs的物理基地址.
而PTEs里的的每一个表项的12-31位的值就是当前表项对应的物理页基地址。并且界限是0ffffh。
三、 根据BIOS检查内存
为了节约内存,所以首先可以先检查PC机器的内存大小,。这样就知道用不用映射4GB的空间了
因为需要调用15hBIOS中断,所以为了方便直接在8086实模式入口处直接使用。
Int 15h 需要输入的数据:
mov eax,0e820h ;功能号
mov ebx,0 ;第一个后续值是0 。这个BIOS会自动填补下一个地址描述符的后续值。
mov ecx,20;BIOS通常规定只填充20个BYTE,所以固定20
mov edx,0534D4150h ;BIOS将会使用这个标志,对调用者将要请求的系统映像信息进行校验,校验成功后会被放置到es:di 指向的内存缓冲区中。也就是20BYTE的地址描述符.
接下来就可以 int 15h 了。调用中断后。BIOS会根据寄存器对应的信息反馈一些信息到对应的地方。
这里就列举获取内存信息需要的:
CF;如果CF等于1表示获取错误
eax; 存放着SMAP,也就是输入的edx.验证成功返回到eax
es:di;指向的如果获取成功了,那么es:di里面就会指向n个地址范围描述符信息的基址。其中一个地址范围描述符是20个BYTE。
ecx;被BIOS填充到地址范围描述符中的字节数量。最少是20
ebx,后续值 第一次是0,NEXT后BIOS会自动填充。
得到es:di指向的所以地址描述符结构体后,就可以使用dwBaseAddrLow+dwLengthLow得到内存上限的大小。也就是可以使用的内存最大值
四、具体分页设计:
分页学起来确实有点复杂,,因为牵涉的东西太多。
段内寻址、段外寻址、特权级、等等以前的知识点都要用到。
就来说说怎样编程实现分页吧:
1、需要先给页目录项附初始值。这个值分别对应着页表的物理基地址。
2、给页目录对应的页表附物理页基址,这个值是物理地址,可以从0开始.也可以不从0开始.在线性转换中会根据低12位偏移来换算。fffh能代表的也是4KB所以一个表项目最多能表示4KB的物理页,而一个表是1024个表项,那么可以表示4MB的内存页空间.
3、将页目录表的基地址装入cr3 并打开CR0的PG位。这样就启动了分页了.
五、总结:
1、访问数据时要注意数据所属于的段。
2、访问代码时要注意CPL对方DPL RPL。
3、在调用返回时要注意是RET 还是RETF
注意堆栈的指向。等