背景
硬件
- CPU所能直接访问的存储器只有内存和处理器内的寄存器
- 因此执行指令以及指令使用的数据必须在这些直接可访问的存储设备上
- 如果不在,那么在CPU使用前,必须先把数据移到内存中
- CPU内置寄存器通常在一个或几个时钟内就能完成访问
- 而对于内存就不行了,就需要多个
- CPU就会由于没有数据来完成正在执行的指令而需要暂停
- 这是难以忍受的
- 因此就在CPU和内存之间增加了高速内存来协调这种速度差异
- 除了保证物理内存的相对速度之外,还要确保操作系统不被用户进程所访问
- 还要确保用户进程不被其他用户进程访问
- 那就是基地址寄存器,界限地址寄存器
- 基地址寄存器决定了地址的起始位置
- 界限地址寄存器决定了距离
地址绑定
- 将指令与数据绑定到内存地址有以下几种情况
- 编译时:如果就知道进程将在内存中的驻留地址,那么就可以生成绝对代码
- 例如,如果实现就知道用户进程驻留在内存地址R处,那么所生成编译代码就可以从该位置开始并向后扩展。
- 如果将来开始地址发生变化,那么就必须重新编译代码
- 加载时:如果编译时不知道进程驻留在内存的何处,那么编译器就必须生成可重定位代码。
- 绑定会延迟到加载时才进行
- 如果开始地址发生变化,只需重新加载用户代码以引入改变值
- 执行时:如果进程在执行时,可以从一个内存段移到另一个内存段
- 那么绑定必须延迟到执行时才进行
- 绝大多数计算机操作系统采用这种方法
逻辑(虚拟)地址和物理地址
- CPU所生成的地址通常称为逻辑地址
- 内存单元所看到的地址通常称为物理地址
- 用户程序处理逻辑地址,内存映射硬件,将逻辑地址转变为物理地址
- 逻辑地址在使用前必须映射到物理地址
动态加载
- 进程的整个程序和数据必须处于物理内存中
- 因此进程的大小受物理内存大小的限制
- 过程:
- 主程序装入内存并执行
- 当一个子程序A需要调用另一个子程序B时,调用的子程序A先检查子程序B是否已加载
- 如果没有,则加载
- 优点:不用的子程序决不会被加载
动态链接不太懂
- 与动态加载类似,不过:这里不是将加载延迟到运行时,而是将链接延迟到运行时
- 如果有动态链接,那么二进制镜像中,对每个库程序的引用都是一个存根
- 存根是一小段代码,用来指出如何定位适当的内存驻留库程序,或如果该程序不在内存时,应如何装入库
- 当执行存根时,它首先检查所需子程序是否已经在内存中
- 如果不在,就将子程序装入内存
- 不管如何,存根都会用子程序地址来替换自己,并开始执行子程序
- 因此下次执行该子程序代码时,就可以直接进行,而不会因为动态链接而产生任何开销
交换
- 进程需要在内存中以便执行。不过进程可以暂时从内存中交换到备份内存上
- 需要再次执行时,再调回到内存中
- 假如,有一个采用转轮法的多道程序环境
- 当时间片用完,内存管理器开始将刚刚的程序换出,将另一进程换入
- 同时,CPU还可以将时间片分给内存中的其他进程
- 除此之外,时间片必须够大,以保证执行效率(如果较小,那么大部分时间会被用在进程的切换,而不是计算。即尽量降低调度在时间片中的占比)
- 内存管理器可以交换出低优先级的进程
内存分配
碎片问题
- 程序不断的在内存中进进出出,就会在内存中残留很多大大小小的碎片空间
- 他们断断续续的存在于连续的内存中,被正在内存中的进程分隔
- 他们的总大小可能很大
- 但是由于都是分散开的
- 所以其实一个进程也放不下
- 因此可能就会导致内存实际能使用的大小变小
分页
- 将物理内存分为固定大小的块,称为帧
- 将逻辑内存也分为,同样大小,的块,称为页
- 页大小(帧大小)是由硬件来决定的,通常是2的次幂。方便将逻辑地址转换成页号和页偏移
- 页号:作为页表中的索引
- 页表:包含每页所在物理内存的基地址,这些基地址和页偏移的组合就形成了物理地址
- 当系统进程需要执行时
- 他将检查该进程的大小,按页来计算。进程的每一页都需要一帧
- 因此,如果进程需要n页,那么内存中至少应有n个帧
- 哪些帧已占用,哪些帧可用,共有多少帧等。这些信息通常保存在帧表中。
硬件支持
- 每个操作系统都有自己的方法来保存页表
- 绝大多数都为每一个进程分一个页表
- 转换表缓冲区TLB,是关联的快速内存
- 由键和值组成,当关联内存根据指定值查找时,它会与所有键进行比较,如果找到相应条目,就返回相应域值
- 当CPU产生逻辑地址后,其页号提交给TLB。如果其中找到了对应的页号,那么也就得到了帧号
共享页
- 就当多个进程,代码段相同,但是数据段不同时,就可以采用共享
- 共同使用同一代码段,然后各自维自己的数据段
页表结构
层次页表
哈希页表
反向页表
分段
- 通常,在编译用户程序时,编译器就会自动根据输入程序来构造段
- 一个C编译器,可能会创建如下段:
- 代码
- 全局变量
- 堆
- 每个线程采用的栈
- 标准的C库函数
- 同页表类似,段通过段表进行维护映射
- 段表中有段基地址和段界限
- 段基地址包含该短在内存中的开始物理地址
- 而段界限指定该短的长度