内存空间的分配与回收

连续分配管理方式

连续分配:指为用户进程分配的必须是一个连续的内存空间

单一连续分配:内存被分为系统区和用户区。系统区通常位于内存的低地址部分,用于存放操作系统相关数据;用户区用于存放用户进程相关数据。内存中只能有一道用户程序,用户程序独占整个用户区空间



优点:实现简单,无外部碎片(内存中某些空闲分区由于太小而难以利用);可采用覆盖技术扩充内存,不一定需要内存保护

缺点:只能用于单用户,单任务的操作系统中,有内部碎片(分配给某进程的内存区域中,如果有些部分没有用上,这就是内部碎片),存储器利用率极低

固定分区分配:

为了能在内存中装入多道程序,又让这些程序互不干扰,会将整个用户空间划分为若干个固定大小的分区,每个分区内只放一道作业

有两种固定分区的划分方法,一种是分区大小相等,即在用户区分出若干个大小一样的分区,这样必然会导致不灵活,有的小作业却独占那么大的内存,有的大作业因为内存不够而进不来,但这种方法适用于控制多个相同对象的场合

另一种是分区大小不等,这样当然是增加了灵活性,可以满足不同大小的进程要求

操作系统会建立一个数据结构——分区说明表,来实现各个分区的分配与回收。他会有一个是状态,但被占用了会修改状态为已分配

那固定分区分配的优点:实现简单,无外部碎片

缺点:当用户程序太大,可能所有分区都不能满足要求,此时不得不采用覆盖技术空充,但会境地性能;会产生内部碎片,内存利用率低

动态分区分配:

动态分区分配不会预先划分内存分区,而是在进程装入内存时,根据进程的大小动态地建立分区,并是分区大小正好适合进程的需要。有外部碎片,无内部碎片

系统会用空闲分区表或者空闲分区链这两种数据结构来记录内存的使用情况

当有很多空闲分区都能 满足时,会按照动态区分配算法来分配,具体以后再说

回收:(合并!!!)若某分区只被占了一部分,就在相应的表或者链改数据,若彻底被占了就直接在相应的表或者链去掉一个,若是回收内存,就要和他相邻的空闲内存分区合并,并把相邻的分区在相应的表或者链改数据

外部碎片:提到了很多次外部碎片这个问题,那怎么解决这个问题呢,其实办法是有的,那就是紧凑技术,就是将 内存区挪动,使他腾出一块连续的内存,这也就是为什么我们在装入时最好选择动态重定位方式。虽然紧凑技术可以解决存在外部碎片的问题,但时间代价很大

## 非连续分配管理方式

非连续分配方式:为用户分配的是一些分散的内存空间

基本分页存储管理:

如图所示,我将内存区分成每块为10MB的分区,也就是固定分区,这个时候进程A来了,他是23MB,我的每个区都满足不了,我还得满足他只能被放在一个完整的分区里,那么这个时候我必然要使用覆盖技术,但这很浪费。那么怎么解决这个问题呢,我可不可以把进程A拆成10+10+3呢,然后分别放在三个不同的分区里(不用连续),答案是可以的,这就是基本分页存储,但是需要注意的是,必须是按照分区大小拆,我不能拆成8+10+5,但是好像这么做我会留下一个7MB的内部碎片啊

那我们如果把分区划分的小一点呢,比如说化成2MB,那就是2*11+1,这样我只会有1MB的内存碎片,试着把分区划的更小,你会发现内存碎片也更小

基本分页存储的思想就是把内存的用户区分成一个个相等小的分区,再按照分区将进程拆分成一个个小部分,小分区越小,产生的内部碎片越少,实际上就是将固定分区分配改造成非连续分配版本。每个分区就是一个页框,每个页框有一个页框号,页框号从0开始,边框号小的在低地址。将进程按页框大小分成若干部分,每部分叫页或页面,页的编号时页号,也是从0开始。

那也就是说页面会被一一放到页框里,但最后一个页面的大小可能会小于页框的大小,因此页框大小不应太大,否则会产生过大的内部碎片。

同时页面不一定要放入连续的页框中,可以间隔地放入页框中


实现方式:与之前连续存储不同的是,我现在把每一块都分散着放了,我的物理地址该怎么获取,因此这个实现就是我怎么能根据逻辑地址转换成物理地址

如图所示,这种方式是之前说过的装入方式的一种,动态重定位,我们把自己想做操作系统,我怎么找到逻辑地址为80对应的物理地址呢?首先我知道1页有50的存储单元,那80就应该在1号页,相对于1号页的偏移地址为30,好了,搞清楚之后我要装入了,这个时候我就要弄清楚1号页的其实物理地址是多少,哦是在450,那我的逻辑地址为80对应的物理地址就应该是450+30=480

