今天在思考一个可能由page table引发的Linux操作系统内存报警问题时发现基础知识缺失的太多,因此找了几本操作系统相关的书复习了一下,在这里记下来。
首先上一幅32位寻址空间的虚拟地址结构图:(仅适用于一级页表,页面大小为4KB)
前12位表示页内偏移量,后20位表示页号,可寻址2^20=1M个页,假如页面大小为4KB,那么就能寻址4GB的虚拟地址。
那么这样一个32位的虚拟地址是如何转换为物理地址的呢?
下图说明了一切:
图1.分页系统的地址变换机构 --摘自《计算机操作系统》汤小丹版
寻址步骤解析:
1.首先假设虚拟地址为A,页面大小为L,则可以通过以下公式迅速计算出相应的页号P和页内偏移量d:
P=INT [A/L] --除法取整
d=A%L --求余
2.得到P和d之后,使用P与页表寄存器中的寄存的页表始址(一个物理地址),进行逻辑运算得到页表项的具体位置。(其实就是通过页表始址找到页表页,再在里边找到页号)
3.在此页表项中取到后半截的块号--通过块号得到块的物理始址,通过这个物理始址与页内偏移量相加得到真正的物理地址。
整个过程很简单,就两个加法,两次寻址。
举例说明:
一个页表的结构如下所示:(这里省了标识页表的始址,直接给了页表的内容)
页号 块号 0 3 1 2 2 0 3 1
假设有一个虚拟地址0x000B,页面大小为4KB(0x1000),那么:
1.P=0x000B/0x1000=0; d=0x000B%0x1000=0x000B;因此页号为0,页内偏移量为0x000B
2.通过与寄存器的逻辑运算找到如上页面的0页号,发现0号对应物理块号是3
3.3*0x1000+0x000B=0x300B
因此真实的物理地址就是0x300B。
拓展知识:
由于页表空间必须是连续的内存地址,这样的开销在32位分页系统中几乎是不可接受的,因此我们一般采用如下两种办法解决此问题:
1.多级页表,即页表的页表,这样页表可以离散的分布于内存之中,不必是连续内存,64位寻址空间的分页系统常见3级页表,32位的常见两级页表。
2.只缓存一部分的页表,其他页表部分在外存中,根据需要调入,这样解决了页表空间占用的问题;对于多级页表,其外部页表必须调入内存,页表则可以只缓存一部分。
现在常见的64位操作系统一般采用多级页表的方式,大多数为3级页表,其实就是页表3级索引,因此CPU多出了2个页表寄存器来记录多级页表的物理始址,但是本质与一级页表寻址方式类似。
自2.6.11内核版本以来,Linux采用4级页表的分页方式(9+9+9+12)。
在64位分页系统中,由于可寻址范围已经大大扩展,因此所有的页目录和页表都已被存入内存。
对于用户空间和内核空间,以及高端内存、低端内存还有线性映射、非线性映射等概念,在64位分页系统中应当全部舍弃,除非你是研究内核的。
最后再放一个段页式逻辑地址-->线性地址(虚拟地址)-->物理地址的转换图: