内存
用户空间与内核空间
- 现代的操作系统都引入的虚拟内存概念。
- 操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核,保证内核的安全,操作系统将虚拟内存划分为两部分,用户空间和内核空间。
- 将最高的 1G 字节(从虚拟地址 0xC0000000 到 0xFFFFFFFF),供内核使用,称为内核空间,而将较低的 3G 字节(从虚拟地址 0x00000000 到 0xBFFFFFFF),供各个进程使用,称为用户空间。
进程切换
为了控制进程的执行,内核必须有能力挂起正在 CPU 上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的。
从一个进程的运行转到另一个进程上运行,这个过程中经过下面这些变化:
- 保存 CPU 上下文,包括程序计数器和其他寄存器
- 更新 PCB 信息
- 把进程的 PCB 移入相应的队列,如就绪、在某事件阻塞等队列
- 选择另一个进程执行,并更新其 PCB
- 更新内存管理的数据结构
- 恢复处理机上下文
进程切换很耗资源。
进程的阻塞
正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语 (Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得 CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用 CPU 资源的。
虚拟内存机制
物理内存
物理内存是指真实的物理设备中的地址。
如果 CPU 使用物理地址向内存寻址的话,就是下面这样,这条指令中的地址就是数据真实存放的地址。
虚拟内存
虚拟内存背景
1、进程空间隔离问题
虽然分配给每个进程的空间是无交集的,但是仍然无法避免进程在某些情况下出现访问异常的情况
2、内存效率低下问题
3、定位调试和编译运行问题
由于程序运行时的位置是不确定的,我们在定位问题、调试代码、编译执行时都会存在很多问题。如果所有进程的空间地址分配都是一样的,那么 Linux 在设计编译和调试工具时就非常简单了,否则每个进程都可能是定制化的
什么是虚拟内存?
虚拟内存是介于操作系统物理内存和进程之间的中间层;引入虚拟内存,对于每一个进程都认为进程自身可以拥有很大的内存空间;虚拟地址是面向每个进程的。
CPU 使用虚拟地址向内存寻址,通过专用的MMU(内存管理单元)硬件将虚拟地址转换为真实的物理内存地址 (地址翻译),操作系统负责把虚拟地址和物理地址的映射关系维护在页表中。
每个程序都拥有自己的虚拟地址空间,这个地址空间被分割成一页一页,这些页映射到物理内存,但不需要连续的物理内存,也不需要所有页都必须在物理内存中;当程序引用到不在物理内存的页时,由 MMU 发起缺页中断来映射,将缺失的页装入物理内存并重新执行失败的指令。
指令中的地址不是数据真实存放的地址
虚拟内存的作用?
虚拟内存是操作系统物理内存和进程之间的中间层,它为进程隐藏了物理内存这一概念,为进程提供了更加简洁和易用的接口以及更加复杂的功能。
- 虚拟内存可以利用内存起到缓存的作用,提高进程访问磁盘的速度
- 虚拟内存可以为进程提供独立的内存空间,简化程序的链接、加载过程并通过动态库共享内存;
- 虚拟内存可以控制进程对物理内存的访问,隔离不同进程的访问权限,提高系统的安全性;
页面置换算法
当发生缺页中断时,如果当前内存中并没有空闲的页面,操作系统就必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。用来选择淘汰哪一页的规则叫做页面置换算法,或者看成是淘汰页面的规则。
页面置换算法的主要目标是使页面置换频率
- OPT 最佳 (Optimal replacement algorithm)
所选择的被换出的页面将是最长时间内不再被访问,通常可以保证获得最低的缺页率
- FIFO 先进先出
选择换出的页面是最先进入的页面,该算法会将那些经常被访问的页面换出,导致缺页率升高
- 第二次机会算法
为了解决 FIFO 会把经常使用的页面置换出去
- LRU 最近最久未使用
- LFU 最少使用
虚拟内存空间分布
Linux 内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的,进程就可以很方便地访问内存,也就是我们常说的虚拟内存虚拟内存。
虚拟内存被分为用户空间和内核空间两部分;当我们的进程在用户态的时候,只能访问 * 用户空间;只有进入内核态,才能访问内核空间。用户空间是每个进程私有的;内核空间是每个进程共享的,不与任何用户进程共享;
根据地址范围的不同,我们分为 32 位和 64 位,一般我们见的比较多的就是 32 位操作系统。32 位,虚拟地址空间低位 03G 用于用户层;虚拟地址高位空间 34G 用于内核层。
32 位地址和 64 位地址空间的分布:
地址空间保存的数据:
- 栈:保存局部变量、函数形参、自动变量。数据具有先进先出、后进后出的特点;
- 堆:保存由 malloc、ralloc、calloc 分配空间的变量;
- BSS 段:保存未初始化或初始化为 0 的全局变量和静态局部变量;
- data 段(数据段):保存初始化不为 0 的全局变量或者 static 修饰的变量;
- 代码段:保存代码、可执行代码、字符串字面值、只读变量;
- 每个进程都占有了这么多的虚拟内存空间,那么多个进程怎么办?
每个进程都以为自己占据了全部的地址空间;其实只有在实际使用虚拟内存的时候,才会分配物理内存
虚拟内存与物理内存的联系与区别
https://blog.csdn.net/lvyibin890/article/details/82217193
页
操作系统以页为单位管理内存,当进程发现需要访问的数据不在内存时,操作系统可能会将数据以页的方式加载到内存中,这个过程是由内存管理单元(MMU)完成的
分页
虚拟地址和物理地址的映射关系是以 “ 页 “ 为单位的。分页就是把整个虚拟内存和物理内存分割成大小固定的块,以一个页作为映射的最小单位。
运行时,CPU 请求一个虚拟地址,虚拟地址又被翻译为物理地址,从而确定数据在内存中的哪个位置。
页表
下面的页表中记录了这个进程虚拟内存每个页的映射关系。
当 CPU 寻址的时候,这个映射会有三种可能:
- 未分配:虚拟地址所在的那一页并未被分配,代表没有数据和他们关联,这部分也不会占用内存。
- 未缓存:虚拟地址所在的那一页被分配了,但并不在内存中。
- 已缓存:虚拟地址所在的那一页就在内存中。
缺页异常
当访问一个未缓存的区域时,系统将产生缺页中断,然后进程被阻塞,等待操作系统将缺失的那一页从磁盘复制到内存。当复制完成后,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);
- 参数 start:指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址
- 参数 length:代表将文件中多大的部分映射到内存
- 参数 prot:映射区域的保护方式。
- 返回值是 void * 类型,分配成功后,被映射成虚拟内存地址。
Ref
- 操作系统:图文详解 内存映射
https://www.jianshu.com/p/719fc4758813 - 为什么 Linux 需要虚拟内存
https://draveness.me/whys-the-design-os-virtual-memory/ - Linux 内存背后的那些神秘往事
https://os.51cto.com/article/702386.html