产生背景:不管是固定分区还是动态分区,都会有一个现象级影响,内存利用率不高。固定分区会产生较大的内部碎片,而动态分区会产生很多的外部碎片(内存被一直分区,导致有很多分区,若有一个大一点的进程进入内存,是进不来的),这些内存碎片都利用不起来。
定义:进程运行时在内存中的地址空间可以是离散的,一块一块的。
页就是内存分配的基本单元。
整个内存按照固定大小去分区,每个分区就是一个页。
说白了就是固定分区。一般为4K。
没有了外部碎片,即使有内部碎片,但是很小,可以接受。
进程中的是逻辑地址,而内存中的是物理地址。
页实现了物理地址的分配,接下来我们要将逻辑地址分配,从而更好地关联,映射起来。
所以,逻辑地址地分页地大小也是4K,即和物理地址的页相同。
利用页表来记录此进程分了多少页,然后在和物理地址的页对应上。
页表是一种数据结构,存放在进程PCB中。

页表是一个一维数组,因为页号和数组下标索引刚好对应上。
页表不能太大,会降低内存利用率。
页表归根结底也是在内存中,准确地来说是在PCB中,那么CPU也需要一个组件来告诉它页表在哪里。由于就有了页表寄存器,它不存储页表,只存储页表地起始地址和页表长度。

页表实现了逻辑地址和物理地址的映射关系,但这还远远不够,需要地址变换机构得到真正的物理地址(即那一串二进制数字),此动作非常频繁,需要足够快的速度,所以交给硬件去完成。
每次访问内存都需要进行地址转换。


A % 4K = A & (4K-1)
根据页号找到块号,即访问页表,是一次访存。计算出物理地址后,还要去进行数据访存。光要读取这个数据,我就已经访问了两次内存了。性能不太好。
可以将页表放入到高速缓存中。
流程即:当一个进程要对一个数据进行操作时,不去内存中的慢表去看,而是先去高速缓存中的快表去看,如果匹配成功,那么进行访存。 <--------> 一次访存。
若快表中没有,那么回慢表中找,同时将此条记录同步到快表。(局部性原理)
但是你这样有点多此一举,相当于访问了两次页表,所以OS有一个优化的动作,会让访问快表和访问慢表并行,看看快表是否命中。
快表是二维表,因为它的页号不连续。

聊聊为什么会有两级页表甚至是多级页表,我们以32位机为例。
一个页表最多占多少内存取决于我们存储多少个页号。在逻辑地址中前20为用作页号。
2的20次方就是1024×1024,然后在×4个字节,所以每个页表最大为4MB,这个大页表放在内存中显示很不好,太大了,占用了大量的连续空间,而且程序是动态装入的,我一段时间只需要访问某一部分页表,没必要把整个页表加载进来。而一个一个页表所映射的内存大小为 1024×1024×4K ,即4GB。
所以就产生了多级页表,我按照1024个页号进行划分,存入内存中刚好就是4KB,而且二级页表也是4KB,与我的页框大小完美吻合,这可能就是设计者的初衷。这样我的二级页表是实现了离散存储,建立了一个索引目录进行管理。
比如,我要取数据。在页表寄存器中找到了一级页表的地址,然后进行位运算得到一级页号,然后得到二级页表的存放位置,然后在进行位运算得到实际地址的块号,然后在加上偏移地址就找到了。
下图为逻辑地址表




将用户进程进行段式管理,因为每一个都是4K,页太多了,不利于管理。而且用户进程是模块化的,不一定就是4K。还是一句话,按照需求,按照模块化进行分段。
编译器帮我们分段。
段表是二维空间。
段表的起始地址和长度在寄存器中,即段表寄存器。实际的数据放在内存中。


将用户进程进行逻辑分段,将内存进行物理分页。
首先从段表寄存器中得到段表的起始位置,即去内存中寻找,利用位运算得到段号,然后得到相应的页表和长度。这里有一个对应关系,一个段表项即一个模块,对应着至少一个页框,所以一个段表项对应着一个页表,整个页表你需要段表项的块号去找,得到页表后,利用位运算得到页号,然后再得到了块号,然后与偏移地址结合得到物理地址。




