背景
连续内存分配给内存分配带来了很多不便,可能所有空闲片区大小都无法满足需求大小,这个分配就会失败。基于这种现状,就有了非连续内存分配的需求。非连续分配成功的几率更高,但也面对更多的问题,比如分配时是不是1个字节的空间也可以进行分配?显然1个字节为单位分配太短了。因此我们需要选择不同尺度的基本块进行分配管理。实际操作系统中出现了两种基本块,一种是段式,一种是页式。段式分的块比较大,页式分配的块比较小。分配的块越小,逻辑地址与物理地址之间的映射关系就越复杂。根据这种映射关系形成了页表。还有一种结合的方式段页式管理。
连续分配的缺点
- 分配给程序的物理内存必须连续
- 存在外碎片和内碎片
- 内存分配的动态修改困难
- 内存利用率较低
非连续分配的设计目标
针对连续内存分配的这些缺点,非连续分配的设计目标就显而易见了:提高内存利用效率和管理灵活性。具体来说有以下几条:
- 允许一个程序使用非连续的物理地址空间;
- 允许共享代码与数据,因为各个进程可能有很多代码是相同的或者使用的数据是相同的,比如使用了同一个函数库;
- 支持动态加载和动态链接。
非连续分配需要解决的问题:
虚拟地址到物理地址的转换。当进程的内存地址连续时,只要知道进程内存起头的位置,就知道整个内存区域的位置了。而非连续分配则不然,逻辑地址中不同位置可能存储于物理内存中不同的区域,因此转换会比较复杂。具体的转换方式有两种:
- 软件实现,灵活但开销大,就比如数据的外排序时,需要分几部分将数据排序存到外存,最后再整体排序;
- 硬件实现,够用并且开销小。
非连续分配的硬件辅助机制:
非连续分配中如何选择非连续分配的内存分块大小是个很重要的问题。实际操作中主要有两种方式:
- 段式存储管理,内存基本块较大;
- 页式存储管理,内存基本块较小。
段式存储管理
段式存储管理中,将程序的逻辑地址空间内容分为不同的段进行管理,逻辑地址空间与物理地址空间之间的映射关系图可以如下所示:每个段内部是连续的,但是不同的段在物理内存上是不连续的。
段的概念:
- 段表示访问方式和存储数据等属性相同的一段地址空间;
- 一个段对应一个连续的内存块;
- 若干个段共同组成进程的逻辑地址空间。
段访问:
逻辑地址由二元组(s,addr)表示。其中 s 表示段号,addr 表示段内偏移量。如下图:
硬件实现:
如下图所示:在程序P运行过程中,CPU要访问逻辑地址中的某个位置,已经知道段号与偏移。操作系统中维护段表,段表记录段号对应的基址与长度,首先MMU比对偏移量与段号对应的长度,如果偏移量大于长度说明操作不合法内存异常,否则是合法的,此时将段基址与偏移相加得到真实物理地址,然后进行访问。
页式存储管理
页式存储管理中,物理内存被划分为大小相同的基本分配单位,我们称为页帧,页帧的大小必须是2的幂次方,这样进行地址转换的时候比较快,可以通过二进制移位实现。比如32位系统中,4Kbyte是常见的页帧大小。而逻辑内存也被划分为大小相同的基本分配单位,我们称为页面,页面与页帧大小必须相等。页帧与页面一个是对物理内存地址一个是对逻辑内存地址而言的。因此页式存储管理中要处理逻辑地址与物理地址的转换,也就变为对页面到页帧的转换。而储存映射关系的表我们称为页表,由操作系统维护。具体硬件实现则是由MMU和TLB共同实现。
帧
物理内存被分为大小相等的帧,物理内存的地址用一个(f,o)二元组表示,其中 f 是帧号,比如一个帧号由 f bit表示,那也就是说一共有 2**f 个帧,o 是帧内偏移,比如偏移由 S bit 表示,那么一个帧内 2**S 有字节。那么 物理地址 = 。
页
逻辑内存被划分为大小相等的页,表示方式与帧类似。由于帧与页的大小是相等的。因此在映射关系中 ,页内偏移与帧内偏移是相等的,但是页号与帧号通常是不相等的。因为逻辑内存是连续的,物理地址不是连续的。
页表
那么如何实现页与帧之间的地址转换呢?也就是如何实现逻辑地址与物理地址之间的转换。如下图:操作系统维护页表,页表内存储页号与帧号之间的映射关系。页表基址表明了页表存储在什么地方。比如当程序P执行过程中,CPU要访问(p, o),操作系统通过页表得到帧号f,通过(f, o)找到物理内存地址。而由于帧和页的大小是2的幂次方,因此实际上地址就是将 f 左移 S 位之后加上 o 即得到物理地址。
上面简单介绍了页表的作用,而实际上页表中还有一些其他辅助的内容。下面我们对页表进行详细讨论。每个进程都有一个页表,且有以下特点:
- 每个页面对应一个页表项
- 页表随进程运行状态而动态变化
- 页表的起始地址即基址存储在页表基址寄存器(PTBR: Page Table Base Register)中
页表内除了存储前面提过的帧号,还存储了一些页表项标志位:
- 存在位,记录该页号是否有对应的帧;
- 修改位,记录页面的内容是否修改了;
- 引用位,记录是否有对该页面的引用。
那么实际操作中,如下图,标志位为0意味着没有给页分配帧,就可以动态的进行变化。
页式访问的性能问题
内存访问的性能问题:访问一个物理内存单元需要两次访问内存。第一次先访问页表进行查询,第二次才是访问物理内存获取数据。
页表的大小问题:页表可能非常大。比如一个64位机器(2**64 字节内存),假如一页大小为1024字节(2**10),那么一共可以产生页(2**54 帧),64位的系统想要表示一个帧的地址就需要64位,也就是8字节,也就是说想表示一个帧,即使不考虑标志位也需要8字节,那使用很多页面,比如全部使用 2**54 页,就需要 2**57 字节,仅仅存储页表就要这么多字节占用了很多空间。
针对这些问题,也有一些对应的解决方案:
- 缓存:程序执行时具有连续性即相邻性,访问了一个数组第一个元素,下一个极有可能访问第二个元素,因此当我缓存下来页表项,利用缓存可以极大可能地访问到想要的数据。
- 间接访问:将长页表切断,实际就是多级页表,一层层去查询。
为了缓解上述性能问题,出现了一些解决方案,比如快表、多级页表和反置页表,下面对这几种解决方案进行详细讨论。
快表(TLB: Translation Look-aside Buffer):
快表是指缓存近期访问过的页表项。它有以下特点:
- TLB使用关联存储(associative memory)实现,具备快速访问性能,因为关联存储在CPU内;
- 如果TLB命中,可以直接访问物理内存;
- 如果TLB未命中,仍需查询页表,并将对应表项更新到TLB中。
如果能够很大比例地命中,就可以大幅度提高访问效率。
多级页表
多级页表顾名思义,类似于树的概念,一级一级往下查询,图示如下:比如图中是三级页表,逻辑地址的表示由四元组 表示。p1、p2、p3 分别表示在各级页表中的偏移,o 则表示物理内存中的偏移。通过多级页表可以有效减少每级页表的长度。
具体操作中,比二级页表的操作如下:一级页表的起始地址存储在CPU寄存器CR3中,然后一级一级根据偏移获取实际内存地址。
反置页表页寄存器
反置页表也是为了减少页表占用存储空间的一种做法。对于大地址空间系统,比如64位系统,多级页表变的非常繁琐,比如5级页表,正常情况下总共需要6次查询。并且逻辑地址空间的增长速度快于物理地址空间。每个进程有一个页表,随进程数量增加页表占用的存储空间也会增加。
针对上述情况,出现了页寄存器,页寄存器与反置页表思路相同:
- 不让页表与逻辑地址空间的大小相对应;
- 让页表与物理地址空间的大小相对应。
通过这个思路就可以使页表占用空间与进程数目没有关系,而至于物理内存的大小有关。接下来我们首先对页寄存器进行介绍,理解了页寄存器就可以轻松理解反置页表。页寄存器将每个物理帧与与一个页寄存器关联,寄存器内存储以下内容:
- 使用位:此帧是否被进程占用;
- 占用页号:对应的逻辑页号 p ;
- 保护位:比如可读、可写等性质。
由上面的内容我们可以看到页寄存器的优点是与逻辑地址空间大小无关,并且大小相对物理内存而言很小。缺点是需要在页寄存器中存储页号,也就是帧号是键,页号是帧,而进程运行时CPU产生的是逻辑地址页号,因此需要在页寄存器中搜索逻辑地址的页号。那么这种搜索是比较困难的。实际操作中,采用hash将页号映射到帧号,提高检索效率。这样又产生一个问题就是hash冲突,出现冲突时遍历冲突链表,找到需要的帧号。同时还可以将快表的思想融入进来,缓存使用过的页号帧号映射。引入快表又引入了新的问题,快表存储在CPU缓存中容量被限制到很小,同时快表的功耗是很大的。
反置页表
接下来我们看下反置页表。反置页表也是基于hash映射查找页对应的帧,但是反置页表将进程号也考虑进来,对进程号和页号同时进行hash。这里我自己的理解是最初给进程分配帧的时候就是根据哈希值进行分配的,因此查找时自然可以根据哈希值进行查询。冲突解决方式依然是链表的方式解决,查询发现第一个地址内的进程号与页号与需要的进程号与页号不一致时则继续查询链表中的下一个节点。过程如下图:哈希值加反置页表基址得到反置页表中的位置,验证进程号和页号,命中则根据hash结果查询物理内存。
具体实现看下图。pid和vpn(virtual page number)共同参与哈希,得到一个物理地址,根据这个地址去查询反置页表,验证pid和vpn是否匹配,不匹配则查询next中存储的地址,此时匹配,则访问此时对应的物理地址。
段页式存储管理
段页式存储管理是将前面讲过的段式存储管理与页式存储管理结合起来。这一节对段页式存储管理进行讨论。
段页式存储管理的需求
- 段式存储管理在内存保护方面有优势。如何理解呢?因为分段时是将具有相同访问方式和数据属性的内容分配到一段连续内存中,也就是每个段内的数据属性是相似的,便于统一管理和保护。
- 页式存储管理在内存利用和优化转移到后备存储方面有优势。因为页式存储管理中内存划分的基本块更小,对提高内存的利用率有很大帮助。同时对于内外存之间转移也是比较快和利用率高的。
段页式存储管理的实现
段页式存储管理的实现如下图。逻辑地址由段号+页号+业内偏移组成。首先根据寄存器得到段表基址,段表基址加段内偏移得到段表项,段表项内存储页表基址,页表基址加页偏移得到帧号,帧号加页内偏移得到实际物理内存地址。
段页式存储管理的优势
段页式存储管理将段式存储管理和也是存储管理的优势结合在了一起。最明显的一个是可以非常方便地实现进程间的段共享。如下图,只要两个进程中共享段指向相同的页表基址,就可以实现内存共享。