页表描述符
转载:页表描述符(page table descriptor) (zhihu.com)
之前的文章曾提到,在多级页表中,每一级页表的entry除了存放下一级页表(对于PTE来说是页)的首地址,还留下了不少bit空间可供使用,本文将就此详细介绍下。
以32位的x86为例,从存储page首地址的PTE(page table entry)说起。
![](https://pic4.zhimg.com/v2-943d96845970c66749cbdcb51ae58103_b.webp?consumer=ZHI_MENG)
通常page frame的大小为4KB,所以PTE中指向的page首地址是4KB对齐的,也就是说page首地址的最后12个bit都为0。而一个PTE的大小为4字节,所以这后面的12个bit可以被用来表达page的属性。
![](https://pic3.zhimg.com/v2-6a984f923247cf69540de7875a4d9a76_b.webp?consumer=ZHI_MENG)
P(Present) - 为1表明该page存在于当前物理内存中,为0则PTE的其他部分都失去意义了,不用看了,直接触发page fault。P位为0的PTE也不会有对应的TLB entry,因为早在P位由1变为0的时候,对应的TLB就已经被flush掉了。
G (Global)- 这个标志位在这篇文章中有介绍,主要是用于context switch的时候不用flush掉kernel对应的TLB,所以这个标志位在TLB entry中也是存在的。
A(Access) - 当这个page被访问(读/写)过后,硬件将该位置1,TLB只会缓存access的值为1的page对应的映射关系。软件可将该位置0,然后对应的TLB将会被flush掉。这样,软件可以统计出每个page被访问的次数,作为内存不足时,判断该page是否应该被回收的参考。
D (Dirty)- 这个标志位只对file backed的page有意义,对anonymous的page是没有意义的。当page被写入后,硬件将该位置1,表明该page的内容比外部disk/flash对应部分要新,当系统内存不足,要将该page回收的时候,需首先将其内容flush到外部存储。之后软件将该标志位清0。
R/W和U/S属于权限控制类:
R/W(Read/Write) - 置为1表示该page是writable的,置为0则是readonly,对只读的page进行写操作会触发page fault。
U/S(User/Supervisor) - 置为0表示只有supervisor(比如操作系统中的kernel)才可访问该page,置为1表示user也可以访问。
PCD和PWT和cache属性相关:
PCD(Page Cache Disabled)- 置为1表示disable,即该page中的内容是不可以被cache的。如果置为0(enable),还要看CR0寄存器中的CD位这个总控开关是否也是0。
PWT (Page Write Through)- 置为1表示该page对应的cache部分采用write through的方式,否则采用write back。
再来看看存储page table首地址的PDE(page directory entry)里的标志位。
![](https://pic3.zhimg.com/v2-93b46fcb6087cd60325f207e503456ea_b.webp?consumer=ZHI_MENG)
上图第三行P位为0,处理方法和PTE是一样的。第二行是采用4KB大小page的情况,PDE指向下一级page table,则G位和Dirty位是无效的。PS(page size)位在PDE里就有意义了,为0表示为normal size的page(即4KB)。为1表示large page,即 ,PDE直接指向large page,如第一行所示(其实,还要看CR4寄存中PSE - page size enable这个总控开关位是否为1)。
![](https://pic1.zhimg.com/v2-84469f755f1b76994c25483535ae1c68_b.webp?consumer=ZHI_MENG)
再往上走就是存储当前进程的page directory首地址的CR3寄存器了,CR3里只有PCD和PWT位是有效的。
![](https://pic4.zhimg.com/v2-f0854233378d387fb4a8519bd53094d3_b.webp?consumer=ZHI_MENG)
之前的文章中称页表中的一个item为entry,是因为它存储了下级页表(或者页)的入口地址。但它同时存储了下级页表的诸多属性,可视为对下级页表的描述,因此又常被称为page table descriptor。类似的概念也有用在其他地方,比如从80286处理器开始引入的segment descriptor,高速网卡收发中用到的buffer descriptor等等。
【64位系统】
现在64位系统已经被广泛使用了,我们来看看page table descriptor在64位系统中有什么变化。
目前64位系统多采用48位虚拟地址,除了页表可扩展为4级(甚至5级),每级页表的高16位(5级的话是高7位)也被空了出来。其实低12位对属性的描述已经比较全面了,而且高16位通常需要预留给以后的扩展(比如5级页表),所以64位descriptor也就是在最高位(bit63)加入了一个XD(eXecute Disable)。
它跟CD一样,为1表示disable,与一般的逻辑是反的。在32位的x86中,没有对page是否可执行的权限控制位,也就是说你可以将stack上的数据当做代码来执行,这会被hacker利用来制造攻击。
另外,64位CR3中加入了对PCID的支持,因为CR3存放的是当前进程的页表首地址,所以CR3是最适合放PCID的了,但是高16位通常保留,所以只能塞到低12位和PCD,PWT共用bit位了。
当CR4寄存器的PCIDE位为1,此时这低12位就表示PCID,PCD和PWT就被覆盖了,那就默认为0吧,反正大部分情况下都是cache enable, write back的嘛。PCID位为0,PCD和PWT才又重见天日,可以在0和1之间自由切换了。
![](https://pic4.zhimg.com/v2-48347838b3305b0fb4a49296927283d7_b.webp?consumer=ZHI_MENG)
参考:
《Intel Softwate Development Manual Volume 3》Chapter 4 - Paging