现在操作系统中内存管理模块是一个很重要的功能,它的好坏会直接影响到整个操作系统和上层的应用程序,我们在编写程序的使用的时候,可能用的比较高级的语言例如Java,就根本不需要关心如何分配程序内存变量的内存用完了,是否要回收这些问题;程序运行起来是要占用物理内存空间的,我们买电脑的时候都会看物理内存多大啊,内存越大说明性能越强这种观点,这是为什么呢?还有有时候我们开了很多软件但是这些软件是怎么在一个8GB内存条上装得下呢?这些问题都是操作系统内存管理要解决的问题?

内存管理

内存管理要解决的问题,第一让多个程序能够隔离起来,在程序的角度来讲相当于独占整个内存使用,用户无感知的;并且不能让程序之间访问其他程序的内存,提高多个程序对内存使用率。

解决办法对应了计算机科学中一句老话:计算机科学中遇到的大部分问题都可以加一层来解决。 操作系统内存管理也是这样引入了虚拟内存的概念。物理内存看起来就像一个超大的数组,而虚拟内存起到作用就是把这些连续的物理内存分成一页一页的内存页,这就有点像一本书一样。

通常普通做法是:计算机的CPU可以通过总线去访问物理内存上的一个地址,然后读取这个地址上的数据;虚拟内存就是一个书的目录一样把一整块书分成不同内存页面,目录的索引就是对应的书的页面映射;将虚拟地址转成物理地址这个操作就叫地址翻译,这个操作由操作系统的MMU所控制,如图:

逻辑内存

什么是逻辑内存?把物理内存通过程序进行虚拟化出来的内存映射,这就称之为逻辑内存虚拟内存。内存对于程序来说非常重要,当然大部分现在如果你不搞操作系统或者一些特定领域的开发一般很少了解。内存对于计算机来说非常宝贵的东西,现在的程序员只会在这些基础之上进行开发东西,内存管理是交给操作系统进行管理的。

另外一个好处就是内存保护,有了逻辑内存就可以把不同的程序内存隔离起来,每个程序只能访问自己内存而不是其他区域的内存;内存分割有一个功能模块叫:重定位寄存器和界地址寄存器来协调工作,重定位寄存器记录的是虚拟地址的位置在物理内存的偏移量,界地址寄存器本程序内存数据所在的内存界限地址,如果访问超过这个地址那么就会发生越界异常

当同时执行多个程序时,操作系统会为每个程序分配一个连续的RAM分区,以便同时处理多个程序。现在让我们假设两个程序完成了它们的执行。如果这两个程序释放的空间不连续,并且不足以让其他程序运行,那么RAM就会在不同的地方出现漏洞,这会导致内存碎片:

而这些碎片不满足新的程序所需要的运行内存占用空间,因为这些空间不是连续的内存,就不能满足比自己内存占用体积更大的程序所使用,这就是内存为什么要引入一个虚拟内存来解决。

为了解决这样的问题,操作系统早期的设计者就提出了虚拟内存,但是虚拟内存实现方式也是经历过很多变化的,最早的分段管理也就是上面我锁说的一块大内存分成不同大小的段,这些段是固定的,后面有出现了一种比段更小划分机制就是分页。

这里我只介绍了个人整理一下分配规则并没有详细介绍,如果需要补习这块内容可以查看:操作系统内存分配问题

分页管理

分段允许进程的物理地址空间是非连续的,分页是提供这种优势的另一种内存管理方案,然而,分页避免了外部碎片和紧缩,而分段不可以;不仅如此,分页还避免了将不同大小的内存块匹配到交换空间的问题,在分页引入之前采用的内存管理方案都有这个问题。由于比早期方法更加优越,各种形式的分页为大多数操作系统采用,包括大型机的和智能手机的操作系统,如下图多个进程的内存布局:

实现分页的基本方法涉及将物理内存分为固定大小的块,称为帧或页帧,而将逻辑内存也分为同样大小的块,称为页或页面。当需要执行一个进程时,它的页从文件系统或备份存储等处,加载到内存的可用帧,备份存储划分为固定大小的块,它与单个内存帧或与多个内存帧的大小一样。

分页的好处也就是可以利用硬盘来做交换区辅助内存数据临时存储,当有时候我们内存不够用的时候,就可以从硬盘来获取一段空间来弥补内存的容量不足,这样就可以实现可以运行比内存大占用程序,也可以做到多个程序同时运行时内存空间占用不足的问题。

换页算法

换页算法是什么?为什么要有换页算法?上面介绍如果内存不够用,可以用硬盘的内存来充当,这里的内存到硬盘的内存数据交换算法就是换页算法;换页算法的方式有很多种,例如经常使用的算法LRU,这个在计算机缓存算法里面也用的比较多;LRU最近最少使用的算法,用到换页算法就是把不需要经常触发换页的程序,内存换到硬盘中,然后把内存的空出来给其他程序使用。

