关于系统内存管理

内存管理

1.问题

很长时间我对与内存管理的理解都非常模棱两可,利用空余时间,决定搞清楚这部分内容,本文结解决的问题如下:
1.什么是虚拟内存,为什么要使用虚拟内存
2.简单的解决方案:内存分段
3.进一步的优化:内存分页
4.进一步的优化:多级分页
5.TLB
6.段页式内存管理方式

2.虚拟内存

  • 假设有两个程序A,B。它们都需要被加载到内存中,在使用如果它们的内存有任何交集,那么两个程序都将会崩溃掉。
**实际上我们希望进程A,B所用的空间完全隔离,那么我们就需要用虚拟内存来代替原本的绝对地址**,这样进程地址互相隔离,互不干涉。但是有个前提每个进程都不能直接访问物理内存,至于虚拟内存是如何落到物理内存中区的,对于进程来说这是透明的额,操作系统已经将该问处理好了。此时的系统架构应为:

关于系统内存管理

操作系统引入虚拟内存,进程持有的虚拟地址会通过CPU芯片中的内存管理单元(MMU)的映射关系,来转换变成物理地址,然后通过物理地址访问内存,如下图所示:

关于系统内存管理

操作系统是如何管理虚拟地址与物理地址之间的关系? 主要有两种方式:1.内存分段 2.内存分页

3.内存分段

  • 程序是由若干个逻辑分段组成的,如何由代码分段,数据分段,栈段,堆段组成,不同的段是有不同的属性,所以就用分段的形式将其分离开来

  • 问题:分段机制下的虚拟地址是如何映射的?
    在分段机制下的虚拟地址由两部分组成,段选择子段内偏移量
    关于系统内存管理

  • 段选择子就保存在段寄存器立面。段寄存器立面最重要的是段号,用作段表的索引。段表里保存的是这个段的基地址,断的界限和特权等级等。

    虚拟地址中的段内偏移量应该位于0和段界限之间(也就是说段内最大空间就是:段基址+段界限)。如果段内偏移量是合法的,就将段基地址加上段内偏移量获得物理内存地址。


    给出一个映射的案例:
    关于系统内存管理

分段很好的解决了程序自身不需要考虑关心具体内存地址的问题,但他本身也存在一些缺陷: 1.第一个就是内存碎片的问题 2.第二个是内存交换效率低的问题(为什么要内存交换,会在后面讲到)
碎片为什么会产生,可以看接下来这个例子: * 假设在一个1G物理内存中同时运行了多个程序:
运行程序 占用内存
游戏 512MB
浏览器 128MB
音乐 256MB

这个时候,如果我们关闭了浏览器,则空闲的内存还有 1024-512-256=256MB。
但是如果这个256MB的内存并不连续,例如被分为了两端128MB的内存,这就导致没有足够的内存去再打开一个200MB大小的程序。

关于系统内存管理


这里会引出两个问题:
1.外部内存碎片,也就是产生了多个不连续的小物理内存,导致新的程序无法被引进
2.内部内存碎片,程序所有的内存都被装到了物理内存,但是这个程序有部分内存可能并不是很长使用,导致了内存的浪费


针对上面这种问题,解决的方式也有不同,解决外部内存碎片的方法就是**内存交换** 可以把音乐程序占用的那256MB内存写到硬盘上,然后再从硬盘上读回到内存中。不过再堵回来的时候,我们不会再将它放回到原来的位置,而是紧紧跟随在已被占用的512MB内存的后面,这样就可以空出256MB的空间,于是200MB的程序就可以装载进来了。

这个内存交换空间,在Linux系统中就是Swap空间,可以用free -m查询到:

关于系统内存管理



为什么分段会导致内存交换效率低?
对于多进程的系统来说,用分段的方式,内存随便是很容易产生的,产生了内存碎片,那就不得不重新交换内存区域,这个过程很容易产生性能瓶颈。因为硬盘的访问速度要比内存慢太多了,每一次内存交换,我们都需要把一大段连续的内存数据写到硬盘上。如果内存交换的时候,交换的是一个占用内存很大的程序,那么这个机器都会显得卡顿。 为了解决这个问题,就出现了内存分页

4.内存分页

分页的好处就是能产生连续的内存空间,但是会出现内存碎片和内存交换的空间太大的问题。

为了解决这些问题,那么就要想出少出现内存碎片的方法。另外,当需要进行内存交换的时候,让需要交换写入或者从磁盘装载的数据尽可能的少一点,这样问题就解决了。实现上述问题的方法就是内存分页

分页就是把整个虚拟和物理内存切分成一段段固定尺寸的大小。这样一个连续并且尺寸固定的内存空间,就被称为页(page)。在Linux系统下,每页的大小为4KB。此时,虚拟地址与物理地址之间通过页表来映射。
关于系统内存管理

页表实际上存储在CPU的内存管理单元(MMU)中,于是CPU就可以直接通过MMU,找出要实际访问的物理地址。如果进程访问的虚拟地址在页表中查询不到,那么系统就会产生一个缺页异常,进行系统内核空间分配物理内存,更新进程页表,最后再返回用户空间,恢复进程运行。 分页如何解决分段的内存碎片和交换效率低的问题?
由于内存空间都是预先划分好的,也就不会想分段产生间隙非常小的内存,这也正是分段会产生碎片的原因,采用了分页后,释放内存都是以页为单位释放,也就不会产生无法给进程使用的小内存。如果内存空间不够,操作系统会把其他正在运行程序中[最近没有使用]的内存页释放掉,暂时写入到硬盘上,成为换出。到需要的时候再载入进来,成为换入。所以,一次性写入磁盘的只有少数的一个页或者几个页,不会花套多时间,内存交换的效率特酒相对较高。

