【README】
1.本文内容总结自 B站 《操作系统-哈工大李治军老师》,内容非常棒,墙裂推荐;
2.程序使用内存的3个步骤:
- 步骤1:把程序分为多个段,包括代码段,数据段;这是编译要做的事情;
- 步骤2:在内存中找一段空闲内存(或空闲分区);
- 步骤3:一旦找到空闲分区,就把磁盘上的程序的段内容读入到空闲分区;在读的过程中,需要把LDT初始化好,LDT存储了段序号与段基址的映射关系;
【1】内存分区
1)内存分区: 内存如何分割,以便把程序的各个段载入到相应的内存分区;
简单地,内存分区指的是划分的一段用于存储程序段(或代码段cs,或数据段ds)的内存空间;
【1.1】固定分区与可变分区
1)固定分区
等分,操作系统初始化时把内存等分为k个分区;
但是段长度有大有小,固定分区不能满足段长度不定的业务场景;
【1.2】可变分区管理
1)核心数据结构
- 空闲分区表:存储空闲的内存分区数据(或段释放后的内存空间列表);
- 已分配分区表:已使用的内存分区的记录信息;
- 注意:分区信息只需要记录 分区基址 和 长度 这两种信息;
2) 请求分配内存
【图解】
- 目前空闲的内存地址空间为 250K~500K;
- 而段3内存请求分配100K,则把250K~350K分配给该段3(seg3);并新增一条已分配分区表记录;
3)释放内存
【例】段2不再需要,释放段2的内存
同时把释放出的内存更新到空间分区表,从已分配分区表中移除段2的记录;
4)再次申请
此时空闲分区表如下:
内存分区序号 | 基址 | 长度 |
1 | 350K | 150K |
2 | 200K | 50K |
【1.3】内存分区分配算法
内存请求分配40K,又分区1有150K,分区2有50K,那应该分配分区1还是分区2给到这个请求?
- 首先适配:(350, 150),挑选空闲分区表中第一条且分区空间足够大的分区来分配;空闲分区表查询足够快;
- 最佳适配:(200, 50),内存空间浪费少,但空闲分区大小会越来越小,分割后产生的内存碎片比较多;
- 最差适配:(350, 150),挑选最大的分区进行分配,内存空间浪费多,但空闲分区大小比较均匀,内存碎片少;
【例】哪种内存分区分配算法最好
【2】 内存分页
【2.1】内存碎片问题
0)问题:内存分区导致的内存碎片问题
- 解决方法:引入分页解决内存分区导致的内存碎片的问题;
- 实际的物理内存分配是采用分页而不是分区; (补充:虚拟内存是采用分区来分割或分配的)
【图解】内存碎片问题:
- 如上图所示,申请大小为160K的内存空间,而空闲分区表中的2个分区大小均小于160K且他们不是连续内存地址空间,所以无法直接分配160K的内存空间(需要先做空闲分区合并,把碎片收集在一起),即使总的内存大小为200K(大于160K);
1)内存碎片:
- 可用的总内存大小大于申请的内存大小,但内存分区不连续,且每个分区小于申请大小,导致内存申请失败;
2)解决方法:
- 内存紧缩;简单说,把白色的分区合并在一起,彩色分区合并在一次,这就需要复制段内容;
3)内存紧缩带来的问题(内存紧缩耗时耗力):
- 需要修改段基址的LDT,非常麻烦;
- 在内存紧缩过程中,上层用户应用程序无法执行,因为程序的多个段在复制内容到新的内存空间,段基址需要级联修改;
- 内存紧缩耗时长,容易导致机器假死;
小结: 内存紧缩方法解决内存碎片是不可行的;从而引出了内存分页解决内存碎片问题;
【2.2】内存分页
1)定义:操作系统初始化时,物理内存空间就分为多个页,每页占用4K内存 大小;
在处理段内存申请时,以页为单位把内存分配给段,如下表:
表2 内存分页表
物理内存页序号(页框号) | 段 | 页 |
7 | 段0 | 页3 |
6 | 段0 | 页0 |
5(空闲页) | ||
4(空闲页) | ||
3 | 段0 | 页2 |
2(空闲页) | ||
1 | 段0 | 页1 |
0(空闲页) |
2)内存分页优点:
- 优点1:不存在内存碎片,因为中间的空闲页可以分配给其他进程;
- 优点2:内存浪费少:一个段最多浪费1页,即一个段最多浪费4K(一个段由多个页组成);
【小结】
- 物理内存采用分页进行分割和分配,减少了内存浪费,避免了内存碎片;
- 用户希望把程序分为多个段;
- 综上,操作系统需要既支持分段也支持分页对内存进行分割和分配;
【例】以页为单位的内存分配
【问题】jmp 40 映射到哪个页?
- 内存页的分配情况,通过页表进行记录,且每个进程都有一个页表;页表寄存器时 CR3;其中页表基址存入进程的PCB;
【例】mov [0x2240], %eax
- 步骤1:0x2240 除以4K,即右移12位,得到页号为0x02, 偏移地址0x240;
- 步骤2:通过页号2找到叶框号为3;
- 步骤3: 得到物理地址0x3240 ;(3乘以4K,3左移12位得到基址0x3000);
- 步骤4: 页框3的基址为0x3000,偏移地址0x240,所以得到的物理地址为0x3000 + 0x240得到0x3240 ;
- 补充:把逻辑地址0x2240根据CR3存储的页表基址,可以翻译为物理地址 0x3240,这个内存地址翻译过程是由MMU完成的;
【小结】
- 一个程序由多个段组成;
- 每个段内容分散存储在内存中的多个页;每个段不是直接存入内存(那样会造成内存碎片与浪费);
- 为了在程序执行时能够实现重定位(找到段基址加上偏移地址得到物理内存地址),就需要建立页表存储页号与页框号映射关系,而找到页框号就可以计算出内存页基址;
- 因为每页大小4K,用过页框号乘以4K就得到页基址了;