文章目录

  • 内存管理
    • 回顾
    • 内存管理的作用是什么?
    • 如何分配物理内存
    • 物理内存分配方案
      • 1.连续分配存储管理(可应用于嵌入式设备)
        • 1.1单一连续分配
        • 1.2固定分区分配
        • 1.3可变分区分配
        • 连续分配存储方案总结
      • 2.分页存储管理
        • 概念补充
        • 如何管理离散页?
        • 实例练习
        • 分析上述方案的优缺点
          • 问题1:
          • 问题2:
      • 3.多级页表
        • 二级页表如何将逻辑地址转换成物理地址?

内存管理

操作系统内核的几大模块:

进程调度是核心模块,本系列博客的大部分内容都在讲解进程调度模块。从本篇开始讲解内存管理,可能分成两篇来讲解。

回顾

进程的内存抽象:

还是再讲解下这张图:

  • 每个进程有text段:存放代码
  • data段:存放全局变量和静态变量
  • heap区:new, malloc从此处申请空间
  • stack区:存放局部变量
  • 栈从高地址向低地址扩展

加过内存条吗?内存条长啥样?

内存条就长这样,当然还有一个问题:

  • 早期的计算机内存很小很小,比如1991年Linux0.11版内核只能管理16M的内存空间,那么内存如果不够用怎么办?

    • 有句话说的好,饭不够汤来凑:内存不够磁盘来凑。这里涉及虚拟内存,参见我的另一篇博客。
  • 关于虚拟内存:饭不够汤来凑,饭和汤的区别是什么?
    • 饭顶饱,汤不顶饱 对叭
    • 那么内存和磁盘的区别呢?
    • 同理,内存块,磁盘慢

磁盘长啥样?现在主要有两种:SSD(固态)和HHD(机械)

左边的是固态(笔者在更换大容量SSD的时候拍的),右边的是机械,机械硬盘俗称自行车(因为比较慢)。

现在的SSD降价很厉害,所以很多童鞋们都准备丢掉自行车啦!

好了,不闲扯了,步入正题

内存管理的作用是什么?

  • 我们上面的第一张图给出了进程的内存抽象表示,也就是说进程创建的时候,需要获取一段内存空间,从而建立自己的各个段。
  • 后面的几张图给出了内存条和磁盘的图片,那么问题来了,怎么在这两者之间建立联系?
  • 这就是内存管理所起的作用,将内存和磁盘组成的内存空间 分配 给进程使用,如何分配就是我们要讲解的重点。

如何分配物理内存

下面以提问-回答的方式来思考如何分配物理内存!

首先要考虑一个问题,能不能把所有内存都给用户?

  • 当然不能,想想我们开机后内存剩余多少?
  • 笔者电脑开机后,内存已经被占用了近4G,这4G的内存去哪里了?
  • 大部分给操作系统使用了(当然也有一个开机启动的软件占用了一部分内存),即给内核使用。

课外充电站:

Linux0.11版内核中一共可以管理16M内存,其中0-1M给内核使用,其余才能分配给用户

既然内存分成两部分:内核使用部分、用户使用部分,那么它们是否是平等的呢?

  • 不平等,试想如果是平等的:那么我们可以随意修改内核所用的内存,那么系统岂不是乱套啦!
  • 既然不平等,就要引入保护机制,防止恶意修改。

再来一个问题:我们如何记录哪些内存已经分配/未分配呢?这些信息保存在哪里?

  • 衍生问题:新的请求到达时如何分配内存给它?
  • 进程运行结束如何回收内存?

有了上述问题,我们开始研究内存分配方案。

物理内存分配方案

有以下几种物理内存分配方案:

  1. 连续分配存储管理
  2. 分页存储管理
  3. 多级页表管理
  4. 虚拟存储管理
  5. 分段存储管理

我们本应该先讲分段再讲分页,但是连续分配和分页机制是思维的一个转变,具有衔接性,所以我们讲完连续分配就讲分页管理。

1.连续分配存储管理(可应用于嵌入式设备)

