操作系统导论读书笔记—虚拟化CPU之调度

那年仲夏 提交于 2020-01-22 16:11:09

1. 底层机制

操作采用时分共享CPU的方式实现虚拟化CPU。在实现虚拟化的同时,我们要保证整体性能且不失去CPU的控制权。

1.1 用户模式与内核模式

为了防止某个任务抢夺所有资源或者影响其它任务的运行,我们需要限制进程的操作,保证操作系统的控制权。
在用户模式下,应用程序不能完全访问硬件资源。在内核模式下,操作系统可以访问机器的全部资源。并提供了内核态与用户态的切换方式(陷阱指令也叫系统调用)。

1.1.1 系统调用

操作系统启动的第一件事,就是告诉硬件在发生某些异常时要运行哪些代码。每个都有一个内核栈,在进入内核和离开内核是,寄存器被保存和恢复。

1.1.2 用户态与内核态切换过程

内核态 硬件 用户态
系统启动初始化陷阱表
记录系统调用处理程序的地址
系统运行,创建进程、分配内存、加载程序代码段、设置程序栈、用寄存器/程序计数器填充内核栈、从陷阱返回
从内核恢复寄存器、转向用户态,跳到main函数地址
运行main函数直到调用系统调用,陷入操作系统
将寄存器保存到内核栈、进入内核态,跳到陷阱处理函数地址
处理陷阱、做系统调用的工作、从陷阱返回
从内核恢复寄存器、转向用户态,跳到陷阱之后的程序计数器
…从main返回,陷入(通过exit()
释放进程内存、停止进程

1.2 进程间切换

1.2.1 协作方式:等待系统调用

过去某些操作系统采用过这种方式,在这种模式下,操作系统相信进程会合理运行。运行过长的进程会主动放弃CPU,以便其他进程使用。
操作系统等待进程进行系统调用或者某些非法操作,从而重新控制CPU。这种方式比较被动,某些恶意程序可能进入无限循环,其他进程得不到运行机会。

1.2.2 非协作方式:操作系统控制

操作系统通过时间中断,运行预设的中断处理程序,重新获取CPU控制权。因此可以停止当前进程,并启动另一进程。在这个过程中,各种寄存器会被保存,当切换回原进程时,进程状态能得到恢复。

1.2.3 保存和恢复上下文

如何决定进行切换,OS会执行一些底层代码,即所谓的上下文切换。上下文切换在概念上很简单:操作系统要做的就是为当前正在运行的进程保存一些寄存器的值到它的内核栈,并为即将运行的进程恢复一些寄存器的值。这样操作系统可以确保操作系统从陷阱返回指令时,即将执行的进程变成了正在执行的进程。至此上下文切换完成。

1.2.4 时间中断进程切换

内核态 硬件 用户态
系统启动初始化陷阱表
记录系统调用处理程序和时钟处理程序
启动时钟中断
启动时钟,定时中断CPU
运行进程A
触发时钟中断,将寄存器保存到内核栈、进入内核态,跳到陷阱处理函数地址
处理陷阱、调用switch,将寄存器(A)保存到进程结构(A),将进程结构(B)恢复到寄存器(B)做系统调用的工作、从陷阱返回(进入B)
从内核栈(B)恢复寄存器(B)、转向用户态,跳到B的程序计数器
运行B进程

2. 调度策略

2.1 进程调度的相关概念

2.1.1 调度的性能指标

周转时间

任务的周转时间定义为任务完成时间减去任务到达系统的时间。

响应时间

任务的响应时间定义为任务首次运行时间减去任务到达系统的时间。

2.1.2 IO消耗型和CPU消耗型进程

进程可以分为IO消耗型和处理器消耗型。前者指需要大量IO操作的进程,CPU占有时间短但很频繁。这种需要很高的响应速度。相反,处理器消耗型把大量时间用在执行代码,CPU占有时间长,需要处理很多数据。
调度策略往往在这两个目标中寻找平衡:高响应速度和高吞吐量。

2.1.3 进程优先级

调度算法中最基本的一类就是基于优先级的调度。通常做法是优先级高的进程先运行,低的后运行,相同优先级的进程按轮转方式进行调度。

2.1.4 时间片

时间片是一个数值,它表示进程在被抢占前所能持续运行的时间。

2.2 几种基本调度算法

2.2.1 先入先出FIFO

先来的任务先运行,这种方法比较简单,且易于实现。但会存在护航效应,短任务排在长任务之后,需要等待长任务运行完后才能运行。就好像在超市里,我们就买了一瓶水,但是需要等前面买了几购物车商品的人先结账一样。这种体验让人很不爽。

2.2.2 最短任务优先SJF

在所有同时到达的任务中挑选运行时间最短的,这种方式能解决护航效应。但是实际应用中同时到达的这种情况很少。运行时间短的任务在时间长的任务后到达,在非抢占调度程序中,他会等待长任务的执行,从而又遭遇同样的护航问题。

2.2.3 最短完成时间优先STCF

向SCF中添加抢占机制,称为最短完成时间优先(STCF)调度程序。每当新工作进入系统时,它都会确定当前任务剩余工作和新工作,谁的剩余运行时间少,然后调度此任务。

2.2.4 轮转调度RR

随着分时操作系统的引入,系统的交互性要求越来越高,IO消耗型进程增多,响应时间越来越重要。
为了解决这个问题,轮转调度出现。RR在一个时间片内运行一个任务,然后切换到运行队列的下一个任务,并不是运行一个任务直到结束。时间片越短,响应速度越快,但是上下文切换的成本也就越高。因此系统需要在响应时间与上下文切换成本之间平衡。

2.2.5 总结对比

介绍了两种类型的调度程序。第一种类型(SJF、STCF)优化周转时间,但对响应时间不利。第二种类型(RR)优化响应时间,但对周转时间不利。在实际应用情况中,任务的运行长度是未知的,SJF/STCF并不能直接使用。

2.3 经典调度算法

2.3.1 多级反馈队列MLFQ

MLFQ有许多独立的队列,每个队列有不同的优先级。任何时刻,一个工作只能存在于一个队列中。MLFQ总是优先执行较高优先级的工作。对相同优先级队列的工作采用轮转调度。IO消耗型进程具备搞优先级,CPU消耗型进程会随着运行时间而降低。
MLFQ规则如下:
1:如果A的优先级>B的优先级,运行A(不运行B)。
2:如果A的优先级=B的优先级,轮转运行A和B。
3:工作进入系统时,放在最高优先级(最上层队列)。
4:一旦用完了其在某一层的时间配额(无论中间主动放弃多少次CPU),就降低其优先级(移入低一级队列)。
5:经过一段时间S,就将系统中所有工作重新加入最高优先级队列。
MLFQ不需要对工作的运行方式有先验知识,而是通过观察工作的运行过程给出对应的优先级。通过这种方式,MLFQ可以满足各种工作要求:对于IO消耗型任务,获得类似SJF/STCF的全局性能,同时对于长时间运行的CPU消耗型任务也可以公平地、稳定的运行。

2.3.2 比例份额调度

比例份额算法基于一个简单的想法:调度程序的最终目标,是确保每个工作获得一定比例的CPU时间,而不是优化周转时间和响应时间。
彩票调度
每隔一段时间进行一次彩票抽奖,以确定接下来应该运行哪个进程。应该频繁运行的进程,拥有更多的彩票会得到更多的运行机会。按概率获取调度机会,短时间运行获取CPU时间的结果可能不符合预期。
步长调度
步长值与票数值成反比,会有一个计数器[行程值]累计它的步长值,调度时,会挑选步长值最小行程值的进程。每运行一次,计数器增加一个步长值。这种调度方式具有周期性,在每个调度周期后,CPU时间比例正确。
这两种方法都不能很好适用于I/O,且票数问题没有确定的解决方式。

2.4 多处理器调度

2.4.1 多处理器的特性

多处理器架构
两个有缓存的CPU共享内存
缓存
缓存是很小但很快的存储设备,通常拥有内存中最热的数据的备份。
缓存是基于局部性概念的,时间局部性和空间局部性。时间局部性是指当一个数据被访问后,它很可能在不久的将来被再次访问。空间局限性指的是,当程序访问的地址为x的数据时很有可能会紧接着访问x周围的数据。
缓存一致性问题
在CPU1上运行的程序从内存地址A读取数据D,读取数据到缓存中进行修改得到D`,修改的值不会立即刷新到内存。这时CPU2上的程序访问内存地址A,获取的是旧值D。
线程同步
跨CPU访问共享数据和结构时,需要使用互斥原语,才能保证正确性。
缓存亲和度
一个进程在某个CPU上运行时,会在该CPU的缓存中维护许多状态。下次该进程在相同CPU上运行时,由于缓存中的数据而执行得更快。相反,在不同的CPU上执行,会由于需要重新加载数据而很慢。

2.4.2 单队列调度

沿用单处理器调度的基本架构,将所有工作放在一个单独的队列中。这种方法简单,改动较少。
这种方法有一些缺点。第一个是缺乏可扩展性对单个队列进行操作,需要通过加锁保证原子性。锁会带来很大的性能损失。
第二个就是没有考虑缓存亲和性。进程会在不同CPU之间经常切换。

2.4.3 多队列调度

每个CPU都有自己的调度队列,最初的调度模式是进入队列的任务只会在这个CPU运行。保证了缓存亲和性与扩展性,减小了锁带来的性能损失。
但只在一个CPU队列运行又带了负载不均衡的问题,各个CPU队列进程数量不相同。后来引入迁移技术,系统会迁移不同CPU队列的工作任务数量。
有一个基本的迁移技术,名为工作窃取。工作量较少的队列会不定期查看其它队列是不是比自己工作多,如果目标队列任务更多,就会从目标队列窃取一个或多个工作,实现负载均衡。

2.4.4 linux多处理器调度

实时调度
linux的实时调度是一种软实时工作方式。软实时的含义是,内核调度进程,尽力使进程在它的限定时间到来前运行,但内核不保证总能满足这些进程的要求。相反硬实时系统保证在一定条件下,可以满足任何调度要求。
linux提供了两种实时调度策略:SCHED_FIFO和SCHED_RR。而普通、非实时的调度策略是SCHED_NORMAL。
SCHED_FIFO
SCHED_FIFO实现了一种简单的、先入先出的调度算法:它不使用时间片。处于可运行状态SCHED_FIFO类型的进程会比任何SCHED_NORMAL类型进程都先得到调度。SCHED_FIFO类型的进程处于可运行状态,就会一直执行,直到阻塞或主动放弃CPU。除此之外会被更高优先级的SCHED_FIFO和SCHED_RR进程抢占,这时会放弃CPU。只要有SCHED_FIFO类型的进程运行,其他低优先级进程就只能等待它变为不可运行状态后才有机会运行。
SCHED_RR
SCHED_RR与SCHED_FIFO大体相同,只是SCHED_RR的进程耗尽分配给它的时间后就不在继续执行了。也就是说SCHED_RR是带时间片的SCHED_FIFO。这两种调度算法都是静态优先级的,优先级在0-99范围。
SCHED_NORMAL
SCHED_NORMAL级进程nice优先级对应100-139范围。这是一种完全公平调度方式(CFS)。CFS是确定的比例调度方法(类似之前的步长调度)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!