实际虚拟内存的内存布局是是真实内存的偏移量,如上图就是内存偏移量,对应内存来说地址看上去没有什么区别,但是经过映射程序转义之后是在每个页面段从添加内存偏移量完成的。当触发内存交换的时候,操作系统会把当前程序的进程信息保存在操作系统的PCB队列中,然后将程序内存换出,当程序内存恢复过来的时候就从PCB中读取当前程序的状态。

换页的过程中可能会发生缺页异常,缺页异常的原因就是,虚拟页是有分布的但是实际物理页上的数据没有分配,而是存储在硬盘中;当某个程序访问到未映射的虚拟页时就会触发缺页异常,操作系统会把存在外存的数据换页到物理内存中。平凡触发缺页异常是一种很浪费资源的操作所以有预取机制:程序会通过算法把可能需要使用的连续内存数据读取提前加载换入到物理内存中。

1. MIN或者OPT策略:这种策略就是会优先选择未来不会再访问的页,或者在最长时间内不会被访问的页面进行换出;但是这种策略很难适应用户随机打开某个应用,又重新打开另外一个应用的,因为用户打开软件是随机性的很难做大怎么换出那些页面的预测。

2. FIFO策略:先进先出策略,这个应用很简单当你的使用软件的时候,打开多个软件,每打开一个软件都是有前后顺序的,这就对应这先进先出的策略,后面打开是最近使用的软件,那么内存肯定是被占用的,先打开软件内存数据可以被交换到硬盘中保存;同样和OPT策略存在一样的问题,程序是顺序取决于打开程序的顺序,而打开程序是人为随机的,也很难预测到那些内存是常用的。

3. LRU策略:和常用的缓存淘汰算法策略一样,会优先选择最近没有被使用的程序的内存,然后交换出去到硬盘上,维护一个固定大小的链表,最近访问的页面被插入到链表尾部,未访问的在头部,每次插入的时候如果发现满了就将其换出到硬盘中,每次访问的时候都要调整链表的中的页面位置顺序;

4. CLOOK策略: 是一种时钟算法,将程序内存页以每次打开程序的顺序用链表围成一个环,并且每个环上有一个标志位,这个标志位方便时钟指针在摆动的时候进行标记使用的,环是有限的,如果为止不够先把时钟转一圈并且设置所有标志位为1,然后拿去停止位上第一个然后换出,如果下次还有将继续摆动,如果已经在换内内存页标志位设置为1,直到标志位上为0就重置。

换页算法只是为了优化程序调度的策略,但是换页速度还是取决于磁盘的,CPU调度绝大数部分在处理换页上,但是换页内存和磁盘的速度是哟哟差距的,并且还要等待磁盘IO,这样会很大程度上影响整个程序的运行速度,可能会出现程序卡顿的情况,出现卡顿情况就是触发操作系统内存换页机制。

写时复制

上面介绍了内存换页调度算法,都每个程序独占一份内存的,这样如果某些程序的动态链接库和一些内存数据是一样的,并且不发生修改的操作的话,完全就可以把这些程序共享的内存放到一起避免更多的内存占用,标记为只读。当如果需要其中一个程序发生写入操作的时候程序会触发一个写入异常,操作系统此时就可以将此块内存复制一份,再共其他程序使用,这就是写时复制的基本原理。

Copy-on-write: 有时也称为隐式共享将复制操作推迟到第一次写入时进行,在创建一个新副本时,不会立即复制资源,而是共享原始副本的资源,当修改时再执行复制操作。通过这种方式共享资源,可以显著减少创建副本时的开销,以及节省资源,同时,资源修改操作会增加少量开销。

优点很明显能充分利用内存空间,节省内存,在Linux的fork中能加快创建进程的速度;缺点就是如果在子进程存在期间发生了大量写操作,那么会频繁地产生页面错误,不断陷入内核,复制页面,这反而会降低效率。

内存压缩

写时复制技术另外一个应用就是对内存数据去重工作,内存去重是针对操作系统内存的多个程序进程内存数据去重,工作原理就是操作系统会有一个定时器会定时对操作系统上的进程进行扫描,查看内存是否有数据重复的程序进程,如果有进程的内存重复,那么这些程序重复的内存就被合并到一个内存页上,这样就能达到节省内存空间的效果,这个在Linux系统上有应用叫KSM技术。

另外一种就是内存压缩技术,这个在Linux内核中也有应用叫zswap,这是一种内存数据页压缩技术,zswap中提供一个叫作内存页压缩区域,当有需要换出的内存数据页的,操作系统会将这些内存数据页数据压缩到这个zswap空间中,然后批量输入到硬盘中,从而实现更高效的io操作,避免频繁的磁盘io操作;

其他资料

便宜 VPS vultr
最后修改:2023 年 07 月 05 日
如果觉得我的文章对你有用,请随意赞赏 🌹 谢谢 !