连续分配存储管理可以再细分成如下几类:

  1. 单一连续分配
  2. 固定分区分配
  3. 可变分区分配

1.1单一连续分配

单一连续分配是最简单的管理方式:

  • 只能用于单用户、单任务的操作系统
  • 将可分配内存全部分配给用户进程
  • 逻辑地址=物理地址
  • 如果没有保护机制,用户进程可以修改OS内存,如MS-DOS

举个例子:

  • 既然其适用于单用户、单任务的操作系统,那么同一时刻只能有一个进程在运行。
  • 假设有两个任务:A、B,。A先运行,将所有内存都给A(比如1M-10M),需等A运行完或强行终止才能运行B。运行B时,将内存都给B(比如1M-9M)。
  • 此即单一连续分配的基本原理。

优点:

  • 简单
  • 基本不需要硬件支持
    缺点:
  • 单任务。我们使用的Windows都是分时多任务(写博客的时候也能听歌的)

1.2固定分区分配

将内存划分成固定大小的区域,每个区域装一个程序,举例如下:

  • 第一个分区12KB大小
  • 第二个分区32KB大小
  • 第三个分区64KB大小
  • 第四个分区128KB大小

优点:

  • 简单、支持多道程序(上例中可以支持4道程序)
  • 开始支持内存保护
  • 是一种很好的思路,后面的分页思想也是借鉴了这种思路

缺点:

  • 分区大小不好定,因为我们不知道要运行的程序有多大

1.3可变分区分配

是上述方案的改进版本,分区大小是可变的,这种方案最致命的缺点是容易形成小碎片,至于碎片如何形成,下面举例讲解。

这里的可变分区看似很灵活,比如某进程需要10M内存,那么就分配给它10M内存,这种灵活的背后隐藏着问题:

如何分配内存以及小碎片如何形成:

  • 开始时内存是完整一块,可以理解成一个hole
  • 某进程请求内存->从hole中选取一段空间分配给它
  • 某进程执行完毕,释放hole

    如上图所示:
  • 开始三个进程将内存占满了
  • 然后process8执行完毕,将内存空间释放
  • process9进入,从空内存中分出一段给它
  • 随后process10进入,同理,分一段给它
  • 最后剩余一小段空间,假设这小段空间很小很小,以至于不够任何进程使用,那么这段小碎片就浪费掉了
  • 同理:按照上述思路无数次分配释放之后会形成很多很多小碎片,这些小碎片太小了,用不起来

所以我们需要引入额外的机制来解决上述的小碎片问题:

首先我们思考能不能改进空闲内存的分配方案,从而减缓小碎片问题,思考如下几种分配方案:

  • 首次适应:上述分配方案即首次适应,含义是:每次分配的时候从头开始寻找合适的空间分配给进程
  • 循环首次适应:比如第一次分配的是0-1M的空间,那么下次分配的时候从1M开始向后找,上述两种方案没有太大的本质区别
  • 最佳适应:遍历整个内存空间,寻找大小最接近的空间分配。比如内存空间中1-3M, 5-6M,10-15M都是空闲的,现在某进程需要1M的内存空间,那么将5-6M给它,这种方案有时能避免碎片,但有时不行,比如某进程需要0.9M的空间,那么给他1M,另外0.1M就成碎片啦
  • 最坏适应:与上述方案相反,还是使用上面的数据,某进程需要1M的空间时,将10-15M的空间给它,即给它大小最不合适的。这种分配方案可以避免此类碎片的生成:需要0.9M,不给它1M,因为如果给它1M,会形成0.1M碎片,所以从10-15M的空间中给它

看完了上述分配方案,其实不难发现,别管使用哪种方案,都无法避免碎片的产生,所以继续研究新的机制。

既然碎片无法避免,那就想办法将碎片集中起来:

  • 碎片的紧凑
  • 即运行一段时间后会产生碎片,产生碎片后我做一个紧凑操作,将碎片集中起来。

由于目前操作系统并不采用可变分区管理方式,所我们不深究上述机制,不过可以简单想想:紧凑操作不是那么容易,需要移动其他进程的内存空间,可能会带来新的问题。

连续分配存储方案总结

