计算机系统的存储器层次结构自上而下依次分为:寄存器、高速缓存、内存储器、磁盘缓存、可移动存储介质5层。
存储介质的访问速度由下而上越来越快,容量越来越小,价格越来越高。
寄存器、缓存和内存均属于操作系统存储管理的管辖范畴,掉电后他们存储的信息不复存在,磁盘和磁带属于文件管理和设备管理的管辖对象,他们所存储的信息将持久性保存。
操作系统中管理分层存储体系的部分被称为存储管理器。它的任务是有效管理内存,记录哪些内存是正在使用的,哪些内存是空闲的,在进程需要的时候为其分配内存,在进程使用完之后回收内存。
基本概念
地址空间
地址空间是一个进程用户寻址内存的一套地址集合; 每个进程都有一套属于自己的地址空间,并且这个空间独立于其他进程的地址空间。简单的说,每个进程都运行在属于自己的内存沙盘中;地址空间是一种内存抽象,为程序创建了一种抽象的内存。
虚拟内存
虚拟内存的基本思想是每个程序都拥有自己的地址空间,这个空间被分割为很多块,每一块被称为一页或者页面(page),每一页都有连续的地址范围,这些页被映射到物理内存,并不是所有的页都在内存中才能运行程序。
页面和页框
虚拟地址空间按照固定大小划分内存,固定大小的内存被称为页或者页面,在物理内存对应的内存单元称为页框。
页表与TLB
页表
将一个虚拟地址转换为物理地址,一般有两种办法,用一个确定的数学公式进行转换或者用表格存储虚拟地址对应的物理地址,这类表格被称为页表。
页表由一个个条目组成,每个条目存储了一段虚拟地址对应的物理地址及其访问权限,或者下一个页表的地址。
举例:
一个典型二级页表的转换过程
根据给定的虚拟地址找到一级页表的条目
如果此条目是段描述符,那么返回物理地址,转换结束。
如果此条目是二级页表描述符,继续利用虚拟地址在此二级页表中找到下一个条目
如果第二个条目是页描述符,返回物理地址,转换结束。
其他情况出错
一级页表的地址转换流程
二级页表的地址转换换流程
加速查找-TLB
从虚拟地址到物理地址的转换过程可以可知,使用一级页表时,需要访问两次内存,第一次访问一级页表获取物理地址,第二次才是真正的读写数据。使用两级页表时需要访问三次内存,读写两次页表获取物理地址,最后才能真正读写数据。
上面流程大大降低了CPU的性能,通过一个高速,容量较少的存储器存储近期用过的页表条目,避免每次地址转换时都去主存中查找。这个用来帮助快速进行地址转换的存储器被称为转移查找缓存(TLB)。
虚拟地址(VA)到物理地址(PA)的转换流程
在程序运行需要读取或者写入某个内存地址的时候,虚拟地址不是直接被发送到内存总线上,而是被送到MMU(内存管理单元),MMU 将虚拟地址转换为真实的物理地址。
当程序引用到一部分在物理内存的地址空间时,直接用MMU(内存管理单元)进行映射。
当程序引用到一部分不在物理内存的地址空间时,会产生一个异常,由操作系统负责将缺失的部分重新装入物理内存并且重新执行失败的指令。
内存分段
一个常见的例子:编译器的工作过程,我们知道在编译器工作时需要保持多个数据结构:词法分析树,常数表,代码段,符合表 ,调用栈。使用保持多个数据结构并没有任何问题,问题出现在:这些数据结构可以独立的增长和缩小,从而造成了该数据结构所需的内存空间的变化。
解决方法:那一个程序占用一个虚拟空间不能解决我们各个数据结构段增长的需求!那么就使用多个虚拟地址空间来解决。
分段管理是将一个程序按照逻辑单元分成多个程序段,每个段使用自己单独的虚拟地址空间。5个段占用5个虚拟地址空间。这样一个段占用一个虚拟地址空间,就不会发生空间增长时碰撞到另一个段的问题。
实现方法:每个段都有名称和长度,地址指定了段名称和段内偏移。因此用户通过两个量来指定地址:段名称和段偏移。
为了实现简单起见,段是编号的,通过段号而不是段名称来引用。因此,逻辑地址由有序对组成:<段号,偏移>。
通常,在编译用户程序时,编译器会根据输入程序来自动构造段。 一个 C 编译器可能会创建如下段:
代码
全局变量
堆(内存从堆上分配)
每个线程使用的栈
标准的C库
在编译时链接的库可能分配不同的段。加载程序会装入所有这些段,并为它们分配段号。
段表的使用如上图所示。每个逻辑地址由两部分组成:段号 S偏移 D。段号用作段表的索引,逻辑地址的偏移 D 应位于 0 和段界限之间。如果不是这样,那么会陷入操作系统中(逻辑地址试图访问段的外面)。如果偏移D合法,那么就与基地址相加而得到所需字节的物理内存地址。因此,段表实际上是基址寄存器值和界限寄存器值的对的数。
缺页异常
程序加载的时候,并不是程序的所有内容都会加载在内存中,
执行特定指令时,如果发现它要访问的页没有在内存中,那么停止该指令的执行,并产生一个缺页中断,对应的中断处理程序可通过从外存加载该页的方法来排除故障,之后,原先引起的异常的指令就可以顺利的继续执行。
缺页异常的执行顺序:
1)硬件陷入内核,在内核堆栈中保存程序计数器。大多数机器将当前指令的各种状态信息保存在特殊的CPU寄存器中。
2)启动一个汇编代码例程保存通用寄存器和其他易失的信息,以免被操作系统破坏。这个例程将操作系统作为一个函数来调用。
3)当操作系统发现一个缺页中断时,尝试发现需要哪个虚拟页面。通常一个硬件寄存器包含了这一信息,如果没有的话,操作系统必须检索程序计数器,取出这条指令,用软件分析这条指令,看看它在缺页中断时正在做什么。
4) 一旦知道了发生缺页中断的虚拟地址,操作系统检查这个地址是否有效,并检查存取与保护是否一致。如果不一致,向进程发出一个信号或杀掉该进程。如果地址有效且没有保护错误发生,系统则检查是否有空闲页框。如果没有空闲页框,执行页面置换算法寻找一个页面来淘汰。
5) 如果选择的页框"脏"了,安排该页写回磁盘,并发生一次上下文切换,挂起产生缺页中断的进程,让其他进程运行直至磁盘传输结束。无论如何,该页框被标记为忙,以免因为其他原因而被其他进程占用。
6) 一旦页框"干净"后(无论是立刻还是在写回磁盘后),操作系统查找所需页面在磁盘上的地址,通过磁盘操作将其装入。该页面被装入后,产生缺页中断的进程仍然被挂起,并且如果有其他可运行的用户进程,则选择另一个用户进程运行。
7) 当磁盘中断发生时,表明该页已经被装入,页表已经更新可以反映它的位置,页框也被标记为正常状态。
8) 恢复发生缺页中断指令以前的状态,程序计数器重新指向这条指令。
9) 该例程恢复寄存器和其他状态信息
内存交换
操作系统中,所有运行进程需要的RAM数量总和要远远超出内存能够支持的范围,将所有进程一直保存在内存中需要非常大的内容容量。
同时在内存中的某些进程由于某事件尚未发生而被阻塞运行,但它却占用了大量的内存空间,甚至有时可能出现在内存中所有进程都被阻塞而迫使CPU停止下来等待的情况。另一方面,却又有着许多作业在外存等待,因无内存而不能进入内存运行的情况。显然这是对系统资源的一种严重浪费,使系统吞吐量下降。
内存交换技术指的是,进程没有在运行的时候,可以暂时从内存交换到备份存储,并在再次执行时转移回内存。
当物理内存出现不足时,操作系统的内存管理系统需要释放部分物理内存页。在Linux中,这一任务由内核的交换守护进程 kswaped 完成,该内核守护进程实际是一个内核线程,它的任务就是保证系统中具有足够的空闲页,从而使内存管理子系统能够有效运行。
总结
操作系统的使命之一就是内存管理,作用的是更高效合理的使用物理内存。
每个进程直接使用的都是虚拟内存,虚拟内存需要经过MMU/TLB映射才能使用到真正的物理内存,映射的时候是按页(page)映射的。
MMU 映射物理内存是通过页表的方式实现的,一般为多级页表,TLB 是为了加速查找页表实现的一种页表缓存。
进程虚拟地址的内存是分段和分页的
进程使用不在物理内存的内存地址时,会触发缺页中断,操作系统会自动装入缺失的内存,执行之前失败的指令。
操作系统使用内存交换技术来扩展物理内存。