关于系统内存管理


更进一步地,分页的方式使得我们在加载程序的时候,不再需要一次性都把程序加载到物理内存中。我们完全可以在进行虚拟内存和物理内存的页之间的映射之后,并不真的把页加载到物理内存里,而是**只有在程序运行中,需要用到对应虚拟内存页里面的指令和数据时,再加载到物理内存里面去。**这也就是为啥4G内存的机器可以运行8G的程序的原因了。 分页机制下,虚拟地址和物理地址是如何映射的? 在分页机制下,虚拟地址分为两部分,页号和页内偏移。页号作为页表的索引,页表包含物理页每页所在物理内存的基地址,这个基地址与页内偏移的组合就形成了物理内存地址,见下图。

关于系统内存管理

总结一下,对于一个内存地址转换,其实存在三个步骤: 1.把虚拟内存地址,切分成页号和偏移量; 2.根据页号,从页表里,查询对应的物理页号 3.直接拿物理页号加偏移量便获得了物理内存地址。

关于系统内存管理



这种方法在空间上依然存在缺陷: 因为操作系统是可以同时运行非常多的进程的,那这不就意味着页表会非常的庞大。
在 32 位的环境下,虚拟地址空间共有 4GB,假设一个页的大小是 4KB(2^12),那么就需要大约 100 万 (2^20) 个页,每个「页表项」需要 4 个字节大小来存储,那么整个 4GB 空间的映射就需要有 4MB 的内存来存储页表。

这 4MB 大小的页表,看起来也不是很大。但是要知道每个进程都是有自己的虚拟地址空间的,也就说都有自己的页表。 那么,100 个进程的话,就需要 400MB 的内存来存储页表,这是非常大的内存了,更别说 64 位的环境了。

5.多级页表


要解决上面的问题,就需要采用的是一种叫作多级页表(Multi-Level Page Table)的解决方案。
在前面我们知道了,对于单页表的实现方式,在 32 位和页大小 4KB 的环境下,一个进程的页表需要装下 100 多万个「页表项」,并且每个页表项是占用 4 字节大小的,于是相当于每个页表需占用 4MB 大小的空间。
我们把这个 100 多万个「页表项」的单级页表再分页,将页表(一级页表)分为 1024 个页表(二级页表),每个表(二级页表)中包含 1024 个「页表项」,形成二级分页。如下图所示:

关于系统内存管理


此时对于1级页表,需要4KB(总大小为4GB,要映射整个4GB空间:4G/1024/1024=4KB)二级页表需要4MB看起来其实需要的空间更大了,但是 对于大部分程序而言,其使用到的内存远达不到4GB,因为会存在部分对应的页表项都是空的,根本没有分配,对于已分配的页表项,如果存在最近一定时间未访问的页表,在物理内存紧张的情况下,操作系统会将页面换出到硬盘,也就是说不会占用物理内存。

如果使用了二级分页,一级页表就可以覆盖整个 4GB 虚拟地址空间,但如果某个一级页表的页表项没有被用到,也就不需要创建这个页表项对应的二级页表了,即可以在需要时才创建二级页表。做个简单的计算,假设只有 20% 的一级页表项被用到了,那么页表占用的内存空间就只有 4KB(一级页表) + 20% * 4MB(二级页表)= 0.804MB,这对比单级页表的 4MB 是不是一个巨大的节约?

对于64位的系统,两级分页就不够用了,就变成了了四级目录,分别是:
全局页目录项 PGD(Page Global Directory);
上层页目录项 PUD(Page Upper Directory);
中间页目录项 PMD(Page Middle Directory);
页表项 PTE(Page Table Entry);

5.TLB

  • 多级页表虽然解决了空间上的问题,但是虚拟地址到物理地址的转换就多了几道转换工序,这显然就降低了地址转换的速度,增加了时间上的开销。


    程序具有局部性,即在一段时间内,整个程序的执行仅限于程序中的某一部分。相应的,执行所访问的存储空间也局限于某个内存区域。

关于系统内存管理

我们就可以利用这一特性,把最常访问的几个页表项存储到访问速度更快的硬件,于是计算机科学家们,就在 CPU 芯片中,加入了一个专门存放程序最常访问的页表项的 Cache,这个 Cache 就是 TLB(Translation Lookaside Buffer) ,通常称为页表缓存、转址旁路缓存、快表等。

关于系统内存管理

在 CPU 芯片里面,封装了内存管理单元(Memory Management Unit)芯片,它用来完成地址转换和 TLB 的访问与交互。 有了 TLB 后,那么 CPU 在寻址时,会先查 TLB,如果没找到,才会继续查常规的页表。 TLB 的命中率其实是很高的,因为程序最常访问的页就那么几个。

6.段页式内存管理

内存分段和内存分页并不是对立的,它们是可以组合起来在同一个系统中使用的,那么组合起来后,通常称为段页式内存管理

关于系统内存管理


段页式内存管理实现的方式 : 1.先将程序划分为多个有逻辑意义的段,也就是前面提到的分段机制; 2.接着再把每个段划分为多个页,也就是对分段划分出来的连续空间,再划分固定大小的页; 这样,地址结构就由 **段号、段内页号和页内位移**三部分组成。 用于段页式地址变换的数据结构是每一个程序一张段表,每个段又建立一张页表,段表中的地址是页表的起始地址,而页表中的地址则为某页的物理页号,如图所示:

关于系统内存管理


段页式地址变换中要得到物理地址须经过三次内存访问: 第一次访问段表,得到页表起始地址; 第二次访问页表,得到物理页号; 第三次将物理页号与页内位移组合,得到物理地址。 可用软、硬件相结合的方法实现段页式地址变换,这样虽然增加了硬件成本和系统开销,但提高了内存的利用率。