《操作系统导论》走读 - 不定期更新

1. CPU虚拟化

操作系统也被叫做资源管理器。
将单个cpu转换为看似无限数量的cpu、从而让许多程序看似同时运行、这就是所谓的虚拟化cpu。
时分共享CPU技术: 通过让一个进程只运行一个时间片,然后切换到其他进程中,操作系统提供了存在多个虚拟CPU的假象

1.1 虚拟化内存

内存就是一个字节数组、要读取内存、必须制定一个地址、才可以访问到内存中的数据、程序运行时、要一直访问内存、将所有的数据结构保存在内存中,并通过各种指令来访问他们,程序的每个指令也在内存中、因此每次读取指令也需要访问内存。
每个进程访问自己的私有虚拟地址空间、与其他运行的进程互不干扰。

1.2 寄存器

进程的机器状态的另一部分时寄存器,许多指令明确的读取或更新寄存器,有一些特殊的寄存器例如:
程序计数器(指令指针):主要告诉我们程序即将执行哪个命令,
栈指针、帧指针用于管理函数参数栈、局部变量及返回地址

1.3 进程

1.3.1 进程创建

程序如何转换为进程?

  1. 操作系统做的第一件事就是将代码和所有的静态资源加载到内存中,加载到内存的地址空间中,(现代计算机懒加载:在需要运行时才会加载到内存)
  2. 分配一些内存给程序的运行时栈(存放局部变量、函数参数和返回地址)
  3. 分配一些内存给堆(比如对象或者链表 、树等数据结构)
  4. 其他的特别任务,如输入/输出(I/O)相关任务。

1.3.2 进程状态

在这里插入图片描述

1.3.3 两个进程间的状态交换 有I/O

时间ProcessOPROCESS1
1运行就绪
2运行就绪
3运行就绪PROCESS0发起I/O
4阻塞运行PROCESS0阻塞
5阻塞运行PROCESS1运行
6阻塞运行
7就绪运行I/O完成
8就绪运行PROCESS1完成
9运行
10运行PROCESS0完成

2 CPU虚拟化机制

2.1 受限直接执行

为了实现虚拟化,操作系统需要以某种方式让许多任务共享物理CPU,让他们看起来像在同时运行。

2.1.1 直接运行协议(无限制)

操作系统程序
在进程列表上创建条目
为程序分配内存
将程序加载到内存中
根据argc/argv设置程序栈
清除寄存器
执行main方法
执行main方法
从main中执行return
释放进程的内存将进程从进程列表中删除

2.1.2 受限制的操作

2.2.1中存在一些问题 如果进程希望执行某些受限制操作(如磁盘发出I/O请求或申请资源)怎么办?

2.1.2.1 用户模式

为了解决上述问题 操作系统出了2种模式。在用户模式下运行的代码会受到限制如发出I/O指令、操作系统会引发异常、可能会终止进程。

2.1.2.2 内核模式

在内核模式下、运行的代码可以做他喜欢做的事情。包括特权操作。

2.1.2 模式间的切换

操作系统允许内核模式中的权限小力度的透给用户模式使用。
要执行这些指令、程序必须执行特殊的陷阱指令。该指令可以让程序跳入内核并将权限提升到内核模式。完成后操作系统调用特殊的陷阱返回指令。降低权限回到用户指令。
陷阱指令如何知道在os中运行那些代码?内核模式通过在启动中设置陷阱表来实现。

2.1.2 直接运行协议(受限)

操作系统@内核模式硬件程序
初始化陷阱表
记住系统调用处理程序的地址
在进程列表上创建条目
为程序分配内存
将程序加载到内存中
根据argc/argv设置程序栈
用寄存器/程序计数器填充内核栈
从陷阱返回
从内核栈恢复寄存器
转向用户模式
跳到main
运行main
调用系统调用、陷入操作系统
将寄存器保存内核栈
转向内核模式
跳到陷阱处理程序
处理陷阱
系统调用
陷阱返回
从内核栈恢复寄存器
转向用户模式
跳到陷阱之后的程序计数器
…从main返回 exit()
释放进程的内存将进程从进程列表中清除

2.2 协作方式和时钟中断

协作方式:等系统自动调用并获得CPU控制权
时钟中断:时钟设备可以编程为每隔几毫秒产生一次中断、中断时正在运行的程序终止,操作系统预先配置的中断处理程序会开始运行、操作系统重新获得CPU控制权,并安排后续的执行等。