优点:

  • 十分简单
  • 只需要很简单的硬件支持

缺点:

  • 碎片问题难以解决
  • 紧凑操作可能带来新问题

连续分配在嵌入式设备中有用武之地,现代操作系统没有采用连续分配方案。

2.分页存储管理

上面我们讲了连续分配方案,也阐明了连续分配方案的缺点,下面我们针对连续分配的缺点继续思考:

  • 连续分配最大的缺点在于碎片问题
  • 我们使用了分区的思想来缓解了碎片问题
  • 我们把思路再回到固定分区管理方案:该方案是给每个进程分配一个分区,如果某个小分区比较小,而我要运行的程序恰恰比较大,那这个小分区就类似于碎片了
  • 给每个进程分配一个分区,粒度比较大,因为如果程序比较大,整个分区就浪费掉了
  • 那么能不能把粒度做小! 比如:我将内存划分成一小块、一小块,给每个程序分配多个小块,这样粒度变小了,浪费的内存空间也变小了
  • 但是即使将粒度做小,也无法避免碎片化问题,再想想碎片产生的根本原因是什么?根本原因在于给每个进程分配的空间都是连续的
  • 比如现在有0-1000小块内存,出现这样的场景:A进程得到了1-9块内存,B进程得到了10-15块内存,A执行结束,释放了1-9块,C进程进来得到了1-8块内存,那么离散的第9块内存就被置空了
  • 上述场景就是碎片产生的原因,别管用什么空闲内存分配算法都无法避免上述碎片的产生
  • 那我们的思路就来了,能不能将离散地分配在各个角落的小块碎片利用起来,或者说直接将小块的分配完全离散化,不就能避免碎片化问题了嘛!
  • 至此:分页的思想产生,首先是分成块,上述的块就是页的概念,然后离散管理各个页,此即离散管理的思想。

我们一步一步地思考出了如何克服碎片化问题,重要的思想:将内存划分成一小块、一小块,然后离散管理。

上述思想很重要,以后还会用到,即离散化克服碎片问题(因为碎片就是离散的,干脆完全离散分配,那么碎片问题就解决了)

既然要将内存的分配离散化,那么我们先来直观的看下离散化后的场景:

本来代码是连续分布在内存中的,现在离散化了,每段代码都离散的分布在各个角落。

问题来了:代码的分布都这么散乱,怎么管理呢?

讲解上述问题之前先补充下几个概念:

概念补充

  • 页面:逻辑地址空间划分为多个页面,如上图中的一页一页,称为页面
  • 页框:物理地址空间划分成多个页框,或物理块,是在物理内存中的表述。
  • 我们写程序的时候多用页面,因为操作系统的内存管理将物理地址屏蔽掉了,我们操作的都是逻辑地址或虚拟地址
  • 页面和页框大小是一致的
  • 进程请求页面数不应该超过系统剩余页框数
  • 页面大小:常用4KB,当然如果塞不满4KB的话就浪费掉了(虽然克服了连续分配的碎片化问题,但是分页机制也是有浪费存在的)

先思考一个简单的问题:

  • 分页就分页,为什么要引出页面和页框的概念?
  • 首先页面是逻辑地址的表述,页框是物理地址的表述!
  • 那么问题又来了,为什么要分出逻辑地址和物理地址?只用一个物理地址不行吗?
  • 这里我们暂且不深究,原始是因为历史上出现过分段的概念,还有虚拟内存的原因,这里就假设笔者有一定的基础,了解过分段机制,或者看下面分段章节的讲解。

还会引出一个问题:页面是逻辑地址的表述,页框是物理地址的表述,那么两者如何转换呢?两者的对应关系是什么?

如何管理离散页?

现在我们来回答上面提到的问题:

代码的分布那么散乱,怎么管理呢?

其实思想也很简单:图书馆那么多书,也是离散的分布在各个楼层的各个角落,怎么管理的?

  • 当然是用数据结构存起来
  • 数据结构那么多,用什么数据结构?
  • 从简单开始看:数组和链表鸭

