更新于 

段页式管理与虚拟内存概念

基本分段存储管理

分段

实际上类似于分页管理中的“分页”思想。我们先看一下分段的定义。

地址空间:按照程序自身的逻辑关系将程序划分为若干个段,每个段都有一个段名(在低级语言中,程序猿使用段名来编程),每段从0开始编制。而分段的内存分配规则就是:以段为单位进行分配,每个段在内存占据连续空间,但各个段可以离散存储不相邻,所以大小也是可以不相等的(不像分页必须规定固定大小的页帧)。

思考:为什么要引进分段的概念?

我们熟悉的数据段,代码段都是分段的概念,那么分段和分页的区别是什么呢,又为什么要引入分段的概念呢?我们思考分页的存储方式,他是直接将进程按照固定大小切成许多小部分存到了不同的页。但是这里会涉及到一个尴尬的问题,就是大部分页要不存的全是代码,要不存的全是数据,但是总是会有那么几个页且在了数据和代码的交界处,这样这个页就会同时存储着数据和代码的混合体,这种页对于系统管理当然是没有问题的,但是对于编程人员来说就会可读性很差,不友好,导致用户编程不方便。按照人类的正常思维,最好将程序的不同的种类分段,这一段全存储数据,这一段全存储数据等等这样就会对人类可读友好。所以引进了段的概念。即分页是按照物理大小划分,分段是按照逻辑功能模块划分。

那么分段系统中同样有逻辑地址此时的逻辑地址是由段号(段名)和段内地址(段内偏移量)组成,如下:

段号决定每个进程最多可以分为几个段,而段内偏移量决定了每个段的最大长度是多少。如上图这样的32位划分,那么如果系统是按照字节编址,那么段号16位,因此程序最多可以分为2^16=64K个段,段内地址为16位,因此每个段最大长度为2^16=64KB。

同样的和分页查找地址类似,段中的某一个地址也是根据段号+段内偏移量找到具体的位置,如下:

那么

段号一般是用[]包裹,然后根据逻辑地址和段号就可以求得段内偏移量同样也需要段表查找到段的物理起始地址然后就可以找到真正的物理地址进行相应的读/写操作了(实际上和分页存储的查找方式是一样的)。

段表

问题:程序会被分为许多个段,各段离散地装入内存中,为了保证程序能正常运行,就必须从物理内存中找到各个逻辑段的存放位置。因此同样需要建立一张段映射表简称“段表”。

不同的是此时段表没有块号而是改为基址,并且有新添加了一栏段长,这是因为分段存储中段的大小长度不固定而导致的。所以物理块的起始地址也就不能使用X+4*M这种来计算了。

这里有几个要注意的点:

  1. 每个段对应一个段表项,其中记录了该段在内存中的起始位置(又称“基址”)和段的长度
  2. 各个段表项的长度是相同的。例如:某系统按照字节寻址,采用分段存储管理,逻辑地址结构为(段号16位,段内地址16位),因此使用16位即可表示最大段长。物理内存大小为4GB(可以用32位表示整个物理内存)。因此可以让每个段表项占16+32=48位,即6B。由于段表项长度相同,因此段号是可以隐含的,不占存储空间。若段表其实地址为,。则K号段对应的段表项存放的地址为M+K*6。
思考:分段存储和大小不等的固定分区分配的区别?

我们知道在讲解分页时我们对比了分页和固定大小的固定分区分配两者的区别。这里我们同样来区分以下分段和大小不等的固定分区分配的区别。大小不等的固定分区分配是将内存块分成大小不等的一个个小空间,每个空间存放一个作业/进程,各个空间之间的进程/作业互不干扰。但是分段存储是将进程首先分成许多个大小长度不固定的块然后离散存储到物理块的不同位置处。这就是区别。

思考:此时我们是不是也需要考虑页表项凑成刚好被放入整数个时的字节大小问题?

答案是不用的,我们思考以下问什么,对于分页存储每一个存储单元是固定大小的,所以需要连续存储时就是选取相邻连续的页帧凑成的,只有刚好恰好装满整数个时才能实现页表项之间的连续。但是现在段存储长度是可以改变的,所以就是页表项有多少,段的大小就是多少不需要再去凑了,毕竟段表肯定也是以段的方式存储到物理内存的(此时我们不讨论段页式存储,就仅仅是纯页式存储或纯段式存储)

思考:有没有可能有多级段表?

应该是没有的,毕竟之所以有多级页表是因为页完全不需要连续,可以在离散的基础上再次离散,但是段就是按照逻辑模块进行划分的最小单元了,在离散逻辑模块就不连续了。所以应该是没有多级段表的。

地址变换

