学过计组的同学都了解一点页式管理,就是将内存划分成较小的、大小固定的、等大的块。现在OS引入了进程的概念,那么为了匹配内存的分块,同样把进程也划分成同样大小的块。
这里区分两个概念
在组原的课程当中我们知道逻辑地址包括页号和页内偏移量,有以下两个基本公式:

一般来说,页面大小都是2的整数次幂,可以直接通过位运算得到结果。比如,页大小是1K = 2^10,那么偏移量 = 逻辑地址%页面大小 ,也就是逻辑地址后10位,前面的6位就是页号。
下标从0开始,所以page1是从上往下数第二个页面,即从1024开始到2047结束,然后偏移量offset=478是针对于1024的基地址的相对位移。
最后我们可以看到,图b在最下面有一段内部碎片,就是page2中不会使用到的区域,但是为了分页必须要分配。
分页的特点
为了便于管理进程,操作系统为每个进程维护一张页表,包含进程的每个页面所对应的页框位置(号),即逻辑地址和物理地址的映射关系。

如图所示,OS给A、C、D进程分配了页框,其中A是连续的0-3,而C是连续的7-10,D比较特殊是4-6+11-12,空闲13-14。由于会出现像D这种分配的页框不连续的情况,所以就必须使用页表专门记录。
接下来说一下,地址映射关系和存储保护机制

如图所示,页表开始地址b放在页表始址寄存器,类似于基址寄存器。页表长度l放在页表长度寄存器,防止地址越界。
根据图示,地址映射关系可以按以下步骤描述:
第一步:比较运算
p与 l 进行比较(比较条件为 P >= l),这决定了数据流向哪个分支,如果为真则地址越界抛出异常,反之进行下一步。第二步:加法运算
b 和页号 p 通过b+p*页表项长度查询页表,得到的结果p'就是所在页框的物理起始地址。第三步:输出映射
p' 和 d 直接结合作为输出,得到真实的物理地址。p=l也被视为是地址越界,因为我们的下标默认都是从0开始,所以页号最大是l-1,等于l也是非法的,实际写过代码的同学会比较熟悉。类似进程的管理,我们的程序在逻辑上也可以分开,也就是我们常说的分段管理,比如代码段、数据段等。段式管理中一般段长可变,但是有最大段长。
段式地址有两个部分构成,和页式管理类似

接下来我们了解一下段式存储的内存划分和分配:

分析:
注:有关存储保护和共享方面,本文最后一个标题会详细介绍
与页式管理类似,段式管理也有段表,不过由于每个段的长度不同,所以段表长度寄存器的值不是唯一的。

具体流程如下:
S 和段内地址 d 组成。Cb+S*段表项长度查找段号找到对应的段基地址 b 和段长 l。d 与 l 比较,确保 d 小于l,即段内偏移小于段长。d 在范围内,计算物理地址 b + d。页式管理和段式管理的比较

注:这里的一维和二维是根据程序员编程的角度分析,由于页的大小是固定的,所以给出一个逻辑地址OS就可以直接对应到物理地址,但是段的大小不一,就必须给定段名和段内地址,类似于基址变址寻址。
段页式管理就是结合页式管理和段式管理

如图所示,我们知道分页管理不能按照逻辑模块实现信息共享和保护,而分段管理又会像动态分区一样容易出现外碎片,虽然压缩紧凑可以解决这个问题,但是非常耗时且麻烦。于是就有人提出了段页式管理,综合了分段管理的逻辑模块和分页管理的无外碎片的优点。

如图所示,段页式管理的地址结构基于段式管理,将原先的段内地址改成了页号和页内偏移量。由于段页式管理的段的大小对于用户可见,对于OS不可知,所以是二维的地址结构。

其中,段表中存放了段号、页表长度、页表存放块号,一般段号都是隐藏的,而页表长度代替了原先的段表长度,页表存放块号也代替了原先的段基地址,通过该物理块号可以到页表中去查询实际物理块地址,可以通过图中箭头指向看到数据流向。

综上,我们可以图示得到段页式管理的地址访问步骤:
由于段长是不固定的,所以第四步需要根据段表中的页表长度检查页号是否越界。形式上,段页式管理就是将段式管理的段表长度改成页表长度,段基地址改成使用一级页表间接查询得到。
我们前面提到的段式管理有利于存储保护和共享,主要是基于以下两点:

然后我们拓展讲一下存储保护类型和共享方式,了解一下即可。
存储保护类型
存储共享方式
写时复制是Unix类OS的一个很重要的特点,子进程创建的时候不是完全继承父进程的地址空间,只有在真正需要写的时候才复制一份进行修改。