再来确定下我们需要的数据结构:

  • 我们上面提到的管理本质是什么?
  • 是页面和页框的对应关系!
  • 我们所说的离散化其实是离散的分配和使用物理页即页框
  • 代码的执行还是要顺序执行的,物理内存都离散了怎么保证顺序执行?
  • 让逻辑地址对应的页面是连续的就OK了鸭

向上滚动一下,再看下上面的图:

  • 假设左边的(01)、(23)、(4)分别对应一个页,右边就是将代码离散的装入物理内存后的情景
  • 我们假设将(01)装入了5号页框,(23)装入了3号页框、(4)装入了6号页框,这样就体现了离散装入物理内存了!
  • 我们对左边重新编号,(01)对应逻辑页面号0, (23)对应1, (4)对应2:
  • 上图的中间部分就是我们的数据结构需要做的事情,管理页面号和页框号之间的映射关系

设计出了这种方案,那么代码怎么执行呢?

  • 代码按照逻辑地址执行
  • 首先执行到0号页面,查表找到对应页框号是5
  • 还有页内偏移的概念,假设0号页面中的call指令的页内偏移是1,那么在5号页框的页内偏移也是1,即能够顺利找到某个页表对应的指令
  • 思想就是查表+偏移

这样我们的数据结构就设计出来了,而且已经验证过,程序可以运行,那么我们思考:数据结构就这么简单吗?还需要其他信息吗?

  • 比如本页是保存的代码,那么代码应该是只读的,怎么标识?
  • 当然还应该有其他信息,比如特权级等
  • 我们成上述数据结构为页表
  • 引出此问题的目的想说明:页表不仅仅包含上述两项,还有其他内容,我们为了简化问题,后面页仅仅讨论只有上述两项内容的页表。
  • 这里虽然将问题简化了,但是不要着急,分页的思想讲述完毕以后,我会讲解一段Linux源码来讲解真实的分页场景

实例练习


该程序页数:100000/1000=100页
逻辑地址1002的物理地址:

  • 该逻辑地址对应的逻辑页面号为1
  • 则页框号为2
  • 页内偏移为2,那么物理地址为2*1000+2=2002

逻辑地址4010的算法类似。

我们将上述的计算过程一般化:

  • 假设一页有S字节,程序大小为L字节,该程序有多少页?

    • L/S向上取整
  • 页表等价于一个映射函数m(lp),lp指逻辑页面号,m(lp)返回逻辑页对应的物理页框号pb
  • 如何根据逻辑地址la算物理地址ba?
    • 逻辑页号lp=la/S
    • 页内偏移=la%S
    • 物理块号pb=m(lp) 是一个查表的过程
    • pa=m(la/S)*S+la%S

上面提到了逻辑地址,那么逻辑地址长啥样?

  • 首先我们来算一下:一页占4K=212
  • 那么我们需要用12位才能表示页内偏移
  • 假设32位机器,那么逻辑地址32位,其中12位用来表示页内偏移
  • 那么剩余的20位用来表示页号:

    逻辑地址和物理地址的转换可以用下图表示:
  • 页表寄存器记录了页表初始地址
  • 页表初始地址+页号 就能找到对应的页框号
  • 对应的页框号+偏移就是物理地址

分析上述方案的优缺点

问题1:

我们结合上图来分析:取一个逻辑地址里的操作数的过程:

  • 首先从页表寄存器里拿到页表基地址
  • 基地址+页号*页表项 大小得到我们需要的页表项地址
  • 访问该地址,得到页框号
  • 页框号+页内偏移 得到对应的物理地址
  • 访问该物理地址得到操作数

即上述方案方案了两次内存,会影响效率。

问题2:

