保护模式的内容接近尾声,这一篇文章补充一下琐碎的知识点,下面先从TLB开始
TLB
设计原因
- 假设我们通过一个线性地址访问一个物理页,想要去读取物理页上某个字节。但是实际过程中,并非只读了1个字节,我们需要先读取PDE,再读取PTE,最后再读取存放1个字节的物理页,读取的内容远远超过1个字节了。
- 在2-9-9-12分页下,会多读24个字节,如果读取的内容跨页了(存在两个不同的物理页上),那多读的字节会更多
为了提高效率,只能通过做记录来进行弥补。
因此CPU内部设计了一个表,用来做记录;由于位于CPU内部,速度和寄存器一样快,当然,表也不能做的过大。这个表叫做TLB(Translation Lookaside Buffer),用于地址解析
TLB结构
LA(线性地址) | PA(物理地址) | ATTR(属性) | LRU(统计) |
---|---|---|---|
xxxxxxxx | xxxxxxxx | xxxxxxxx | xxxxxxxx |
说明:
- ATTR(属性):PAE分页,用PDPE&PDE&PTE。10-10-12分页则PDE&PTE
- 不同CPU的TLB表大小不一样
- 只要Cr3改变了(说明进程切换了),先前的TLB则会失效,换一套新的TLB,一核一套TLB
由于操作系统中的高2G映射基本不变,如果Cr3改了,直接刷新TLB,对于重建高2G以上的对应关系很浪费,所PDE和PTE中有个标志位G位,刷新TLB时将不会刷新PDE/PTE的G位为1的页。若TLB满了,则CPU会根据统计信息将不常用的地址废弃,保留最近最常用的
注意:只有当PDE的PS位为1时(即当前物理页为大页),G位才有效。
TLB种类
TLB在x86体系的CPU里的实际应用最早是从Intel的486CPU开始的,在x86的CPU里,一般都设有如下4组TLB:
- 缓存一般页表(4K字节页面)的指令页表缓存(Instruction-TLB)
- 缓存一般页表(4K字节页面)的数据页表缓存(Data-TLB)
- 缓存大尺寸页表(2M/4M字节页面)的指令页表缓存(Instruction-TLB)
- 缓存大尺寸页表(2M/4M字节页面)的数据页表缓存(Instruction-TLB)
TLB验证
呵呵,这个破实验花了我一下午,我真是太菜了,一个原因是0地址挂物理页,踩了好几次坑,第二个是VC6很多强转不支持,耽误了很多时间。给0地址挂物理页的步骤就不赘述了,这里采用的10-10-12分页,只是采用了代码挂物理页的方式,具体可以参考基址小实验这一篇,这里就讲讲验证的过程。
我们先给0地址挂上第一个地址(0x425000,这是我随便选的,选错了可能蓝屏)的物理页,然后取0地址的处的置,发现值为0。
这时,我们注释掉给0地址挂第一个地址(0x425000)的物理页的代码,并给0地址挂第二个地址(0x426000)的物理页。这时再取0地址处的值,发现值为0x43,可以发现,这两个线性地址所对应的物理页上的值是不同的。
这时我们把上面的注释拿掉,先给0地址挂第一个地址的物理页,然后再给0地址挂第二个地址的物理页,按照道理,这时我们取到的值应该是第二个地址对应物理页上的值,我们来查看结果:
神奇的事情发生了,我们取到的仍然是第一个地址对应物理页上的值,这其实就是TLB的作用。
这时,我们增加一条语句
1 | invlpg dword ptr ds:[0] |
再次运行程序发现,仅仅多了这一条语句,读取0地址的值,就变成了第二个地址对应物理页上的值,Invlpg是让指定页TLB无效化的指令,因此再次访问时,原先的TLB已经被废弃,就需要重新去物理页读取,此时0地址对应的物理页已经是第二个地址的物理页了。当然,除了使用Invlpg指令,修改Cr3也可以做到让TLB无效化。
下面附上完整代码
1 |
|
控制寄存器
说完了TLB,来说说控制寄存器。控制寄存器的作用主要是用于控制和确定CPU的操作模式。主要包括Cr0,Cr1,Cr2,Cr3,Cr4,其中Cr1保留。
Cr0寄存器
Cr0寄存器,主要包括一些控制操作系统模式以及处理器状态的控制标志位。
这里介绍几个主要的标志位,其余位的描述可以参考Intel白皮书第三卷系统架构综述那章。
PE:Cr0下标为0的位是启用保护(Protection Enable)标志。PE=1保护模式,PE=0实地址模式,这个标志仅开启段级保护,而并没有启用分页机制。若要启用分页机制,那么PE和PG标志都要置位。
PG:当设置该位时即开启了分页机制。在开启这个标志之前必须已经或者同时开启PE标志。
- PG=0且PE=0:处理器工作在实地址模式下
- PG=0且PE=1:处理器工作在没有开启分页机制的保护模式下(不存在这样的操作系统)
- PG=1且PE=0:在PE没有开启的情况下 无法开启PG
- PG=1且PE=1:处理器工作在开启了分页机制的保护模式下
WP:对于Intel 80486或以上的CPU,CR0的位16是写保护(Write Proctect)标志,当设置该标志时,处理器会禁止超级用户程序(例如特权级0的程序)向用户级只读页面执行写操作。
- 对于Ring0的特权级程序,如果WP=0,可以读写任意用户级物理页,只要线性地址有效
- 对于Ring0的特权级程序,如果 WP=1 可以读取任意用户级物理页,但对于只读的物理页,则不能写
Cr2寄存器
Cr2寄存器,保存导致缺页异常的线性地址。
之前在中断与异常中,简要概括了缺页异常,当CPU访问某个无效页面,会产生缺页异常,此时,CPU会将引起异常的线性地址存放在Cr2中,以便操作系统处理完缺页异常后,返回到原本执行的位置继续执行。
Cr3寄存器
Cr3我们太熟悉了,在10-10-12分页是页目录表基址,在PAE分页下,则是页目录指针表基址
这里有两个属性,PWT和PCD之前在页的部分一直没有讲,在介绍之前,先来了解一个概念,叫做CPU缓存
CPU缓存
- CPU缓存是位于CPU与物理内存之间的临时存储器,它的容量比内存小的多,但是交换速度远快于内存。
- CPU缓存可以做的很大,从几K,几十K,几百K,甚至上M。
- CPU缓存与TLB的区别:
- TLB:线性地址 <—–> 物理地址
- CPU缓存: 物理地址 <—–> 内存
有了CPU缓存和TLB的概念后,就可以来讲讲PWT和PCD这俩属性了。
PWT(Page Write Through)
- PWT = 1时,CPU向cache写入数据时,同时向memory也写一份,使cache和memory的数据保持一致。优点是简单,缺点是每次都要访问memory,速度比较慢,即Write Through。
- PWT = 0时,CPU向cache写入数据时,不将数据写入内存中,分为两种情况:
- Post Write:CPU更新cache数据时,把更新的数据写入到一个更新缓冲器,在合适的时候才对memory进行更新。这样可以提高cache访问速度,但是,在数据连续被更新两次以上的时候,缓冲区将不够使用,被迫同时更新memory。
- Write Back:CPU更新cache时,只是把更新的cache区标记一下,并不同步更新memory。只是在cache区要被新进入的数据取代时,才更新memory。这样做的原因是考虑到很多时候cache存入的是中间结果,没有必要同步更新memory。优点是CPU执行的效率提高,缺点是实现起来技术比较复杂。
PCD(Page Cache Disable)
- PCD = 1时,禁止某个页写入缓存,直接写入内存。例如,做页表用的页,已经存储在TLB中了,可能不需要再缓存了。
- PCD = 0时,不限制页写入缓存,可以参考上面PWT的情况。
Cr4寄存器
Cr2寄存器,保存了一组启用多种架构扩展的标志位
这里简单概括一下PAE位和PSE位:
- PAE:置1时,是PAE分页;置0时,是10-10-12分页。之前在boot.ini中设置execute/noexecute的作用就是修改PAE位
- PSE:控制PDE中PS位的开关,当PSE置1时,PS位才有效。具体如下:
控制寄存器小节
除了上述介绍的,还有一个Cr8寄存器,仅仅在64位下才存在,这里就不作介绍了,其余寄存器总览如下:
参考文章1:https://blog.csdn.net/wyzxg/article/details/7254458
参考文章2:https://blog.csdn.net/q1007729991/article/details/53000410