文章

内存

内存

用户空间与内核空间

  1. 现代的操作系统都引入的虚拟内存概念。
  2. 操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核,保证内核的安全,操作系统将虚拟内存划分为两部分,用户空间和内核空间。
  3. 将最高的 1G 字节(从虚拟地址 0xC0000000 到 0xFFFFFFFF),供内核使用,称为内核空间,而将较低的 3G 字节(从虚拟地址 0x00000000 到 0xBFFFFFFF),供各个进程使用,称为用户空间。

进程切换

为了控制进程的执行,内核必须有能力挂起正在 CPU 上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的。
从一个进程的运行转到另一个进程上运行,这个过程中经过下面这些变化:

  1. 保存 CPU 上下文,包括程序计数器和其他寄存器
  2. 更新 PCB 信息
  3. 把进程的 PCB 移入相应的队列,如就绪、在某事件阻塞等队列
  4. 选择另一个进程执行,并更新其 PCB
  5. 更新内存管理的数据结构
  6. 恢复处理机上下文

进程切换很耗资源。

进程的阻塞

正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语 (Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得 CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用 CPU 资源的

虚拟内存机制

物理内存

物理内存是指真实的物理设备中的地址。
如果 CPU 使用物理地址向内存寻址的话,就是下面这样,这条指令中的地址就是数据真实存放的地址。
0hf8l

虚拟内存

虚拟内存背景

1、进程空间隔离问题

每个进程需要有独立且隔离的安全空间,
ta2x1

虽然分配给每个进程的空间是无交集的,但是仍然无法避免进程在某些情况下出现访问异常的情况

2、内存效率低下问题

3、定位调试和编译运行问题

由于程序运行时的位置是不确定的,我们在定位问题、调试代码、编译执行时都会存在很多问题。如果所有进程的空间地址分配都是一样的,那么 Linux 在设计编译和调试工具时就非常简单了,否则每个进程都可能是定制化的

什么是虚拟内存?

虚拟内存是介于操作系统物理内存和进程之间的中间层;引入虚拟内存,对于每一个进程都认为进程自身可以拥有很大的内存空间;虚拟地址是面向每个进程的。
CPU 使用虚拟地址向内存寻址,通过专用的MMU(内存管理单元)硬件将虚拟地址转换为真实的物理内存地址 (地址翻译),操作系统负责把虚拟地址和物理地址的映射关系维护在页表中。
每个程序都拥有自己的虚拟地址空间,这个地址空间被分割成一页一页,这些页映射到物理内存,但不需要连续的物理内存,也不需要所有页都必须在物理内存中;当程序引用到不在物理内存的页时,由 MMU 发起缺页中断来映射,将缺失的页装入物理内存并重新执行失败的指令。
ha9yy

指令中的地址不是数据真实存放的地址

虚拟内存的作用?

虚拟内存是操作系统物理内存和进程之间的中间层,它为进程隐藏了物理内存这一概念,为进程提供了更加简洁和易用的接口以及更加复杂的功能。
ksr43

  1. 虚拟内存可以利用内存起到缓存的作用,提高进程访问磁盘的速度
  2. 虚拟内存可以为进程提供独立的内存空间,简化程序的链接、加载过程并通过动态库共享内存;
  3. 虚拟内存可以控制进程对物理内存的访问,隔离不同进程的访问权限,提高系统的安全性;

页面置换算法

当发生缺页中断时,如果当前内存中并没有空闲的页面,操作系统就必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。用来选择淘汰哪一页的规则叫做页面置换算法,或者看成是淘汰页面的规则。
页面置换算法的主要目标是使页面置换频率

  1. OPT 最佳 (Optimal replacement algorithm)

所选择的被换出的页面将是最长时间内不再被访问,通常可以保证获得最低的缺页率

  1. FIFO 先进先出

选择换出的页面是最先进入的页面,该算法会将那些经常被访问的页面换出,导致缺页率升高

  1. 第二次机会算法

为了解决 FIFO 会把经常使用的页面置换出去

  1. LRU 最近最久未使用
  2. LFU 最少使用

虚拟内存空间分布

Linux 内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的,进程就可以很方便地访问内存,也就是我们常说的虚拟内存虚拟内存。

虚拟内存被分为用户空间内核空间两部分;当我们的进程在用户态的时候,只能访问 * 用户空间;只有进入内核态,才能访问内核空间。用户空间是每个进程私有的;内核空间是每个进程共享的,不与任何用户进程共享;

根据地址范围的不同,我们分为 32 位和 64 位,一般我们见的比较多的就是 32 位操作系统。32 位,虚拟地址空间低位 03G 用于用户层;虚拟地址高位空间 34G 用于内核层。

32 位地址和 64 位地址空间的分布:

2kcyu

32 位虚拟内存地址空间分布:
4akor

运行时数据区域:
fnp1m

地址空间保存的数据:

  1. 栈:保存局部变量、函数形参、自动变量。数据具有先进先出、后进后出的特点;
  2. 堆:保存由 malloc、ralloc、calloc 分配空间的变量;
  3. BSS 段:保存未初始化或初始化为 0 的全局变量和静态局部变量;
  4. data 段(数据段):保存初始化不为 0 的全局变量或者 static 修饰的变量;
  5. 代码段:保存代码、可执行代码、字符串字面值、只读变量;
  • 每个进程都占有了这么多的虚拟内存空间,那么多个进程怎么办?

每个进程都以为自己占据了全部的地址空间;其实只有在实际使用虚拟内存的时候,才会分配物理内存

虚拟内存与物理内存的联系与区别

https://blog.csdn.net/lvyibin890/article/details/82217193

操作系统以页为单位管理内存,当进程发现需要访问的数据不在内存时,操作系统可能会将数据以页的方式加载到内存中,这个过程是由内存管理单元(MMU)完成的

分页

虚拟地址和物理地址的映射关系是以 “ 页 “ 为单位的。分页就是把整个虚拟内存和物理内存分割成大小固定的块,以一个页作为映射的最小单位。
运行时,CPU 请求一个虚拟地址,虚拟地址又被翻译为物理地址,从而确定数据在内存中的哪个位置。

页表

下面的页表中记录了这个进程虚拟内存每个页的映射关系。

f204x

当 CPU 寻址的时候,这个映射会有三种可能:

  1. 未分配:虚拟地址所在的那一页并未被分配,代表没有数据和他们关联,这部分也不会占用内存。
  2. 未缓存:虚拟地址所在的那一页被分配了,但并不在内存中。
  3. 已缓存:虚拟地址所在的那一页就在内存中。

缺页异常

当访问一个未缓存的区域时,系统将产生缺页中断,然后进程被阻塞,等待操作系统将缺失的那一页从磁盘复制到内存。当复制完成后,CPU 继续执行导致缺页中断的那条指令,此时就会正常执行了。这种仅在需要的时候将页面拷贝到内存的策略叫做按需调度页面

可以想象当程序被装入内存的时候,开始时仅有有很小的一部分内容被放入内存。程序在运行中不断缺页,不断的把需要的部分拷贝进内存。

Linux 内存映射

在 Linux 中,将一片虚拟内存和一个磁盘上的对象关联起来,并用磁盘上的对象初始化这片虚拟内存,这个机制就叫做内存映射

内存映射原理

虚拟内存映射到物理内存地址,内核为每一个进程维护了一张表,记录了他们对应的映射关系;

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

mmap 函数

mmap 是 Linux 中常用的系统调用 API,用途广泛,Android 中也有不少地方用到,比如匿名共享内存,Binder 机制等。

mmap 函数原型如下:

1
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offsize);
  1. 参数 start:指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址
  2. 参数 length:代表将文件中多大的部分映射到内存
  3. 参数 prot:映射区域的保护方式。
  4. 返回值是 void * 类型,分配成功后,被映射成虚拟内存地址。

Ref

本文由作者按照 CC BY 4.0 进行授权