我们先做下简单的运算:

  • 规定我们要寻址4GB的内存地址空间
  • 一个页框大小为:4KB=212B
  • 4GB能划分成多少个页呢? 4GB/4KB=220
  • 那么每个页表项的大小呢(就是我们之前讨论的数据结构)?
  • 上面运算得到共有220个页框,那么页框号需要用20个比特位来表示
  • 那么每个页表项最起码要有20bit(逻辑页面号当作数组下标,只保存页框号即可)
  • 实际上每个页表项占4个字节,即32bit,为何?一是因为需要保存额外的信息,二是因为4字节对齐的考虑。至于为什么要4字节对齐,参考博客
  • 现在我们知道了:一个页表项占4个字节,那么一个页框可以容纳多少个页表项呢?
  • 4KB/4B=1K=210=1024个页表项
  • 这样算下来:4GB有220个页,即我的页表需要有220个页表项
  • 一个页框能装1024个页表项,那么装220个页表项需要210个页框。

上面的计算可能优点乱,需要仔细看下,计算的目的是为了明确:
我们的一个页表装在了1024个页框里如下图所示:

这个页表实在是太大了,其需要1024个页框才能装下,即4M的内存空间。
页表太大会带来什么问题?

  • 这一个问题涉及到虚拟地址空间相关的知识,可以看我的另一篇博客
  • 虚拟地址空间所起到的作用是让每个进程都以为自己运行在0-4G的地址空间里(假设内存是4G)
  • 需要将整个页表都复制给每个进程,这会增加创建进程的开销,而且会浪费内存空间。
  • 上述问题如果放在64位地址的计算机上,按照相同计算方法,一个进程的页表占用的空间会非常非常大。
  • 关键是:一个进程,真的会需要一整个虚拟地址空间去存放吗?

举个例子来说明上述问题:

  • 一个需要12MB的进程,底端是4MB的程序正文,后面是4MB数据,顶端是4MB堆栈,在数据顶端上方和堆栈之间是大量没有使用的空闲区
  • 注:一个页框保存1024个页表项,能寻址4MB内存
  • 既然我们只用到了3个页框的页表,那么能不能只保存相关这三个表的信息,如下图:

3.多级页表

多级页表能减少每个进程需要保存的信息

如上图所示:每个进程中只需要保存一个4KB大小的一级页表,用来记录1024个二级页表中哪个用到,哪个没有用即可,要比保存4MB大小的所有二级页表划算的多。

使用了二级页表的管理机制以后,32位逻辑地址应该如何划分呢?我们需要做下计算:

  • 共有1024个二级页表,那么就需要1024个一级页表项来管理
  • 一个一级页表项占4字节内存,保存二级页表地址。1024*4B=4KB,一个页框刚好能装得下
  • 既然只有一个一级页表,那么逻辑地址中直接保存一级页表的索引号即可,需要用10个bit。
  • 查询一级页表后定位到二级页表,然后需要10bit的偏移,用来找二级页表项。
  • 一级页表又称外表也称页目录,二级页表又称内表。
  • PDE称为页目录项,PTE称为页表项
  • 所以32逻辑地址划分成:

二级页表如何将逻辑地址转换成物理地址?

先介绍一个寄存器CR3:

地址转换方式:

Linux0.11版页初始化源码解析+虚拟存储管理+分段存储管理放在下一篇博客,这篇太长了。

如果觉得写的不错,对读者有帮助,可以给笔者点个赞,鼓励一下哦~

本系列博客目录
下一篇:内存管理续

