内存需要被分成固定大小的页(Page),通过虚拟地址到物理地址的转换、才能找到实际物理地址、程序看到的地址都是虚拟地址
简单页表
虚拟内存和物理内存映射、最直观的方法就是建立一张映射表、实现虚拟内存到物理内存的一一映射
其实就是页表(Page)
这种转换会把一个内存地址分成页号Directory和偏移量Offset两个部分
前边的高位、就是内存地址的页号; 后边的地位就是内存地址里的偏移量.
地址转换的页表、只保留页号之间的映射关系即可.
同一个页面的内存、在物理层面是连续的、eg. 一个页大小为4K
需要20位的高位和12位的地位
image.png
内存地址转换:
- 将虚拟内存地址、切分成页号和偏移量的组合
- 从页表里边、查询出虚拟页号对应的物理页号
- 拿物理页号 + 偏移量 -> 物理内存地址
思考: 这样一个页表需要多大空间 ?
32位的内存地址空间、需要记录2^20大小的数组、一个页号是完整的32位的4字节、这样一个页表就需要4M的空间
并且: 每个进程都有属于自己的虚拟地址空间、也就是、每个进程都需要这样一个页表.
不管进程本身是只有几KB大小的程序、还是需要几GB这样的内存空间、都需要这样一个页表
现在的内存大多超过了4G、若使用上边的数据结构来保存页面、内存占用会更大、如何处理呢 ?
多级页表
大部分进程占用的内存是有限的、需要的页自然也是有限的、只保存 `用到的页`
之间的映射关系即可
那、是不是可以选择hash表呢 ?其实采用的是多级页表. 为什么呢 ?
在整个进程的内存地址空间、通常是 `两头实、中间空`. 在程序运行的时候、内存地址从顶部往下、
不断分配占用的桟的空间. 而堆的空间、则是从底部往上、不断分配占用的
所以、在一个世纪的程序进程里、虚拟内存占用的地址空间、通常是两段连续的空间、而不是完全散落的随机的内存地址.
而多级页表、就很适合这样的内存地址分布
image.png以一个4级的多级页表为例: 同一个虚拟内存地址、偏移量的部分和简单页表一样不变、但原有的页号部分、拆成4段、从低到高、分成4级到1级这样4个页表索引
对应的、一个进程会有一个4级页表、先通过4级页表索引、找到4级页表里对应的条目Entry. 存放的是一张3级页表所在的位置
4级页表的每一个条目、都对应这一张3级页表、所以可能有多张3级页表
找到对应的3级页表之后、用3级页表找对应3级索引的条目.
3级页表的索引会指向一个2级页表、同样、二级页表使用索引指向1级页表
1级页表的条目对应的数据内容是物理页号.
拿到物理页号之后、使用 `页号 + 偏移量 ` 得到最终的物理内存地址
可能有很多张1级页表、2级页表甚至3级页表、但: 因为实际的虚拟内存地址通常是连续的、可能只需要很少的2级页表、甚至只需要一张3级页表就可以了
事实上、多级页表就像一个多叉树、常称为页表树(Page Table Tree)
因为虚拟地址分布的连续性、树的第一层节点的指针、很多是空的、无需对应子树、找最终物理页号
就类似通过一个特定的访问路径、走到树最底层的叶子节点.
这样分成4级的多级页表来看、若每一级都用5个bit表示、每一张某一级的页表、只需要2^5=32个条目
若每个条目还是4个字节、共需要128字节、而一个一级索引表、对应32个4KB的、即共128KB的大小
一个填满的2级索引、对应32个1级索引、即4MB的大小
若一个进程占用8MB的内存空间、分成2个4MB的连续空间、则它需要2个独立的、填满的2级索引表、
64个1级索引表、2个独立的3级索引表、1个4级索引表、共需要64+2+2+1=69个索引表、每个128字节(9KB)
差不多只有简单页表的1/500、不过也带来了时间上的开销
原本只需要一次内存访问就可以找到物理页号、算出物理内存地址、使用4级页表的话、就需要4次内存访问、才能找到物理页号
思考:
内存访问比Cache的性能要差很多、原本只是要做一个简单地址转换、反而要访问好几次内存、时间性能的损耗如何优化呢 ?
--- 下文分析
网友评论