操作系统硬件备注
初始化陷阱表.
记录地址:系统调用处理程序、时钟处理程序.
启动中断时钟.
启动时钟、每隔x ms中断CPU.
.进程A
时钟中断、将寄存器A保存到内核栈A 转向内核模式、跳到陷阱处理程序.
处理陷阱
将寄存器A保存到进程结构A、将进程结构B恢复到寄存器B
从陷阱返回(进入B)
.
从内核栈B恢复寄存器B、转向用户模式、跳到B的程序计数器.
.进程B

3. 进程调度

调度指标:
T周转时间= T完成时间-T到达时间
T响应时间=T首次运行-T到达时间

3.1 先进先出FIFO

按照先进先出调度、假设A、B、C同时到达系统、且A比B早一点点、B比C早一点点 他们的运行时间都是10m 那么平均周转时间就是T周转时间 = (10+20+30)/3 = 20 (因为程序串行执行需要等上一个进程执行完成)。如果A要运行100秒、其他的运行10秒呢?
平均周期:(100+110+120)/3

3.2 最短任务优先SJF

最短时间的优先执行、次短的其次执行。
在用上面的例子,平均周期(10+20+120)/3。
虽然平均周期得到了提升但假如任务不是同时到达而是分别到达呢、比如A在t=0时到达、BC在t=10到达?
这样BC会被迫等待A执行完成在进行调度

3.3 最短完成时间优先(STCF)

上述的两种调度由于都是非抢断式的、需要等上一个任务执行完成才会执行下一个。但是我们可以通过前面说的时钟中断方式重新获得CPU控制权并将当前执行程序保存至上下文中、进行后续调用。即向SJF添加抢占、称为最短完成时间优先。

3.4 轮转

根据时钟中断不停切换运行的进程(RR时间片必须是时钟中断的倍数)直到完成。
摊销:设置足够长的时间片用于降低上下文切换成本比如时间片100m 上下文切换1m这样比例为1%。

4. 调度

4.1 多级反馈队列 MLFQ

4.1.1 基本原则

MLFQ中有许多独立的队列、每隔队列有不同的优先级、任何时刻、一个任务只能存在于一个队列中。MLFQ会优先执行较高优先级的工作。当每个队列中存在多个任务时,该队列采用轮转调度。MLFQ通过任务的历史执行情况来预测他未来的行为。

  • 规则1:如果A优先级>B,运行A。
  • 规则2:如果A、B优先级相等,轮转执行。
  • 规则3:工作进入系统时、放在最高优先级。
  • ~~ 规则4a:工作用完整个时间片后、降低其优先级 下移一位。~~
  • ~~ 规则4b:如果工作在时间片内主动释放CPU、优先级不变。~~
  • 规则4:一旦程序用完时间配额、无论主动放弃多少次、都会降级
  • 规则5:每经过一段时间、系统就会将所有任务重新加入最高优先级

4.2 比例份额

也称公平份额调度程序、现代使用的典型例子彩票调度:
每隔一段时间、都会进行一次彩票选举,以确定接下来要执行的程序,越是应该频繁运行的程序、越是应该拥有更多赢得彩票的机会。

4.2.1 基本概念

  • 彩票调度:为每个程序设置票数、在时间片执行完成后、进行随机选举确定下一个执行程序。
    例:A 100 B 50 C 40 随机数为120 则运行B
  • 步长调度:认为ABC开始都是0,根据票数设置步长。根据最小布长进行切换
A 步长=100B 步长=200C 步长=40谁执行
00. 0. A执行
1000.0.B
100200.0.C
10020040.C.
10020080..C
100200.120.A
200200.160.C

4. 虚拟化内存

4.1 机制:地址转换

直接受限访问的补充,硬件每次内存访问进行处理(指令获取、数据读取、写入)、将指令中的虚拟地址转换为数据实际存储的物理地址。每次访问,将内存引用重定位到内存中实际位置。营造出每个进程都有自己独立的内存空间

4.1.1 动态重定向

