虚拟内存是什么?它是对主存和I/O设备的抽象,这一点在漫谈进程和线程中已经提及过,也就是说,虚拟内存是将内存看做硬盘的高速缓存,内存中只保存程序的活动区域,根据需要在硬盘和内存之间传输数据;同时,虚拟内存为每个进程提供一个一致的地址空间,比如说32位计算机,每个进程的地址范围是[0,4G]。此外,虚拟内存保护每个进程的地址空间不被其他进程破坏,那么,通过何种方法使得每个进程的地址空间是独立的?看完本文,这个问题你自然就知道答案了。
内存管理的要求
-
针对批处理系统,程序顺序执行,程序依次装入内存运行,一个新装入的程序会完全覆盖老的程序。
-
针对分时系统,多个程序并发执行,要把尽可能多的程序装入内存,但是物理内存的空间是有限的,经常需要将程序换入换出,这样一来,系统性能就变差了;程序在内存中要区分开。
内存分配
如上图,程序1、程序2、程序3装入到内存,而程序2运行完成被换出,内存空闲出20k,然后进来程序4,大小为25K,此时,只有两处空闲块,10K和20K,没有一处是符合条件的,应该怎么办?一个明显的办法就是将两块空闲区域进行合并,形成一个大小为30K的空闲块满足程序4。
注意:此时描述的内存的物理地址,物理内存使用的是连续地址,下面详细介绍物理地址。
物理地址
使用物理地址的缺点:当多个程序同时对某个地址进行操作时就会引起冲突,如下图所示,程序1和程序2中都有指令movl eax,(100)
。针对这个问题怎么解决呢?
在装载程序的时候,修改指令的地址。例如程序2中的(100)+1000,其中1000代表这个程序的开始地址,而程序1中的(100)+0。这样做是很困难的,因为需要我们理解所有的指令。既然这个方法不好,那么有没有其他方法呢?答案就是逻辑地址。
程序1、2地址冲突逻辑地址
CPU有一个内存管理单元(MMU),它有一个基址寄存器,它保存着每个程序的开始地址,比如说程序1的基址寄存器是1000,偏移量是200,转换成物理地址是1200。
逻辑地址
分页
假设一个程序很大,需要占据所有内存,而内存管理的一个要求就是把尽可能多的程序装入内存,两者相互矛盾。应对方法就是分页,就是说每个程序开始运行时只会加载部分数据到内存中,操作系统会为每个进程维护一个页表,页表是维护虚拟页和物理页的映射关系,当页表中的虚拟页对应的物理页是空白时,操作系统会发生缺页中断。分页的理论依据是局部性原理(空间局部性+时间局部性),也正是这个原因,程序在大部分时间内不需要进行页面置换。如果发生缺页中断,缺页中断处理程序读取磁盘,选择一个空闲物理页面,修改页表,重新执行程序。
- 注意事项
- 每个进程都要有一个页表,进程PCB有指向页表的指针
- 页表访问要非常快(硬件缓存来拯救:转换缓冲区--TLB)
- 页表可能非常大(2^32 的内存空间,每个页大小2^12 ,页表中需要2^20个页表条目,假设每个条目4Byte,需要4M空间来存放表,而且每个进程都需要4M,这是非常占用空间的。可以采用多级页表,反向页表等技术来解决)。
- 分页具体流程
以CPU执行MOV (0x560) EAX
为例,CPU内部会将逻辑地址进行拆分成页号和偏移量,然后将逻辑地址转换成物理地址。
分页流程
页面置换算法
- 内存是有限的,不可能把所有的页面都装进来,缺页时需要进行页面置换。
- 页面置换背后是个通用的问题(Web服务器的缓存、Redis、Memcached的缓存等等)。
FIFO(先进先出)
先进先出算法思想很简单,当内存满了,优先置换出最先进入内存的页面。但是它存在一个问题:经常被访问的数据有可能被换入换出,下面我就举个简单的栗子。假设只有3个物理页面,逻辑页面的访问次序是: 7 0 1 2 0 3 0 4
。
LRU(最近最少使用)
LRU算法就是所有页用栈组成,当栈满了,且新增加元素没有命中,则将栈底元素淘汰,新增元素放到栈顶;当栈满了,且新增元素命中,则只需要将新增元素移动到栈顶位置即可。假设只有3个物理页面,逻辑页面的访问次序是: 7 0 1 2 0 3 0 4
。
Clock算法
Clock算法是LRU算法的近似实现,它为每个页加一个引用位,默认值为0,无论读还是写,都置为1,它把所有的页组成一个循环队列,选择淘汰页的时候,扫描引用位,如果是1则改成0(相当于再给该页面一次存活的机会),并扫描下一个;如果该引用位是0,则淘汰该页,换入新的页面。假设只有3个物理页面,逻辑页面的访问次序是: 3 4 2 6
。
分段
上述介绍的页表机制是面向机器的,而为了程序员更好的理解程序,我们的先辈们又提出了分段概念,就是将程序划分为若干段部分,每一段都有独立的功能,例如:代码段,数据段,栈,堆等。通过分段技术,我们把内存空间分成一个个可以自治的段,而且把内存从一维空间变成了一个二维空间。
段表结构
段页结合
段页结合流程:首先根据段表信息,将逻辑地址转换成另一个逻辑地址,在转换的过程中会判断偏移量是否超过指定长度,如果没有超过,则,则根据页表将逻辑地址转换成物理地址。
段页结合虚拟内存具体实现
这里介绍Linux中的虚拟内存的具体实现,如下图,task_struct结构体是进程描述符,属于进程管理(PCB),其中,mm(memory manage)表示内存管理,它指向mm_struct结构体,它描述linux下进程的虚拟地址空间,它又包含两个重要字段:pgd、mmap,其中,pgd指向第一级页表的基址,而mmap指向一个vm_area_struct(区域结构)的链表,vm_start,vm_end分别表示数据的起始地址,vm_prot描述的是这个区域包含的所有页的读写许可权限;vm_flags描述这个区域是和别的进行共享的,还是该进程私有的
Linux是如何组织虚拟存储器Linux缺页中断:MMU(内存管理单元)试图翻译一个虚拟地址A,当这个虚拟地址对应的物理地址不在内存中是,触发一个缺页中断。虚拟地址A是合法的吗?地址A在某个区域地址内吗(vm_start,vm_end),如果不存在,segement fault!段错误。如果存在,则接着判断进程是否可以读,写,执行这个区域内页面的权限?如果没有权限,触发保护异常。经过上诉两项判断,如果都是正常的,最后才开始真正的缺页处理,从硬盘装载数据,修改页表。
image
欢迎关注微信公众号:木可大大,所有文章都将同步在公众号上。
网友评论