操作系统-课堂笔记-内存管理(南航)相关推荐

  1. 操作系统-课堂笔记-文件系统(南航)

    文章目录 文件系统 引言 文件系统需要做什么工作? 1.关于文件名的限制: 2.文件扩展名有哪些 3.文件有哪些类型? 4.文件是怎么保存的以及文件的属性和操作 文件的实现 如何为文件分配磁盘空间? ...

  2. 《现代操作系统》笔记-内存管理3

    接上 内存管理 页置换算法 1. 随机挑选一个置换 缺点:有可能置换出频繁使用的页 2. 最佳页置换算法 缺陷: 前提是必须要知道接下来哪些页面要使用,完全理想的情况,不可能实现.但可作为衡量算法优劣 ...

  3. Linux内核笔记--内存管理之用户态进程内存分配

    内核版本:linux-2.6.11 Linux在加载一个可执行程序的时候做了种种复杂的工作,内存分配是其中非常重要的一环,作为一个linux程序员必然会想要知道这个过程到底是怎么样的,内核源码会告诉你 ...

  4. 利用图文和代码深度解析操作系统OS的内存管理实现原理机制和算法

    利用图文和代码深度解析操作系统OS的内存管理实现原理机制和算法. 内存作为计算机系统的组成部分,跟开发人员的日常开发活动有着密切的联系,我们平时遇到的Segment Fault.OutOfMemory ...

  5. 从零手写操作系统之RVOS内存管理模块简单实现-02

    从零手写操作系统之RVOS内存管理模块简单实现-02 内存管理分类 内存映射表(Memory Map) Linker Script 链接脚本 语法 基于符号定义获取程序运行时内存分布 基于 Page ...

  6. 《现代操作系统》第3章读书笔记--内存管理(未完成)

    写在前面:本文仅供个人学习使用,如有侵权,请联系删除.文章中所用图片绝大多数来源于<现代操作系统(第4版)>,请读者支持原版. 内存(RAM) 是计算机中一种需要认真管理的重要资源.一个事 ...

  7. 操作系统-课堂笔记-进程概述(南航)

    文章目录 进程概述 1.引言 2.进程的概念 2.1进程的内存抽象 2.2分段保护 2.2.1例一 2.2.2例二 2.2.3小结 2.3进程的状态 版本1 版本2 提升思考(可跳过,涉及虚拟内存) ...

  8. 【操作系统】考研の内存管理方法(看不懂你来打我~!)

    文章目录 1 内存管理概述 1.1 存储层次结构 1.2 指令数据绑定到内存地址 1.3 逻辑地址 2 连续内存管理 2.1 单独分区分配 2.2 固定分区分配 2.3 动态分区分配 2.4 可重定位 ...

  9. 操作系统原理之内存管理(第四章第二部分)

    一.基本分页存储管理方式 1.分⻚存储管理的基本原理: 页:将⼀个进程的逻辑地址空间分成若⼲个⼤⼩相等的⽚ 页框:将物理内存空间分成与⻚⼤⼩相同的若⼲个存储块 分⻚存储:将进程中的若⼲⻚分别装⼊多个可 ...

最新文章

  1. [ant]通过Android命令自动编译出build.xml文件
  2. 【对讲机的那点事】节日出游对讲机选择你了解多少?
  3. AS插件-Android Drawable Importer
  4. jQuery-给ul添加了li之后,添加的li并没有绑定点击监听怎么办?
  5. 2018-2019-1 20189210 《LInux内核原理与分析》第六周作业
  6. 使用Nginx过滤网络爬虫
  7. 【转】深入理解Windows消息机制
  8. 设置密码命名是什么linux,orapwd 工具建立密码文件遵守的命名方法
  9. 信息学奥赛一本通C++语言——1029:计算浮点数相除的余
  10. PostgresException: 42883: function ifnull(integer, integer) does not exist
  11. ASP.net的PDF打印(水晶报表)[摘]
  12. how to switch between python3.5 and python3.6
  13. mac的终端通过ssh远程连接Linux服务器
  14. POJ1961 Period
  15. Arduino UNO驱动DS1307数字实时时钟RTC
  16. Introduction to Robotics 总结1~6
  17. 1194_SICP学习笔记_霍夫曼编码树
  18. PAT A 1034
  19. stream_kws_cnn
  20. asp.net房屋出租销售网

热门文章

  1. 基于SIFT特征的图像配准(附Matlab源代码)
  2. 【并查集,Bfs】汽车拉力比赛
  3. Axure RP小技巧:如何利用矩形制作各种形状
  4. Ubuntu安装openssh遇到依赖问题
  5. Chrome 远程桌面
  6. Expandable Input TextView
  7. 龙族幻想学院计算机中心在哪,龙族幻想社团执事在哪里 社团执事坐标位置介绍...
  8. mysql数据复制改一个字段_mysql表复制和修改部分字段
  9. 平板电脑性价比排行,新发布的荣耀平板V6强不强?
  10. 跨国企业在中国 | 7000多台霍尼韦尔设备助力广州地铁;玩具反斗城中国门店破200家...