为了深入计算机底层,无知的我正在参考《王道考研》学习计算机操作系统。
笔记特点是 我重新整理了涉及资料的一些语言描述、排版,而使用了自己比较容易理解的描述、同样是回答了一些常见关键问题
3.1_5_动态分区分配算法
首次适应算法
一句话 每次使用满足要求中的地址最小的空闲分区
空闲分区以地址递增的次序排列。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
最佳适应算法
一句话 每次使用满足要求中容量最小的空闲分区
空闲分区按容量递增次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
缺点:每次会留下外部碎片
最坏适应算法
又称 最大适应算法(Largest Fit)
一句话 每次使用满足要求中容量最大的空闲分区
如何实现:空闲分区按容量递减次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
缺点:如果之后有“大进程”到达,就没有内存分区可用了。
邻近适应算法
一句话 与首次适应算法根本唯一不同是 每次查找的开始位置是 上一个查找停止的位置
空闲分区以地址递增的顺序排列(可排成一个循环链表)。每次分配内存时从上次查找结束的位置开始查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
优点 相较于首次适应算法需要的查找次数减少
四种算法对比
首次适应算法
每次都要从头查找,每次都需要检索低地址的小分区。 但是这种规则也决定了当低地址部分有更小的分区可以满足需求时, 会更有可能用到低地址部分的小分区,也会更有可能把高地址部分的大分区保留下来(最佳适应算法的优点)
邻近适应算法
可能会导致无论低地址、高地址部分的空闲分区都有相同的概率被使用,也就导致了高地址部分的大分区更可能被使用,划分为小分区,最后导致无大分区可用(最大适应算法的缺点)
=>四种算法中,首次适应算法的效果反而更好
知识回顾与重要考点
3.1_6_基本分页存储管理方式
分页存储机制
将内存空间分为一个个大小相等的分区(比如:每个分区4KB)并给它们编号。
- 每个分区就是一个“页框”(页框=页帧=内存块=物理块=物理页面)。
- 每个页框有一个编号,即“页框号”(页框号=页帧号=内存块号=物理块号=物理页号),页框号从0开始。
将进程的逻辑地址空间也分为与页框大小相等的一个个部分并给它们编号。
- 每个部分称为一个“页”或“页面”。
- 每个页面也有一个编号,即“页号”,页号也是从0开始。
操作系统以页框为单位为各个进程分配内存空间。进程的每个页面分别放入一个页框中。
- 也就是说,进程的页面与内存的页框有一一对应的关系。
- 各个页面不必连续存放,可以放到不相邻的各个页框中。
- 进程的最后一个页面可能没有一个页框那么大。也就是说,分页存储有可能产生内部碎片,因此页框不能太大,否则可能产生过大的内部碎片造成浪费
如何实现地址的转换
如何确定一个逻辑地址对应的页号、页内偏移量
**Eg:在某计算机系统中,页面大小是50B。某进程逻辑地址空间大小为200B,则逻辑地址 110 对应的页号、页内偏移量是多少?**如下将进程分为几大块
计算步骤
页号 = 110 / 50 = 2
页内偏移量 = 110 % 50 = 10
- 页号 = 逻辑地址 / 页面长度(一个页面的大小)。取除法的整数部分
- 页内偏移量 = 逻辑地址 % 页面长度。取除法的余数部分
- 页面在内存中的起始地址 = 通过页号查询页表(逻辑地址(页号,页内偏移量) )
- 实际的物理地址页面 = 在内存中的起始地址+页内偏移量
为何页面大小要取2的整数幂?
Eg:假设某计算机用32 个二进制位表示逻辑地址,页面大小为 4KB = 212B = 4096B
0号页的逻辑地址范围应该是 0~4095,用二进制表示应该是:
00000000000000000000000000000000 ~ 00000000000000000000111111111111
1号页的逻辑地址范围应该是 4096~8191,用二进制表示应该是:
00000000000000000001000000000000 ~ 00000000000000000001111111111111
2号页的逻辑地址范围应该是 8192~12287,用二进制表示应该是:
00000000000000000010000000000000 ~ 00000000000000000010111111111111
Eg:逻辑地址 2,用二进制表示应该是 00000000000000000000000000000010
页号 = 2/4096 = 0 = 00000000000000000000,页内偏移量 = 2%4096 = 2 = 000000000010
Eg:逻辑地址 4097,用二进制表示应该是 00000000000000000001000000000001
页号 = 4097/4096 = 1 = 00000000000000000001,页内偏移量 = 4097%4096 = 1 = 000000000001
结论:末尾 K 位即为页内 偏移量,其余部分就是页号
总结:页面大小刚好是 2 的整数幂有什么好处?
①逻辑地址的拆分更加迅速——如果每个页面大小为 2KB,用二进制数表示逻辑地址,则末尾 K 位 即为页内偏移量,其余部分就是页号。因此,如果让每个页面的大小为 2 的整数幂,计算机硬件就 可以很方便地得出一个逻辑地址对应的页号和页内偏移量,而无需进行除法运算,从而提升了运行 速度。
②物理地址的计算更加迅速——根据逻辑地址得到页号,根据页号查询页表从而找到页面存放的内存块号,将二进制表示的内存块号和页内偏移量拼接起来,就可以得到最终的物理地址。
=>就是计算机硬件可以很快速的把逻辑地址拆分成页号,页内偏移量
逻辑地址结构
**地址结构包含两个部分:前一部分为页号,后一部分为页内偏移量 W。**如下图
在下图所示的例子中,地址长度为 32位,其中 0~11位 为“页内偏移量”,或称“页内地址”;12~31 位为“页号”。
计算最多有多少个页面&一个页面的大小
如果有 K 位表示“页内偏移量”则说明该系统中一个页面的大小是 2^K个内存单元
如果有 M 位表示“页号”则说明在该系统中,一个进程最多允许有 2^M 个页面
页面大小有可能不是2的整数次幂的计算方法
页号 = 逻辑地址 / 页面长度 (取除法的整数部分)
页内偏移量 = 逻辑地址 % 页面长度(取除法的余数部分)
重要的数据结构——页表
每个页表项多大?占几个字节?
Eg:假设某系统物理内存大小为 4GB,页面大小为 4KB,则每个页表项至少应该为多少字节?
页表项连续存放,因此页号可以是隐含的,不占存储空间(类比数组)
Eg:假设页表中的各页表项从内存地址为 X 的地方开始连续存放。如图是页表
![image-20220508141743262](https://i-blog.csdnimg.cn/blog_migrate/65dff13fd5a796aae855784afa83a6c4.png)
那么 i 号页表项的存放地址 = X + 3*I
因此,页表中的页号可以是隐含的,即页号不占用存储空间
知识回顾与重要考点
3.1_8_具有快表的地址变换机构
局部性原理——引出改进策略
时间局部性:如果执行了程序中的某条指令,那么不久后这条指令很 有可能再次执行;如果某个数据被访问过,不久之后该数据很可能再 次被访问。(因为程序中存在大量的循环)
空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的 存储单元也很有可能被访问。(因为很多数据在内存中都是连续存放的)
引出问题:上小节介绍的基本地址变换机构中,每次要访问一个逻辑地址,都需要查询内存中的页表。由于局部性原理,可能连续很多次查到的都是同一个页表项
快表是什么(TLB)
快表,又称联想寄存器(TLB, translation lookaside buffer ),是一种访问速度比内存快很多的高速缓存(TLB不是内存!),用来存放最近访问的页表项的副本,可以加速地址变换的速度。 与此对应,内存中的页表常称为慢表。
TLC特点&实例
块表 机制&特点
引入快表机制后,访问一个逻辑地址的速度快多了
例:某系统使用基本分页存储管理,并采用了具有快表的地址变换机构。访问一次快表耗时 1us,访问一次内存耗时 100us。若快表的命中率为 90%,那么访问一个逻辑地址的平均耗时是多少?
- (1+100) * 0.9 + (1+100+100) * 0.1 = 111 us
有的系统支持快表和慢表同时查找,如果是这样,平均耗时应该是
- (1+100) * 0.9 + (100+100) * 0.1 =110.9us
若未采用快表机制,则访问一个逻辑地址需要
- 100+100 = 200us
知识回顾与重要考点
TLB 和 普通 Cache 的区别——TLB 中只有页表项的副本,而普通 Cache 中可能会有其他各种数据的副本
3.1_9_两级页表
单级页表存在的问题
Eg:某计算机系统按字节寻址,支持 32 位的逻辑地址,采用分页存储管理,页面大小为4KB,页表项长度为 4B。
页面大小 4KB = 212B,因此页内地址要用12位表示,剩余 20 位表示页号。
因此,该系统中用户进程最多有 220 页。
相应的,一个进程的页表中,最多会有 220 = 1M = 1,048,576 个页表项,所以一个页表最大需要 220 * 4B = 222 B,共需要 222/212 = 210个页框存储该页表。
引出问题
根据页号查询页表的方法:K 号页对应的页表项存放位置 = 页表始址 + K * 4 (需要专门给进程分配
210 = 1024 个连续的页框来存放它的页表)要在所有的页表项都连续存放的基础上才能用这种方法找到页表项
根据局部性原理可知,很多时候,进程在一段时间内只需要访问某几个页面就可以正常运行了。因此没有必要让整个页表都常驻内存。
如何解决单级页表的问题?
问题一:页表必须连续存放,因此当页表很大时,需要占用很多个连续的页框。
问题二:没有必要让整个页表常驻内存,因为进程在一段时间内可能只需要访问某几个特定的页面。
把页表再分页并离散存储,然后再建立一张页表记录页表各个部分的存放位置,称为页目录表,或称外层页表,或称顶层页表
两级页表的原理、地址结构
32位逻辑地址空间,页表项大小为4B,页面大小为 4KB,则页内地址占12位
两级页表结构如图
如何实现地址变换
例:将逻辑地址 (0000000000,0000000001,111111111111) 转换为物理地址(用十进制表示)。
![image-20220508174607773](https://i-blog.csdnimg.cn/blog_migrate/59ba308516945f7a28df697980b05985.png)
![image-20220508174627620](https://i-blog.csdnimg.cn/blog_migrate/5c0c26080b638be71a3e8230724b2735.png)
最终要访问的内存块号为4
该内存块的起始地址为 4*4096 = 16384
页内偏移量为 4095
最终的物理地址为16384 + 4095= 20479
转换步骤
①按照地址结构将逻辑地址拆分成三部分
②从PCB 中读出页目录表始址,再根据一级页号查页目录表,找到下一级页表在内存中的存放位置
③根据二级页号查二级页表(页表项存放位置: 3 * 4096+1 * 4 = 12,292 ),找到最终想访问的内存块号
④结合页内偏移量得到物理地址
如何解决单级页表的问题?
问题二:没有必要让整个页表常驻内存,因为进程在一段时间内可能只需要访问某几个特定的页面。
可以在需要访问页面时才把页面调入内存(虚拟存储技术)。可以在页表项中增加一个标志位,用于表示该页面是否已经调入内存
若想访问的页面不在内存中,则产生缺页中断(内中断/异常),然后将目标页面从外存调入内存。如下图
需要注意的几个细节
若分为两级页表后,页表依然很长,则可以采用更多级页表,一般来说各级页表的大小不能超过一个页面
例:某系统按字节编址,采用 40 位逻辑地址,页面大小为 4KB,页表项大小为 4B,假设采用纯页式存储,则要采用()级页表,页内偏移量为()位?
- 页面大小 = 4KB =212B,按字节编址,因此页内偏移量为12位
- 页号 = 40 - 12 = 28 位
- 页面大小 = 212B,页表项大小 = 4B ,则每个页面可存放 212 / 4 = 210 个页表项
- 因此各级页表最多包含 210 个页表项,需要 10 位二进制位才能映射到 210个页表项,因此每一级的页表对应页号应为10位。总共28位的页号至少要分为三级。如图
引出问题 为什么不分为二级?
如果只分为两级页表,则一级页号占 18 位, 也就是说页目录表中最多可能有 218个页表项, 显然,一个页面是放不下这么多页表项的。
两级页表的访存次数分析(假设没有快表机构)
第一次访存:访问内存中的页目录表
第二次访存:访问内存中的二级页表
第三次访存:访问目标内存单元
知识回顾与重要考点
3.1_10_基本分段存储管理方式
分段
进程的地址空间规则:按照程序自身的逻辑关系划分为若干个段,每个段都有一个段名(在低级语言中,程序员使用段名来编程),每段从0开始编址
内存分配规则:以段为单位进行分配,每个段在内存中占据连续空间,但各段之间可以不相邻。
分段系统的逻辑地址结构的组成是 由段号(段名)和段内地址(段内偏移量)所组成。如:
段号的位数决定了每个进程最多可以分几个段;段内地址位数决定了每个段的最大长度是多少
在上述例子中,若系统是按字节寻址的,则 段号占16位,因此在该系统中,每个进程最多有 216 = 64K 个段
段内地址占 16位,因此每个段的最大长度是 216 = 64KB
![image-20220508183009624](https://i-blog.csdnimg.cn/blog_migrate/20ef504d8c609521382ccbbea18dc5e8.png)
![image-20220508183027925](https://i-blog.csdnimg.cn/blog_migrate/c033d7213fa908ef81e20c353d28f8d8.png)
段表
每个段对应一个段表项
- 其中记录了该段在内存中的起始位置(又称 “基址”)
- 段的长度
各个段表项的长度是相同的
例如:某系统按字节寻址,采用分段存 储管理,逻辑地址结构为(段号16位, 段内地址16位),
因此用16位 即可表示最大段长。物理内存大小为4GB(可用32位表示整个物理内 存地址空间)。
因此,可以让每个段表项占 16+32 = 48位,即6B。
由于段表项长度相同,因此段号可以是隐含的,不占存储空间。若段表存放的起始地址为 M,则 K号段对应的段表项存放的地址为 M + K*6
![image-20220508183222567](https://i-blog.csdnimg.cn/blog_migrate/c15f8e1b9d90d7ba4316b4f254d22571.png)
![image-20220508183234479](https://i-blog.csdnimg.cn/blog_migrate/1d45675a111d59cbb77c8e354c695e89.png)
地址变换
分段、分页管理的对比
- 页是信息的物理单位。分页的主要目的是为了实现离散分配,提高内存利用率。分页仅仅是系统管理上的需要,完全是系统行为,对用户是不可见的。
- 段是信息的逻辑单位。分段的主要目的是更好地满足用户需求。一个段通常包含着一组属于一个逻 辑模块的信息。分段对用户是可见的,用户编程时需要显式地给出段名。
- 页的大小固定且由系统决定。段的长度却不固定,决定于用户编写的程序。
- 分页的用户进程地址空间是一维的,程序员只需给出一个记忆符即可表示一个地址。
- 分段的用户进程地址空间是二维的,程序员在标识一个地址时,既要给出段名,也要给出段内地址。
分段比分页更容易实现信息的共享和保护。
不能被修改的代码称为纯代码或可重入代码(不属于临界资源),这样的代码是可以共享的。可修改的代码是不能共享的(比如,有一个代码段中有很多变量,各进程并发地同时访问可能造成数据 不一致)
访问一个逻辑地址需要几次访存?
分页(单级页表):第一次访存——查内存中的页表,第二次访存——访问目标内存单元。总共两次访存
分段:第一次访存——查内存中的段表,第二次访存——访问目标内存单元。总共两次访存与分页系统类似,分段系统中也可以引入快表机构,将近期访问过的段表项放到快表中,这样可以少一次访问,加快地址变换速度。