内存管理

内存管理 #

一、虚拟内存思想 #

我们可以把进程所使用的地址「隔离」开来,即让操作系统为每个进程分配独立的一套虚拟地址,人人都有,大家自己玩自己的地址就行,互不干涉。虚拟地址最终怎么落到物理内存里,对进程来说是透明的,操作系统已经把这些都安排的明明白白了(MMU)。

二、内存分段 #

1、机制 #

虚拟地址和物理地址之间通过段表(段基地址+段大小)来映射。

分段机制会把程序的虚拟地址分成 4 个段:代码分段、数据分段、堆段、栈段。

1

2、不足 #

内存碎片问题(不连续的小物理内存,浪费) -> 并因此有内存交换的效率低的问题。

三、内存分页 #

1、分页机制 #

分页是把整个虚拟和物理内存空间切分成一页一页,固定尺寸,Linux下一页是4KB。

虚拟地址与物理地址之间通过页表来映射。

2、多级页表 #

页表一定要覆盖全部虚拟地址空间,不分级的页表就需要有 100 多万个页表项来映射,而二级分页则只需要 1024 个页表项。(此时一级页表覆盖到了全部虚拟地址空间,二级页表在需要时创建)。

2

3、缺页异常 #

而当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入系统内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。

  • 最近最久未使用 LRU
  • 先进先出
  • 第二次机会算法

4、快表 #

局部性原理。

  1. 根据逻辑地址,得到页号+页内偏移,去快表里面,看是否命中。
  2. 如果命中,取出对应的内存块号,和页内偏移拼接得到物理地址。即:只需一次访存。
  3. 如果没命中,访问内存中的页表,找到对应表项,最后得到物理地址,访问内存。即:两次访存。同时,还要存入快表,如果满了,就进行替换。

5、好处和优点 #

  • 解决了内存碎片问题
  • 交换效率也更高
  • 不需要一次性全部加载到物理内存

四、段页式内存管理 #

  • 先将程序划分为多个有逻辑意义的段,也就是前面提到的分段机制;

  • 接着再把每个段划分为多个页。

这样,地址结构就由段号、段内页号和页内位移三部分组成。

段页式地址变换中要得到物理地址须经过三次内存访问:

  • 第一次访问段表,得到页表起始地址;

  • 第二次访问页表,得到物理页号;

  • 第三次将物理页号与页内位移组合,得到物理地址。

五、Linux 内存管理 #

内核空间 和 用户空间两部分:

4

虽然每个进程都各自有独立的虚拟内存,但是每个虚拟内存中的内核地址,其实关联的都是相同的物理内存。这样,进程切换到内核态后,就可以很方便地访问内核空间内存。且所有段的起始地址都一样。(共享和保护)

通过这张图你可以看到,用户空间内存,从低到高分别是 7 种不同的内存段:

  • 程序文件段,包括二进制可执行代码;

  • 已初始化数据段,包括静态常量;

  • 未初始化数据段,包括未初始化的静态变量;

  • 堆段,包括动态分配的内存,从低地址开始向上增长;

  • 文件映射段,包括动态库、共享内存等,从低地址开始向上增长(跟硬件和内核版本有关)

  • 栈段,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。当然系统也提供了参数,以便我们自定义大小;

在这 7 个内存段中,堆和文件映射段的内存是动态分配的。比如说,使用 C 标准库的 malloc() 或者 mmap() ,就可以分别在堆和文件映射段动态分配内存。

5

  • 比较
    • 对程序员的透明姓
    • 地址空间的维度
    • 大小是否可改变
    • 作用:虚拟内存更大空间;逻辑独立共享保护