同样的我们也讨论以下如何实现逻辑地址到物理地址的转换。此时就没有什么“相除取页号,取余得偏移量”的步骤了,段式存储最大的特点就是在指令中直接就指出了段号和逻辑地址,那么直接就可以求得段内偏移然后查表就可以了。即少了求段号的一步(段号一般用[]包裹)。

同样的此时在二进制串中的转换方法是不变的,还是后K位表示偏移量,剩下的位数表示段号,然后根据段号查表找到基址,那么物理地址就是基址+段内偏移量。多简单,都不用像页式存储那样还得得到块号自己算物理起始地址,此时段式存储直接就可以根据表得到基址。

具体的变换演示如图:

我们一定要注意此时还多了一个步骤就是步骤4需要进行检查段内偏移量是否越界了。这个可千万不要忘记。所以也是需要进行两次访存一次是查表,一次是访问数据。

分段、分页管理的对比

我们从以下几个角度探讨:

角度1:划分单位

页是信息的物理单位,分页的主要目的是为了实现离散存储,提高内存的利用率(内部碎片很少)。分页仅仅是系统管理上的需要,完全是系统行为,对用户是不可见的。

段是信息的逻辑单位,分段的主要目的是更好的满足用户需求,一个段通常包含着一组属于一个逻辑模块的信息。分段对用户是可见的,用户编程时需要显示的给段名。

角度2:单位大小

页的大小固定由系统决定

段的长度不固定,决定于用户编写的程序

角度3:地址空间

分页的用户进程地址空间是一维的,程序猿只需要给出一个记忆符即可表示一个地址

分段的用户进程地址空间是二维的,程序猿在标识一个地址时,既要给出段名,也要给出段内地址。

角度4:信息的共享与保护

分段比分页更容易实现信息的共享和保护。不能被修改的代码成为纯代码或可重入代码(不属于临界资源),这样的代码是可以共享的。可修改的代码是不能共享的(比如,有一个代码段中有很多变量,各进程并发地同时访问可能造成数据不一致)

思考:如何使得某个段是临界资源可以被共享?

很简单,就是使表项的基址指向同一个段即可:

而页不是按照逻辑模块划分的,这就很难实现共享。因为假设一个函数模块被放在了两个页A,B,那么如果想让这个函数模块被共享,那么就需要A,B页都是可以允许共享的,但是如果此时A只有一半存的是函数模块,另一半存的是不允许共享的资源,那么显然现在A就很难实现共享,那么这个函数模块就也不能被共享了,这种冲突就造成了分页很难实现共享,同时风险也就更大。

角度5:访存流程

对于分页存储(单级页表):第一次访存–查内存中的页表,第二次访存–访问目标内存单元。总共两次访存。

对于分段存储:第一次访存–查内存中的段表,第二次访存–访问目标内存单元。总共两次访存。

并且分页和分段都可以引入快表机构,这样近期访问过得表项再次被访问时就只需要访存一次了。

总结

段页式管理方式

前面我们一直都是讲解的纯分页式管理或者纯段式管理,那么能不能同时集合两者的优点,这就出现了段页式管理方式。

分页、分段的优缺点

存储方式 优点 缺点
分页管理 内存空间利用率高,不会产生外部碎片,只会有少量的页内碎片 不方便按照逻辑模块实现信息的共享和保护
分段管理 很方便按照逻辑模块实现信息的共享和保护 如果段长过大,为其分配很大的连续空间会很不方便。另外,段式管理会产生很大的外部碎片(当然可以使用“紧凑”技术来解决,但是时间开销大)

分段+分页=段页式管理

我们前面说过页可以再分页,但是段不能再分段,但是我们可以用分段后在分页的方式存储,这样逻辑模块功能划分的优点保存的同时也可以将较大的段离散存储。所以首先要知道段页式存储一定是先分段再分页并且是对相同的段分页。如下:

段页式管理的逻辑地址结构

那块此时逻辑地址的结构也是变化的,我们此时会将逻辑地址变为如下:

此时就没有段内偏移量了,“分段”对用户是可见的,程序猿编程时需要显式地给出段号、段内地址。而将各段“分页”对用户是不可见的,系统会根据内地址自动划分页号和页内偏移量来具体存储段的位置。因此段页式管理的地址结构也是二维的。

段号的位数决定了每个进程可以最多被分成几个段,页号位数决定了每个段可以最多被分成几个页,而页内偏移量决定了页面大小和内存块大小(当然如果是多级页表存储,页号还可以分成许多多级页号更复杂)。

例如上图中的结构如果系统是按字节寻址的,那段号占16位因此该系统中,每个进程最多有2^16=64个段,而页号占4位,因此每个段最多被分为2^4=16页,页内偏移量是12位,因此每个页面/内存块大小为2^12=4096=4KB。

段表、页表

显然此时段表和页表也是有变化的,如下图:

