Bootstrap

操作系统内存管理

内存管理
1、操作系统为了解决多个线程可以同时操作内存,引入虚拟内存,也就是每个线程都操作自己的虚拟内存,然后通过MMU转换成物理内存。这样每个程序都可以正常运行。
2、MMU:内存管理单元,操作系统会提供一种机制,将不同进程的虚拟地址和不同内存的物理地址映射起来。
在这里插入图片描述
操作系统如何管理虚拟地址和物理地址之间的关系呢?

主要有两种方式,分别是内存分段和内存分页,分段是比较早提出的,我们先来看看内存分段。

内存分段
分段机制下,虚拟地址和物理地址如何映射?
虚拟地址是通过段表与物理地址进行映射的

分段机制下的虚拟地址由两部分组成,段选择因子和段内偏移量。
在这里插入图片描述
● 段选择子就保存在段寄存器里面。段选择子里面最重要的是段号,用作段表的索引。段表里面保存的是这个段的基地址、段的界限和特权等级等。

● 虚拟地址中的段内偏移量应该位于 0 和段界限之间,如果段内偏移量是合法的,段基地址加上段内偏移量就是物理内存地址
分段的办法很好,解决了程序本身不需要关心具体的物理内存地址的问题,但它也有一些不足之处
● 第一个就是内存碎片的问题。
● 第二个就是内存交换的效率低的问题。
我们来看这样一个例子。我现在手头的这台电脑,有 1GB 的内存。我们先启动一个图形渲染程序,占用了 512MB 的内存,接着启动一个 Chrome 浏览器,占用了 128MB 内存,再启动一个 Python 程序,占用了 256MB 内存。这个时候,我们关掉 Chrome,于是空闲内存还有 1024 - 512 - 256 = 256MB。按理来说,我们有足够的空间再去装载一个 200MB 的程序。但是,这 256MB 的内存空间不是连续的,而是被分成了两段 128MB 的内存。因此,实际情况是,我们的程序没办法加载进来。
在这里插入图片描述
当然,这个我们也有办法解决。解决的办法叫内存交换(Memory Swapping)。

我们可以把 Python 程序占用的那 256MB 内存写到硬盘上,然后再从硬盘上读回来到内存里面。不过读回来的时候,我们不再把它加载到原来的位置,而是紧紧跟在那已经被占用了的 512MB 内存后面。这样,我们就有了连续的 256MB 内存空间,就可以去加载一个新的 200MB 的程序。如果你自己安装过 Linux 操作系统,你应该遇到过分配一个 swap 硬盘分区的问题。这块分出来的磁盘空间,其实就是专门给 Linux 操作系统进行内存交换用的。

虚拟内存、分段,再加上内存交换,看起来似乎已经解决了计算机同时装载运行很多个程序的问题。不过,你千万不要大意,这三者的组合仍然会遇到一个性能瓶颈。硬盘的访问速度要比内存慢很多,而每一次内存交换,我们都需要把一大段连续的内存数据写到硬盘上。所以,如果内存交换的时候,交换的是一个很占内存空间的程序,这样整个机器都会显得卡顿

为了解决内存分段的内存碎片和内存交换效率低的问题,就出现了内存分页
内存分页
分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小。这样一个连续并且尺寸固定的内存空间,我们叫页(Page)。在 Linux 下,每一页的大小为 4KB。
虚拟地址与物理地址之间通过页表来映射,如下图:
在这里插入图片描述
页表实际上存储在 CPU 的内存管理单元 (MMU) 中,于是 CPU 就可以直接通过 MMU,找出要实际要访问的物理内存地址
而当进程访问的虚拟地址在页表中查不到时,系统会产生一个
缺页异常*,进入系统内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。*
由于内存空间都是预先划分好的,也就不会像分段会产生间隙非常小的内存,这正是分段会产生内存碎片的原因。而采用了分页,那么释放的内存都是以页为单位释放的**,也就不会产生无法给进程使用的小内存**

如果内存空间不够,操作系统会把其他正在运行的进程中的「最近没被使用」的内存页面给释放掉,也就是暂时写在硬盘上,称为换出(Swap Out)。一旦需要的时候,再加载进来,称为换入(Swap In)。所以,一次性写入磁盘的也只有少数的一个页或者几个页,不会花太多时间,内存交换的效率就相对比较高。
分页机制下,虚拟地址和物理地址是如何映射的?

在分页机制下,虚拟地址分为两部分,页号和页内偏移。页号作为页表的索引,页表包含物理页每页所在物理内存的基地址,这个基地址与页内偏移的组合就形成了物理内存地址,见下图。
在这里插入图片描述
总结一下,对于一个内存地址转换,其实就是这样三个步骤:

● 把虚拟内存地址,切分成页号和偏移量;
● 根据页号,从页表里面,查询对应的物理页号;
● 直接拿物理页号,加上前面的偏移量,就得到了物理内存地址。

下面举个例子,虚拟内存中的页通过页表映射为了物理内存中的页,如下图:
在这里插入图片描述

;