因此我要进行如下4步才能将逻辑地址转为物理地址

算出逻辑地址对应的页号(逻辑地址/页面长度)(取整数部分)

该页号在内存中的起始地址

算出逻辑地址在该页面的偏移量(逻辑地址%页面长度)

物理地址=页面地址+页内偏移量

但操作系统一般都会让页号,页内偏移量,页面大小为2的整数幂,这样可以很轻易的就得到物理地址

分页

如上图我们可以看见,如果我用32位表示逻辑地址,页面大小是12位,那后12位(页内偏移量)就是偏移量,前32-12=20位就是页号,则一个页面大小是2的12次方,共有2的20次方个页面

那么我怎么获取相应的页号在内存中对应的起始地址呢?

分页

光看图可能有点晦涩难懂,假设我知道是在1页,页框大小为50,那看图,在页表里页号为1对应着块号为6,那我的起始地址就是6*50=300

实际上每个页表项的长度是一定的,页号是隐含的,具体理解看下图

分页

也就是说在页表里实际上是只有块号,因为页号直接就顺序排列了,至于上图为啥要算字节,是因为要按字节编址

基本分段存储管理:

分段:是将进程按照逻辑分为若干个段,每个段都有自己的段名(程序员自己命名的),每段从零开始编址

如下图所示,就是程序员给进程段起段名,每一段地址都是从零开始,回忆分页管理,这里是不等分的,而分页管理是将内存等分很多块,然后进程对应内存没一块的大小来分割自己进入内存

与分页一样,在装入内存时存在映射关系,且在内存里可不连续,虽然程序员命名了进程段,但编译程序会将段名转换成段号,这样才能让操作系统认识

如此看来,与分页相比,采用分段是用户可见的,程序可读性更高,但分页对于用户是不可见的

分段系统的逻辑地址由段号和段内地址(段内偏移量)组成,比如:

段号的位数决定每个进程最多可以分多少个段

比如上面段号有16位,那他就最多能表示2的16次方个,因此进程最多被分为64K个段

段内地址的长度决定了每个段的最大长度是多少

比如上面段内地址有16位,如按字节编址,则每个段最大长度为64KB

段表:与页表差不多,只不过多了段长,因为分段不是等分的,同时,段表中段号也是隐式的,不占地址空间

其中,每个段表项的长度实际上是相同的,如上面说的假设逻辑地址=16位段号+16位段内地址,那么用16位表示段长(不会超过最大段内地址)就足够了,若物理内存是4GB,则可以用32位表示整个物理内存,因此,让每个段表项占16+32=48=6B就行了,因此若段表的起始地址是M,则K号段对应的段表项存放地址为M+K*6(注意这里说的是段表项,是在段表里找段表项,不是进程)

机器指令中的逻辑地址用二进制表示:00000000000000100000000100000000

其中前十六位表示段号,后十六位表示段内地址,那怎么将逻辑地址转为物理
地址呢

在段表上处理机之前,段表寄存器会被存放在系统区的PCB中

注意第4步,需要比较段内长度和段长,因为不等分,所以有违规的可能

分页与分段的区别:

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

分段的用户进程地址是二维的,程序员既要给出段名,也要给出段内地址

  • 分段比分页更容易实现信息的共享和保护,比如下面的消费者进程和生产者进程都想访问1号段,分段就能体现出他的优越性,但是注意只能共享不能修改的代码(纯代码或可重入代码,不属于临界区)

  • 分页分段都是两次访存,当然分段也可引入快表,加快地址变换速度
段页式管理方式:

分页,分段优缺点:

那结合分段分页就诞生了段页式管理

因此段页式管理的逻辑地址结构应该是

段号的位数决定了进程最多能被分成多少段:段号占16位,所以进程最多被分成2的16次方->64K个段

页号位数决定了每个段最多能分多少个页一:页号有四位,所以每个段最多被分成2的四次方->16个页面

页内偏移量决定了每个页面大小是多少:页内偏移量占了12位,因此每个页面大小为2的12次方,->4KB(按字节编址)

同样的用户只能看到分段,分页是由操作系统做的,用户看不见,因此段页式管理的地址空间是二维的

因此一个进程对应一个段表,多个页面

怎么将逻辑地址转换成物理地址


其中第四步,是因为段长度不等,所以要检查,和分段管理要检查段内地址是一个道理

当然也可以用快叫,这样如果命中就只需要一次访存

-------------本文结束感谢您的阅读-------------