如何让物理地址不是从0开始而是从指定位置开始 :
cpu需要2个硬件寄存器:基址寄存器和界限寄存器(也被称为限制寄存器),这两个寄存器可以让我们的地址空间放在屋里内存任意位置,有能确保只能访问自己的空间。
基址寄存器: 用于保存物理地址开始位置。
界限寄存器: 用于确保只能访问自己的空间。
保护位: 用于判断进程进操是否有特定访问权限

通过基地寄存器和界限寄存器实现的虚拟内存会导致非常多的浪费、那如何支持大地址空间实例呢?

4.1.2 分段

在MMU(内存管理单元)中引入不止一个基址和界限寄存器,给每个地址空间内的逻辑段一对,一个段是地址空间内一个连续定长的区域,如代码、堆、栈。
如何快速定位到虚拟地址对应的物理空间真实位置?
显式方式,用虚拟地址开头的几位标识不同的段。如下图、有3个段、所以取前两位作为标记,用14位虚拟地址来标识,如下
在这里插入图片描述

堆判断

14位虚拟地址:堆地址在虚拟地址上为41024 = 4096,4096换算2进制14位为 01000001101000,所以如果为01、硬件就知道该地址是堆。
在这里插入图片描述
以虚拟地址4200为例(由于堆位置在虚拟地址4KB位置应该为4096),所以偏移量就是4200-4096=104,加上基址寄存器存的物理地址34KB,真实物理地址为34
1024+104 = 34920,这就是真实地址位置。

栈判断

栈与其他存储不同、栈是反向扩展、比如上图栈初始位置在28KB,延伸到26KB,对应的虚拟地址则是16-14KB。
硬件为了支持该反向判断,须新增是否正向增长标记、1代表向下、0代表线下。
如:
15KB虚拟地址二进制为:11110000000000,前两位为指定段、然后我们要处理偏移量3KB

4.1.3 空闲队列

在这里插入图片描述

分割:当程序请求内存时,通过空闲列表找到大小足够的内存块,如上图如果申请大小为1的空间,内存会变成1-10 空闲,10-20 使用,20-21使用,21-30空闲。
合并:当程序调用free()归还空间时,程序会判断与其相邻的队列是否空闲进行合并处理。

4.1.3.1 追踪已分配内存空间的大小

为了方便快速定位内存位置,我们需要在头块(header)中保存一些其他信息,如内存大小,和一个幻数用于完整性检查,当操作系统分配内存时并非找到需要的内存大小、而是需要找到内存大小N+头块大小的空间列

4.1.3.2 让堆增长

当堆被用光后无法在继续分配空间,会在向操作系统申请更大的空间,将他们映射到请求进程的地址空间中去,并返回新的堆的末尾地址。就得到了更大的堆。

4.1.4 基本策略

最优匹配

遍历整个空闲列表,找到和请求大小一样或更大空间的空闲块、最后选择候选者中最小的一块返回。

最差匹配

找到最大的块并进行分割满足用户需求,再将剩余的块加入空闲列表。

首次匹配

找到第一个足够大的块,将请求的空间返回给用户,同样将剩余的空闲空间留给后续请求。优势不需要遍历所有的空闲块,但可能会让空闲列表开头有很多的小块。

下次匹配

不同于首次匹配、下次匹配多维护了一个指针,指向上一次结束的位置

分离空闲列表

基本想法 如果某个程序经常申请一种或几种大小的内存空间,那就用一个独立的列表来单独管理,其他大小的请求交给通用的内存分配程序。
如超级工程师jeff设计的厚块分配程序,在内核启动时,为可能频繁请求的内核程序创建一些对象缓存,并分离出一些特定大小的空闲队列。

4.1.5 分页

有人说,操作系统有2种方法来解决空间管理问题、第一种是将分割成不同长度大小的分片,就像虚拟内存中的分段、这种方法的弊端就是会产生大量的空间碎片,第二种就是将空间分割成固定长度的分片,这种思想也叫做分页。

页表

在这里插入图片描述
为了记录地址空间每个虚拟页放在物理内存中的位置,主要作用是为地址空间的每个虚拟页 保存地址转换。如上图页表具有四个条目:虚拟页0→物理帧3。

  • 有效位:如栈和堆在空间中是两端、中间部分为无效。
  • 保护位:是否可执行访问
  • 存在位:表示该页是在物理存储器上还是磁盘上。
  • 参考位:追踪页是否被访问。
  • 脏位:表明页面被带入内存后是否被修改过

未完待续

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值