1. 虚拟内存
1. 内存存在的问题
1. 内存不足:如果是逻辑内存直接映射到物理内存,当逻辑内存超过物理内存的时候,计算机就会出现内存不足的情况,导致程序崩溃。
2. 内存碎片化:如果程序频率启动或退出,会产生内存碎片,对于连续分配内存时,即使碎片内存数量比申请的内存大,但可能导致申请失败,因为没有足够的连续内存。
3. 程序间互相修改内存:如果程序切换时,不同的程序指向相同的内存时,会导致修改数据错乱。
4. 解决以上问题的方案:虚拟内存
2. 什么是虚拟内存?
3. 如何实现虚拟内存?
2. 内存的3个问题:如果所有程序可以直接访问相同的内存空间
1. 假如内存不足会怎么样?(对于一台32位系统来说,可以使用的内存有:2^32 bytes = 4GB);内存不足,超出内存空间会导致崩溃(crash)(例如32位系统,只有1GB RAM)
2. 内存碎片化:如果连续分配内存,程序可能找不到合适的空间(run out of space),即使有空间,但是不连续
3. 多个程序指向相同的地址:不同程序间读写相同的内容,导致数据污染或崩溃
怎么解决?让每一个程序拥有自己的虚拟内存空间。把程序的内存空间映射到物理内存(物理内存不足时把暂时不用的swap到硬盘上)
3. 什么是虚拟内存(Any problem in computer science can be solved by adding indirection.)
1. 虚拟内存是一个间接容器
- 如果没有虚拟内存时,程序地址直接映射到物理内存。假如内存为1G,程序A运行时占用了1GB,此时再启动程序B时,发现没有内存空间可用,就导致崩溃。
- 虚拟内存:通过Map来查找程序地址对应的物理内存地址。
2. 虚拟内存是怎么解决3个问题:
- 内存不足时:当向系统申请内存但内存不足时,系统会把根据置换算法把暂进不用的内存置换到硬盘里,更新映射关系到硬盘上,再更新新申请的内存映射关系,让我们产生无限内存的错觉。(交换到硬盘后会导致性能下降,硬盘读取速度比内存慢太多了)。例如下面这个例子,程序0,1,2分别加载了物理地址的0,1,2。此时程序3进来,也需要申请内存。然后就根据算法先把内存腾出来,腾出来的放到磁盘上,再把腾出来的空间给程序3。这样就可以解决内存不足的问题而不会导致崩溃。
- 内存碎片:程序通过自身的映射表可以随意找到合适的物理内存,而不一定需要连续分配。
- 程序间相同的地址:即使程序的地址相同,但是每个程序通过自己的映射表映射到不同的物理内存而不会互相产生干扰。但是程序间相互独立一定是好的吗?并不是,因为有些内存是需要共享的。例如不同的程序会共享系统文件,系统选择框等。让程序间实现共享内存的方法是把地址指向相同的物理内存。
3. 内存映射主要由操作系统的内存管理单元(Memory Management Unit, MMU)管理操作的,一切都是同它来掌控。
4. 虚拟内存的原理
1. 核心方法:分离内存空间
1. 虚拟内存:程序操作的地址
2. 物理内存:计算机的物理内存
2. 逻辑地址(亦称虚拟地址:Virtual Address)
1. 程序使用的地址
2. 在MIPS架构上,32位操作系统的取址范围为:0~2^32-1
3. 物理地址(Physical Address)
1. 硬件和内存通信内容
2. 地址空间大小决定了有多少内存
4. 虚拟内存的原理
1. 程序是如何访问内存的:地址翻译
1. 程序指定加载一个逻辑地址
2. 计算机通过映射把逻辑地址转换成物理地址
3. 如果物理地址不在内存中,操作系统会把它从硬盘中加载到内存里,更新映射关系(原来是指向disk的,现在指向内存)
4. 计算机通过物理地址把内存内容读取出来并且返回给程序
5. 页表(page table)
1. 什么是页表
页表是一种数据结构,它用于计算机操作系统中的虚拟内存系统,其存储了虚拟地址到物理地址间的映射。--维基百科
2. 页表入口数量(Page Table Entry, PTE)
以32-bit系统为例,每个程序拥有的虚拟内存空间大小为2^32
= 4GB,每个地址所占的大小4个字节(Bytes),所以虚拟内存的逻辑地址数量为: 2^
32/4=2^30个。
3. 如何管理页表
1. 连续页表入口
所有页表入口排成一个2^30
个连续地址空间,每个地址单独映射,一个逻辑地址对于页表一对一映射到物理地址。这样的问题是:映射的数量太多了:2^32
/4=2^30=1billion个。
2. 页表分块
把页表分块,每一块为4KB,每一块大小对应逻辑地址也是分块,索引到分块时通过偏移量(page offset)来找到准确位置。
解决了映射的数量多的问题,同时也覆盖了所有情况,但是没有原来方便,因为每一次查表都需要先找到页表入口再计算偏移量才能准确找到位置。
每个分块的大小为4KB,它的偏移量需要12位来示:2^12=4KB。这种情况下,页表入口数量为:2^32/4KB=1million个。
查找过程:
6. 地址转换
1. 对于1台32-bit系统,256MB的内存和4KB页表块大小的计算器来说意味着什么?
1. 32-bit系统:代表逻辑地址大小为32-bit。
2. 256MB的内存:代表物理地址大小为256MB, 范围是:0 ~ 2^28-1,地址大小为28-bit。
3. 4KB页表块大小: 意味着偏移量为4KB,占12-bit。
4. 由于页表是根据页号和偏移量两大块组合的,此时逻辑地址和物理地址的构成如下:
7. 地址转换例子
https://www.youtube.com/watch?v=6neHHkI0Z0o
在操作系统上,每个地址实际上是知道对应该地址的长度的,意思是说:通过逻辑地址找到了物理地址,加上长度,就可以找到逻辑地址实际的存储内容了。
8. 缺页异常(缺页中断)
1. 首先,怎么判断是否在是否在内存中:页表映射是否指向硬盘(disk)
2. 如果物理页不在内存中怎么办?
1. CPU生成一个缺页异常(page fault exception)
2. CPU跳转到系统缺页处做清除置换
1. 系统选择某一页从内存中移除并写入硬盘中(这里涉及页面转换算法,如最优算法、先进先出算法、最近最久未使用算法、时钟算法、最不常用算法)
2. 如果被选中从内存中移除的页面是脏数据,必须把它先写到硬盘上
3. 从硬盘中读取缺页异常的页面并写到内存中
4. 更新页表的映射关系
3. 系统跳转回到引起缺页异常的指令处
9. 内存保护
1. 每一个程序都有自己的32-bit虚拟地址空间
2. 进程的地址划分:内核(所有程序共享),栈,库,堆,Data和Text
1. 1GB空间给内核
2. 静态数据在内存起始部分
3. 堆是向上增大的,意味着堆的地址会越来越大
4. 栈是向下递减的,意味着向栈里增加内容时,栈的地址会变小。(注意:有些网上说法甚至是大学老师画图时,都会把栈底画在下面,实际程序的话是栈底在顶部)
3. 在栈和堆的起始位置会增加一个随机偏移值(random offset),是为了增加安全性,不让外部知道。
10. 加速虚拟内存访问(地址变换高速缓存,快表)
1. 增加一个特殊的缓存页表: TLB(Translation Lookaside Buffer)
1. 更快的访问:只需要一次计算(cycle)即可以得出物理地址(通过页表查询需要2步:先在页表上映射找到物理页号,再根据页号找到实际物理地址)
2. 类似一个缓存,如果命中就效率提升,否则回到慢查找过程。
2. TLB很小,但很快
1. TLB分为指令TLB和数据TLB
2. 64个缓存项
3. 查找过程
11. 多级页表(分页)
1. 为什么要分页?
如果每个程序加载时直接分配2^32的页表,就会占用4MB的内存。倘若所有内存都用了,那就没问题。但是很多时间刚开始是用不上的,导致了浪费内存,毕竟内存还是蛮贵的。所以要想办法初始减少分配,提高利用率
2. 如何实现分页
把原来的一个Page Table分为多级页表。例如通过2级页表,1级页表是为了索引2级页表,2级页表索引物理地址。假设页表的大小为4KB,程序启动时所需要的内存为:4KB + 4KB = 8KB,比起4M来说,真的是少太多了。