- 虚拟内存是一种对主存的抽象
三个重要的功能:
- 将内存抽象为了磁盘的活动区域的缓存,并可以按照需求换入换出,高效的使用了主存
- 为每个进程提供了一致的地址空间,方便内存管理
- 保护每个进程地址空间不被其他进程破坏
- 虚拟地址需要翻译为物理地址,翻译的组件叫做内存管理单元(MMU),同时需要配合存放在主存中的页表
页表操作的问题
- 需要判断一个虚拟页是否缓存在主存上,如果有的话还要确定虚拟内存对应的物理地址。
- 如果不存在的话,还要确定虚拟页存放在磁盘的那个位置,并且在内存中牺牲一个页,并将虚拟也从磁盘复制到主存上,替换掉这个牺牲页
页表条目(PTE)
- 在页表中每个固定的偏移量都有一个固定的条目
- 一个PTE由一个有效位和一个n位地址字段组成
- 有效位标识标识该虚拟也是否被缓存在DRAM中
- N位的地址是物理内存页的起始位置,或者是磁盘上的起始位置(未设置有效位)
页命中
- 地址翻译会根据虚拟内存地址翻译出PTE的索引,并从内存读取它,发现设置了有效位,则直接读取地址拿到虚拟内存
缺页
- DRAM不命中则成为缺页
- 虚拟内存根据地址翻译得到PTE的索引,同时读取标志位,发现未设置,则会触发一个缺页异常,缺页处理程序会选出一个牺牲页,如果牺牲页已经被修改了,内核会把修改写会磁盘。
- 之后内核会把虚拟页从磁盘读出来,写入主存并更新PTE,之后会返回,将重新启动导致缺页的指令(故障)
- 磁盘和内存之间传送页的活动叫做换入
虚拟内存作为内存管理工具
共享内存
-
即是多个进程的虚拟地址映射到同一个物理内存地址上
-
VM简化了
- 链接
- 加载
- 代码
- 数据共享
- 已及应用程序的内存分配
简化链接
- 每个进程加载到内存中使用的格式都是代码段起始地址为0x40000开始,然后是数据段,堆等等,这样极大地简化了连接器的设计与实现,允许连接器生成完全链接的可执行文件,并且是独立于物理内存中代码和数据的最终位置的
简化加载
- 简化了加载的步骤,加载器只是为代码和数据段分配好未标记的虚拟页,并不会实际的加载进内存,在被执行时,cpu会按需加载
简化共享
-
简化共享,按理来说,每个进程都会有独立的内存空间,代码,数据,堆和栈信息都是不共享的,实际的物理地址也都是不重合的,但是难免会有需要共享的场景,比如说c语言库,动态链接库。操作系统安排每个进程中将适当的虚拟地址映射到相同的物理地址上去,节省了内存资源实现了共享内存
-
简化内存分配
-
操作系统分配给进程是连续的空间,比如malloc操作,但是在底层物理地址却不必分配连续的物理地址空间,可以随机的分配
虚拟内存还作为保护内存的工具
- 需要保护进程不能修改其他进程的代码和数据,同事保护本进程的代码和数据不被其他进程所修改,同事对于共享内存,不能非显示的修改
- 操作系统在为进程生成页表时,通过添加一些标识位,可以达到目的,
- Sup 只有内核态用户可访问
- READ 可读
- WRITE 可写
- 如果违反条件,则会出发段错误的报错
多级页表
- 页表是常驻内存中的,而且很大程度上每个进程都有一份自己的页表
- 对于32位系统来说,总共可以有2^32个地址,每个地址是1字节,2^32B=2^22KB=2^12MB=4GB
- 可以划分为4GB/4KB=2^32B/2^12B=2^20个
- 2^20*4B=2^22B=4MB
- 可以划分为4GB/4KB=2^32B/2^12B=2^20个
- 每个进程都有4MB存在于内存中,这无疑是很浪费的
- 常用的压缩页表的方法就是采用多级页表
- 如果采用二级页表,一级页表每个PTE标识一个4MB的块,则一级页表一共有1K个PTE,每个PTE大小4字节的话,一级页表只占用4k
- 1.对于一级页表来说,如果一级页表的PTE是空的,则就不需要分配二级页表,潜在巨大的节约
- 2.对于二级页表来说,并不需要常驻与内存中,在需要时创建页表,同时调入和调出,只有常用的页表才会缓存在主存里
linux虚拟内存系统
快表TLB
- 对PTE的缓存表