此时每个段对应一个段表项,每个段表项由段号,页表长度(实际上就是段长度),页表存放块号(可以算出页表其实地址)组成。每个段表项长度相同,段号是隐含的。所以段表变化很大,页表基本上不变。每个页表对应一个页表项,每个页表项由页号和页面存放的内存块号组成。每个页表项长度相同,页号是隐含的。当然对于页表为了连续存储公式计算方便,最好还是一个页帧可以放入整数个页表项。

地址变换

注意此时一般来说就需要三次访存了,一次查段表,二次查页表,三次访存目标内存单元。当然如果加入了快表,那就只需要一次访存就是访存目标内存单元。

总结

虚拟内存的基本概念

这部分主要谈论内存管理中内存空间的扩充部分。我们之前已经讲过覆盖和交换技术了,那么这次来讲一讲虚拟存储技术(在中级调度中有过应用)。

传统存储管理方式的特征与缺点

所以我们之前讲的都是属于传统存储管理的部分。缺点是长期占用内存,所以可以使用虚拟存储技术解决。在传统的存储管理方式中有以下特点:

  1. 一次性:作业必须一次性全部装入内存才能开始运行。这会造成两个问题:一是作业很大时不能全部装入内存导致大作业无法运行,二是当大作业要求运行时,由于内存无法容纳所有作业,因此只能有少量作业能运行,导致多道程序并发度降低。
  2. 驻留性:一旦作业被装入内存,就会一直驻留在内存中,直至作业运行结束。事实上,在一个时间段内,只需要访问作业的一小部分就可以正常运行了,这就导致内存中会驻留大量的,暂时用不到的数据,浪费了宝贵的内存资源。

而以上的缺点我们都可以使用虚拟存储技术来解决。

局部性原理

我们之前已经讲过局部性原理了,实际上虚拟存储技术就应用了局部性原理的思想,已知的应用有快表机构就是将常访问的页表项副本放到更快速访问的TLB中,这种高速缓冲技术的思想就是利用了局部性原理,将近期频繁访问到的数据放到更高速的存储器中,暂时用不到的就放在更低速的存储器中。

虚拟内存的定义和特征

基于局部性原理,在程序装入时,可以将程序中很快会用到的部分装入内存,暂时用不到的部分留在外存,就可以让程序开始执行。

在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。(覆盖技术的思想)

如果内存空间不够,由操作系统将内存中暂时用不到的信息换出到外存。(交换技术的思想)

在操作系统的管理下,在用户看来似乎有一个比实大的多的内存,这就是虚拟内存。(操作系统虚拟性的体现)

思考:覆盖技术,交换技术,虚拟存储技术的区别?

我们可以发现实际上虚拟内存的出现就是虚拟存储技术的具体应用,而且思想貌似就是覆盖交换技术,实际上他们的思路就是一样的,只是作用的单位不同。覆盖技术和交换技术都是对于进程来说的,而虚拟内存是对内存中的数据信息块进行管理。

易混淆知识点:

  • 虚拟内存的最大容量是由计算机的地址结构(CPU寻址范围)确定的
  • 虚拟内存的实际容量=min(内存和外存容量之和,CPU寻址范围)

例如:某计算机地址结构为32位,按字节编址,内存大小为512MB,外存大小为2GB。

那么虚拟内存的最大容量就是2^32B=4GB(虚拟出来的),但是虚拟内存的实际容量就是min(2^32B,512MB+2GB)=2GB+512MB

思考:虚拟内存的特征?

虚拟内存凭借虚拟存储技术有以下三个特点:

  1. 多次性:无需在作业运行时一次性全部装入内存,而是允许分成多次调入内存
  2. 对换性:在作业运行时无需一直常驻内存,而是允许在作业运行过程中,将作业换入、换出。
  3. 虚拟性:从逻辑上扩充了内存的容量,使用户看到的内存容量远大于实际容量。

虚拟内存技术的应用条件

虚拟内存技术允许一个作业多次调入内存,如果是采用的连续分配方式很明显不方便,所以虚拟内存的实现方式需要建立在离散分配的内存管理方式基础上。

但是区别于传统的离散分配存储管理,虚拟内存不是一次性装入全部,所以才会像之前所说的出现缺页中断等现象,此时就需要请求调入内存了,所以在虚拟内存技术中很明显会频繁的发生"请求"所以在虚拟内存技术下的存储方式叫做

所以主要区别就是在程序执行过程中,当所访问的信息不在内存时,有操作系统负责将所需要的信息从外存调入到内存中,然后继续执行程序。若内存空间不够了,就由操作系统将内存中暂时不需要的信息换出到外存。所以操作系统需提供请求页面功能和页面置换功能,当然后面会讲解页面置换算法决定具体该将那个信息暂时调出内存。

总结