Contents

操作系统-小林coding-内存管理

本系列笔记为作者在跟随小林coding学习的时候做的笔记。感谢小林大大。

内存管理

为什么要有虚拟内存

操作系统为每个进程分配独立的「虚拟地址」。

操作系统会提供一种机制,将不同进程的虚拟地址和不同内存的物理地址映射起来。

  • 程序所使用的内存地址叫做虚拟内存地址(Virtual Memory Address)
  • 实际存在硬件里面的空间地址叫物理内存地址(Physical Memory Address)

映射过程主要有两种方式,分别是内存分段内存分页

内存分段

分段机制下的虚拟地址由两部分组成,段选择因子和段内偏移量

https://cdn.xiaolincoding.com//mysql/other/a9ed979e2ed8414f9828767592aadc21.png

虚拟地址是通过段表与物理地址进行映射的

https://cdn.xiaolincoding.com//mysql/other/c5e2ab63e6ee4c8db575f3c7c9c85962.png

不足:内存碎片,内存交换的效率低(需要将整段写入换出)

内存分页

分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小(页)

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

https://cdn.xiaolincoding.com//mysql/other/08a8e315fedc4a858060db5cb4a654af.png

内存管理单元 (MMU)将虚拟内存地址转换成物理地址

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

采用了分页,页与页之间是紧密排列的,所以不会有外部碎片。内存分页机制会有内部内存碎片。

内存空间不够操作系统将正在运行的进程中「最近没被使用」的内存页面暂时写在硬盘上,称为换出(Swap Out)。需要的时候,再加载进来,称为换入(Swap In)。

一次性写入磁盘只有少数的一个页或者几个页,内存交换的效率相对段式较高。只有在程序运行中,需要用到对应虚拟内存页里面的指令和数据时,再加载到物理内存里面去。

https://cdn.xiaolincoding.com//mysql/other/388a29f45fe947e5a49240e4eff13538-20230309234651917.png

在分页机制下,虚拟地址分为两部分,页号和页内偏移

https://cdn.xiaolincoding.com//mysql/other/7884f4d8db4949f7a5bb4bbd0f452609.png

内存地址转换三个步骤:

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

简单的分页进程的页表空间开销会很大

多级页表

https://cdn.xiaolincoding.com//mysql/other/19296e249b2240c29f9c52be70f611d5.png

局部性原理

对于 64 位的系统,两级分页肯定不够了,就变成了四级目录

TLB

把最常访问的几个页表项存储到访问速度更快的硬件 TLB(Translation Lookaside Buffer) ,通常称为页表缓存、转址旁路缓存、快表等

段页式内存管理

  • 先将程序划分为多个有逻辑意义的段
  • 再把每个段划分为多个页

逻辑地址结构就由段号、段内页号和页内位移三部分组成

https://cdn.xiaolincoding.com//mysql/other/8904fb89ae0c49c4b0f2f7b5a0a7b099.png

Linux 内存布局

Linux 内存主要采用的是页式内存管理,但同时也不可避免地涉及了段机制

Linux 系统中操作系统本身和应用程序每个段都是从 0 地址开始的整个 4GB 虚拟空间(32 位环境下)。所以,地址空间都是线性地址空间(虚拟地址),这相当于屏蔽了处理器中的逻辑地址概念,段只被用于访问控制和内存保护。

虚拟地址空间内部被分为内核空间和用户空间两部分,不同位数的系统,地址空间的范围也不同

https://cdn.xiaolincoding.com//mysql/other/3a6cb4e3f27241d3b09b4766bb0b1124-20230309234553726.png

内核空间与用户空间的区别:

  • 用户态,只能访问用户空间内存
  • 内核态才可以访问内核空间的内存

每个虚拟内存中的内核地址,其实关联的都是相同的物理内存

用户空间分布的情况

https://cdn.xiaolincoding.com/gh/xiaolincoder/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E8%99%9A%E6%8B%9F%E5%86%85%E5%AD%98/32%E4%BD%8D%E8%99%9A%E6%8B%9F%E5%86%85%E5%AD%98%E5%B8%83%E5%B1%80.png
  • 代码段,包括二进制可执行代码
  • 数据段,包括已初始化的静态常量和全局变量
  • BSS 段,包括未初始化的静态变量和全局变量
  • 堆段,包括动态分配的内存,从低地址开始向上增长
  • 文件映射段,包括动态库、共享内存等,从低地址开始向上增长(跟硬件和内核版本有关 (opens new window))
  • 栈段,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。系统也提供了参数,以便我们自定义大小

代码段下面还有一段内存空间的(灰色部分),这一块区域是「保留区」。较小数值的地址不是一个合法地址,例如,我们通常在 C 的代码里会将无效的指针赋值为 NULL。因此,这里会出现一段不可访问的内存保留区,防止程序因为出现 bug,导致读或写了一些小内存地址的数据,而使得程序跑飞。

虚拟内存有什么作用

  • 运行超过物理内存大小的程序
  • 进程隔离
  • 标记属性的比特控制内存访问
 |